six-degrees 0.4.11 → 0.5.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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +9 -9
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +12 -12
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js +3 -2
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js +3 -3
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +14 -14
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +14 -14
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +14 -14
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +14 -14
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +14 -14
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +14 -14
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +14 -14
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +14 -14
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +14 -14
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0bat8nx._.js → [root-of-the-server]__13dx4sd._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +8 -0
- package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +8 -8
- package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1ap1tu_._.js → _0gtv9jb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0jdkqgi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +22 -10
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +6 -5
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +19 -3
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/03bziijw5srol.js +1 -0
- package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
- package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
- package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +34 -0
- package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
- package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +2 -0
- package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +1 -0
- package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +1 -0
- package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +43 -0
- package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +1 -0
- package/.next/standalone/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
- package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +1 -0
- package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +1 -0
- package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
- package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +1 -0
- package/.next/standalone/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
- package/.next/standalone/CHANGELOG.md +187 -0
- package/.next/standalone/app/api/ingest/route.js +40 -1
- package/.next/standalone/app/api/scraper/route.js +7 -1
- package/.next/standalone/app/api/settings/sector-preview/route.js +2 -1
- package/.next/standalone/app/components/AppTabs.js +59 -0
- package/.next/standalone/app/components/ChainView.js +303 -89
- package/.next/standalone/app/components/CirclePanel.js +162 -0
- package/.next/standalone/app/components/CompanyScores.js +2 -2
- package/.next/standalone/app/components/FieldStep.js +4 -4
- package/.next/standalone/app/components/FilterPanel.js +121 -183
- package/.next/standalone/app/components/ForceGraph.js +41 -3
- package/.next/standalone/app/components/GalaxyLab.js +26 -31
- package/.next/standalone/app/components/LegacyScoresCard.js +2 -2
- package/.next/standalone/app/components/MapControls.js +154 -0
- package/.next/standalone/app/components/NoteDetail.js +118 -0
- package/.next/standalone/app/components/PathsAnalyzer.js +147 -110
- package/.next/standalone/app/components/PeopleMap.js +308 -0
- package/.next/standalone/app/components/ScanRadar.js +129 -0
- package/.next/standalone/app/components/ScanStatusBar.js +71 -27
- package/.next/standalone/app/components/SeparationView.js +626 -118
- package/.next/standalone/app/components/Sidebar.js +28 -2
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +1 -1
- package/.next/standalone/app/components/settings/SectorSection.js +2 -2
- package/.next/standalone/app/components/settings/TierSection.js +1 -1
- package/.next/standalone/app/components/settings/TitleSection.js +149 -0
- package/.next/standalone/app/components/views.js +15 -14
- package/.next/standalone/app/page.js +89 -156
- package/.next/standalone/app/paths/page.js +39 -27
- package/.next/standalone/app/profile/page.js +33 -5
- package/.next/standalone/app/queue/page.js +56 -34
- package/.next/standalone/app/scores/page.js +16 -66
- package/.next/standalone/app/settings/page.js +27 -13
- package/.next/standalone/app/setup/page.js +298 -205
- package/.next/standalone/app/social/SocialHub.js +374 -0
- package/.next/standalone/app/social/page.js +10 -362
- package/.next/standalone/lib/chain-layout.js +105 -35
- package/.next/standalone/lib/degrees.js +0 -6
- package/.next/standalone/lib/dot-rings.js +25 -0
- package/.next/standalone/lib/galaxy-lab.js +65 -6
- package/.next/standalone/lib/ingest.js +2 -0
- package/.next/standalone/lib/island.js +40 -0
- package/.next/standalone/lib/linkedin-limits.js +6 -2
- package/.next/standalone/lib/map-heat.js +20 -0
- package/.next/standalone/lib/network-health.js +1 -1
- package/.next/standalone/lib/notifications.js +121 -0
- package/.next/standalone/lib/people-map.js +82 -0
- package/.next/standalone/lib/rarity.js +45 -3
- package/.next/standalone/lib/reach.js +7 -6
- package/.next/standalone/lib/rpc.js +15 -4
- package/.next/standalone/lib/scan-pace.js +46 -0
- package/.next/standalone/lib/scoring.js +39 -8
- package/.next/standalone/lib/scraper-client.js +19 -6
- package/.next/standalone/lib/sector-focus.js +4 -4
- package/.next/standalone/lib/separation.js +137 -10
- package/.next/standalone/lib/settings-effects.js +20 -11
- package/.next/standalone/lib/settings.js +3 -0
- package/.next/standalone/lib/tier-grid.js +99 -0
- package/.next/standalone/lib/title-ranking.js +83 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +51 -13
- package/.next/static/chunks/03bziijw5srol.js +1 -0
- package/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
- package/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
- package/.next/static/chunks/0g_fy2034bp0x.js +34 -0
- package/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
- package/.next/static/chunks/0w-byiwb2zax8.js +2 -0
- package/.next/static/chunks/1g7jcovdejc9-.js +1 -0
- package/.next/static/chunks/1i51yylxdzht1.js +1 -0
- package/.next/static/chunks/1zf8swaasjf1e.js +43 -0
- package/.next/static/chunks/2rqtpccruavf2.js +1 -0
- package/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
- package/.next/static/chunks/30x3oq_st7a5r.js +1 -0
- package/.next/static/chunks/355ryio1b7sm1.js +1 -0
- package/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
- package/.next/static/chunks/3fci6qi39-1n5.js +1 -0
- package/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
- package/CHANGELOG.md +187 -0
- package/lib/chain-layout.js +105 -35
- package/lib/degrees.js +0 -6
- package/lib/dot-rings.js +25 -0
- package/lib/galaxy-lab.js +65 -6
- package/lib/ingest.js +2 -0
- package/lib/island.js +40 -0
- package/lib/linkedin-limits.js +6 -2
- package/lib/map-heat.js +20 -0
- package/lib/network-health.js +1 -1
- package/lib/notifications.js +121 -0
- package/lib/people-map.js +82 -0
- package/lib/rarity.js +45 -3
- package/lib/reach.js +7 -6
- package/lib/rpc.js +15 -4
- package/lib/scan-pace.js +46 -0
- package/lib/scoring.js +39 -8
- package/lib/scraper-client.js +19 -6
- package/lib/sector-focus.js +4 -4
- package/lib/separation.js +137 -10
- package/lib/settings-effects.js +20 -11
- package/lib/settings.js +3 -0
- package/lib/tier-grid.js +99 -0
- package/lib/title-ranking.js +83 -0
- package/package.json +1 -1
- package/scripts/scrape.py +51 -13
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_05gt5ya.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0btpv1t.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0-85h6g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_08cfj2b._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0l8cxmo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_14iv_zy._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_212xw8q._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_19c2d9c._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +0 -3
- package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/standalone/.next/static/chunks/15sjx07xhpn6p.js +0 -1
- package/.next/standalone/.next/static/chunks/160wjb73ddwqd.js +0 -6
- package/.next/standalone/.next/static/chunks/1a74dky22dxd6.js +0 -1
- package/.next/standalone/.next/static/chunks/1c_n0973t07l0.js +0 -2
- package/.next/standalone/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
- package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +0 -1
- package/.next/standalone/.next/static/chunks/297e1z34yi--z.js +0 -1
- package/.next/standalone/.next/static/chunks/2_ak6qjxb6oa5.js +0 -1
- package/.next/standalone/.next/static/chunks/2kfhc58df2xyo.js +0 -1
- package/.next/standalone/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
- package/.next/standalone/.next/static/chunks/3ui4d-uqkqj5d.js +0 -2
- package/.next/standalone/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
- package/.next/standalone/app/components/OrbitGraph.js +0 -737
- package/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/static/chunks/15sjx07xhpn6p.js +0 -1
- package/.next/static/chunks/160wjb73ddwqd.js +0 -6
- package/.next/static/chunks/1a74dky22dxd6.js +0 -1
- package/.next/static/chunks/1c_n0973t07l0.js +0 -2
- package/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
- package/.next/static/chunks/236f0jtp68j_h.js +0 -1
- package/.next/static/chunks/297e1z34yi--z.js +0 -1
- package/.next/static/chunks/2_ak6qjxb6oa5.js +0 -1
- package/.next/static/chunks/2kfhc58df2xyo.js +0 -1
- package/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
- package/.next/static/chunks/3ui4d-uqkqj5d.js +0 -2
- package/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
- /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
- /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
- /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
package/lib/separation.js
CHANGED
|
@@ -86,11 +86,6 @@ export function comparePower(a, b) {
|
|
|
86
86
|
}
|
|
87
87
|
const NO_ROUTE = { id: null, bridge: null };
|
|
88
88
|
|
|
89
|
-
/** "Ways in" order: most ways in first, then the power order. The rank shown stays the power rank. */
|
|
90
|
-
export function compareWaysIn(a, b) {
|
|
91
|
-
return b.waysIn - a.waysIn || comparePower(a, b);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
89
|
/**
|
|
95
90
|
* @param {object[]} degree2 2nd-degree rows (one per bridge per person)
|
|
96
91
|
* @param {object[]} connections 1st-degree rows, the bridges
|
|
@@ -236,6 +231,10 @@ export function dotRadius(waysIn) {
|
|
|
236
231
|
|
|
237
232
|
export const MIN_BRIDGE_GAP = 18;
|
|
238
233
|
const TOP_PAD = 16;
|
|
234
|
+
/** Grey dots for mutual connections in circles not scanned yet, at most this many drawn. */
|
|
235
|
+
export const GHOST_MAX = 60;
|
|
236
|
+
const GHOST_LABEL = 40; // room under the last connection for the line that counts them
|
|
237
|
+
const PILL_W_LAYOUT = 236; // SeparationView's PILL_W: the connection pills' width as cards
|
|
239
238
|
|
|
240
239
|
/**
|
|
241
240
|
* The summit map: You → the bridges → the top of the list, every route drawn.
|
|
@@ -249,10 +248,13 @@ const TOP_PAD = 16;
|
|
|
249
248
|
*
|
|
250
249
|
* @param {object[]} top the first K people of whatever the list shows
|
|
251
250
|
* @param {number} width map width in px
|
|
251
|
+
* @param {{ cards?: boolean }} opts cards: people drawn as cards and
|
|
252
|
+
* connections as pills (Blake, 2026-10-02), which need more room per row
|
|
252
253
|
* @returns {{ width, height, you, people, bridges, links, spokes }}
|
|
253
254
|
*/
|
|
254
|
-
export function summitLayout(top = [], width = 800, isMobile = false) {
|
|
255
|
-
const gap = isMobile ? 36 : 26;
|
|
255
|
+
export function summitLayout(top = [], width = 800, isMobile = false, { cards = false } = {}) {
|
|
256
|
+
const gap = cards ? 50 : isMobile ? 36 : 26;
|
|
257
|
+
const bridgeGap = cards ? 36 : MIN_BRIDGE_GAP;
|
|
256
258
|
const youX = 20;
|
|
257
259
|
const bx = Math.round(width * 0.42);
|
|
258
260
|
const px = Math.round(width * 0.58);
|
|
@@ -262,7 +264,7 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
|
|
|
262
264
|
p,
|
|
263
265
|
x: px,
|
|
264
266
|
y: TOP_PAD + i * gap,
|
|
265
|
-
r: dotRadius(p.waysIn),
|
|
267
|
+
r: cards ? 0 : dotRadius(p.waysIn),
|
|
266
268
|
label: fit(`#${p.rank}${p.tied ? '=' : ''} ${p.person.name || ''}`, Math.floor((width - px - 60) / 6.2)),
|
|
267
269
|
}));
|
|
268
270
|
|
|
@@ -288,11 +290,16 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
|
|
|
288
290
|
|
|
289
291
|
const y0 = TOP_PAD;
|
|
290
292
|
const y1 = people.length ? people[people.length - 1].y : TOP_PAD;
|
|
291
|
-
|
|
293
|
+
// Each connection sits level with the people they lead to (the middle of
|
|
294
|
+
// them), moved no more than it takes to keep `bridgeGap` from the next one,
|
|
295
|
+
// so a connection with one person sits on that person's row and the line
|
|
296
|
+
// between them runs straight (Blake, 2026-10-02: "the lines should line up
|
|
297
|
+
// with every box").
|
|
298
|
+
const ys = stackColumn(bridges.map((b) => b.bary), bridgeGap, y0);
|
|
292
299
|
const nameBudget = Math.max(4, Math.floor((bx - 10 - (youX + 22)) / 6));
|
|
293
300
|
bridges.forEach((b, j) => {
|
|
294
301
|
b.x = bx;
|
|
295
|
-
b.y =
|
|
302
|
+
b.y = ys[j];
|
|
296
303
|
b.r = 5;
|
|
297
304
|
if (b.unresolved) b.label = fit(`can't be named (${b.count})`, nameBudget);
|
|
298
305
|
else {
|
|
@@ -302,6 +309,15 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
|
|
|
302
309
|
delete b.ys;
|
|
303
310
|
});
|
|
304
311
|
const nodeFor = new Map(bridges.map((b) => [b.unresolved ? UNRESOLVED : b.id, b]));
|
|
312
|
+
// What each connection is to the people on the map: the only door to some,
|
|
313
|
+
// the best way in to others, or only another way in.
|
|
314
|
+
for (const b of bridges) { b.onlyFor = 0; b.bestFor = 0; }
|
|
315
|
+
for (const pp of people) {
|
|
316
|
+
const first = pp.p.routes[0];
|
|
317
|
+
const bestNode = first ? nodeFor.get(first.bridge ? first.id : UNRESOLVED) : null;
|
|
318
|
+
if (bestNode) bestNode.bestFor++;
|
|
319
|
+
if (pp.p.waysIn === 1 && bestNode) bestNode.onlyFor++;
|
|
320
|
+
}
|
|
305
321
|
|
|
306
322
|
// Routes, per person: routes[0] is the top-scored bridge. Two unnamed routes
|
|
307
323
|
// for one person are one line to the one grey node.
|
|
@@ -326,3 +342,114 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
|
|
|
326
342
|
const spokes = bridges.map((b) => ({ id: b.id, x1: you.x, y1: you.y, x2: b.x, y2: b.y }));
|
|
327
343
|
return { width, height, you, people, bridges, links, spokes };
|
|
328
344
|
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Heights as near the ones asked for as they can be while staying `gap`
|
|
348
|
+
* apart, in the order given (already sorted), never above `top`: neighbours
|
|
349
|
+
* that would overlap move apart by the same amount each way.
|
|
350
|
+
*/
|
|
351
|
+
export function stackColumn(wanted = [], gap = MIN_BRIDGE_GAP, top = 0) {
|
|
352
|
+
const blocks = [];
|
|
353
|
+
wanted.forEach((y, k) => {
|
|
354
|
+
blocks.push({ sum: y - k * gap, count: 1 });
|
|
355
|
+
while (blocks.length > 1 && blocks[blocks.length - 2].sum / blocks[blocks.length - 2].count > blocks[blocks.length - 1].sum / blocks[blocks.length - 1].count) {
|
|
356
|
+
const last = blocks.pop();
|
|
357
|
+
blocks[blocks.length - 1].sum += last.sum;
|
|
358
|
+
blocks[blocks.length - 1].count += last.count;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
const out = blocks.flatMap((b) => new Array(b.count).fill(b.sum / b.count)).map((q, k) => q + k * gap);
|
|
362
|
+
const lift = out.length && out[0] < top ? top - out[0] : 0;
|
|
363
|
+
return out.map((y) => y + lift);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Who the map aims at with the slider all the way to easy: whoever you share the
|
|
368
|
+
* most mutual connections with, scanned or not (Blake, 2026-10-02: "if we have
|
|
369
|
+
* mutuals but not scanned thats still a high probability … i dont want to hinder
|
|
370
|
+
* the algorithm since we havent scanned that person"). `mutualsOf` is LinkedIn's
|
|
371
|
+
* count where a scan saved one, never less than the ways in the app has drawn
|
|
372
|
+
* (lib/rarity.js). Then the most drawn, then the stronger person. Narrowing the
|
|
373
|
+
* tiers first (S only, say) keeps it to the people worth it.
|
|
374
|
+
*/
|
|
375
|
+
export function mostMutuals(people = [], mutualsOf = () => 0, limit = 7) {
|
|
376
|
+
return [...people].sort((a, b) => (Math.max(mutualsOf(b) || 0, b.waysIn) - Math.max(mutualsOf(a) || 0, a.waysIn))
|
|
377
|
+
|| b.waysIn - a.waysIn
|
|
378
|
+
|| comparePower(a, b)).slice(0, limit);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* How many people the map draws with the slider at `at` (lib/rarity.js): all
|
|
383
|
+
* `k` from the rare end to the middle, then fewer and fewer, down to the one
|
|
384
|
+
* person you're aiming at with the slider all the way to easy. Blake,
|
|
385
|
+
* 2026-10-02: at the rare end "it will show that one door"; at the other "all
|
|
386
|
+
* the connections going into that one person".
|
|
387
|
+
*/
|
|
388
|
+
export function mapCount(at, k = 10) {
|
|
389
|
+
const v = Math.max(0, Math.min(100, Number(at) || 0));
|
|
390
|
+
if (v <= 50) return k;
|
|
391
|
+
return Math.max(1, Math.round((k * (100 - v)) / 50));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The map for one person: You → every connection of yours who leads to them →
|
|
396
|
+
* them, with rings round them. Routes the map can't name share one grey node,
|
|
397
|
+
* as in summitLayout, and it comes back in summitLayout's shape (`people` holds
|
|
398
|
+
* the one person, marked `big`), so one map draws both and can glide from one
|
|
399
|
+
* to the other. Pure, so it is tested without a browser.
|
|
400
|
+
*
|
|
401
|
+
* @returns {{ width, height, you, target, people, bridges, links, spokes }}
|
|
402
|
+
*/
|
|
403
|
+
export function convergeLayout(p, width = 800, isMobile = false, { cards = false, mutuals = 0 } = {}) {
|
|
404
|
+
const gap = cards ? 46 : isMobile ? 40 : 34;
|
|
405
|
+
const youX = 20;
|
|
406
|
+
const bx = Math.round(width * (isMobile ? 0.44 : 0.36));
|
|
407
|
+
const tx = Math.round(width * (isMobile ? 0.8 : cards ? 0.6 : 0.62));
|
|
408
|
+
const nodes = [];
|
|
409
|
+
let unnamed = null;
|
|
410
|
+
for (const r of p?.routes || []) {
|
|
411
|
+
if (r.bridge) nodes.push({ id: r.id, bridge: r.bridge, unresolved: false, count: 1 });
|
|
412
|
+
else if (unnamed) unnamed.count++;
|
|
413
|
+
else nodes.push((unnamed = { id: null, bridge: null, unresolved: true, count: 1 }));
|
|
414
|
+
}
|
|
415
|
+
// Mutual connections the app can't draw yet: they're in circles you haven't
|
|
416
|
+
// scanned. Grey dots under the connections who do lead there, each with a faint
|
|
417
|
+
// line in, so the count reads at a glance (GHOST_MAX at most, then "+N").
|
|
418
|
+
const unscanned = Math.max(0, Math.floor(Number(mutuals) || 0) - (p?.waysIn || 0));
|
|
419
|
+
const ghostCount = Math.min(GHOST_MAX, unscanned);
|
|
420
|
+
const ghostStep = cards ? 12 : 11;
|
|
421
|
+
const ghostW = cards ? PILL_W_LAYOUT - 16 : isMobile ? 96 : 120;
|
|
422
|
+
const perRow = Math.max(1, Math.floor(ghostW / ghostStep) + 1);
|
|
423
|
+
const ghostRows = Math.ceil(ghostCount / perRow);
|
|
424
|
+
const ghostH = ghostCount ? GHOST_LABEL + ghostRows * ghostStep : 0;
|
|
425
|
+
const block = Math.max(0, nodes.length - 1) * gap + ghostH;
|
|
426
|
+
const height = Math.max(isMobile ? 190 : cards ? 250 : 220, TOP_PAD * 2 + block + 48);
|
|
427
|
+
const mid = height / 2;
|
|
428
|
+
const top = mid - block / 2;
|
|
429
|
+
const nameBudget = Math.max(4, Math.floor((bx - 10 - (youX + 22)) / 6));
|
|
430
|
+
const bridges = nodes.map((n, j) => {
|
|
431
|
+
const full = n.unresolved ? `can't be named (${n.count})` : n.bridge.name || '';
|
|
432
|
+
return {
|
|
433
|
+
...n, x: bx, y: top + j * gap, r: 6,
|
|
434
|
+
primary: j === 0 && !n.unresolved,
|
|
435
|
+
label: full.length <= nameBudget ? full : fit(n.unresolved ? full : shortName(full), nameBudget),
|
|
436
|
+
};
|
|
437
|
+
});
|
|
438
|
+
const target = { x: tx, y: mid, r: cards ? 0 : 17, rings: cards ? [] : [32, 50, 70].filter((r) => r <= mid - 4) };
|
|
439
|
+
const links = bridges.map((b) => ({
|
|
440
|
+
key: p?.key, bridgeId: b.id, primary: b.primary, unresolved: b.unresolved, tier: b.bridge?.tier ?? null,
|
|
441
|
+
x1: b.x, y1: b.y, x2: target.x, y2: target.y,
|
|
442
|
+
}));
|
|
443
|
+
const you = { x: youX, y: mid, r: 14 };
|
|
444
|
+
const spokes = bridges.map((b) => ({ id: b.id, x1: you.x, y1: you.y, x2: b.x, y2: b.y }));
|
|
445
|
+
const people = p ? [{ key: p.key, p, x: target.x, y: target.y, r: target.r, rings: target.rings, big: true, label: '' }] : [];
|
|
446
|
+
const gy = top + Math.max(0, nodes.length - 1) * gap + GHOST_LABEL;
|
|
447
|
+
const gx = cards ? bx - PILL_W_LAYOUT + 8 : bx - ghostW;
|
|
448
|
+
const ghosts = Array.from({ length: ghostCount }, (_, i) => ({ x: gx + (i % perRow) * ghostStep, y: gy + Math.floor(i / perRow) * ghostStep }));
|
|
449
|
+
// How bright the person is: the share of your mutual connections with them that the map can draw.
|
|
450
|
+
const drawn = p?.waysIn ? p.waysIn / (p.waysIn + unscanned) : 1;
|
|
451
|
+
return {
|
|
452
|
+
width, height, you, target, people, bridges, links, spokes,
|
|
453
|
+
ghosts, unscanned, ghostLabel: ghostCount ? { x: gx, y: gy - 13 } : null, drawn,
|
|
454
|
+
};
|
|
455
|
+
}
|
package/lib/settings-effects.js
CHANGED
|
@@ -5,28 +5,37 @@
|
|
|
5
5
|
|
|
6
6
|
import { rescoreAll } from './rpc.js';
|
|
7
7
|
import { sameFocus, NO_FOCUS, tierMoves } from './sector-focus.js';
|
|
8
|
+
import { rankingFingerprint } from './title-ranking.js';
|
|
8
9
|
|
|
9
10
|
// Each entry: `changed(before, after)` says whether a change matters (by
|
|
10
11
|
// default, any change to the stored value), and `run(db, after, before)` does
|
|
11
12
|
// the work and returns what to tell the page.
|
|
13
|
+
// Rescore everyone and count who changed tier, from the stored tiers before and after.
|
|
14
|
+
function rescoreCountingMoves(db) {
|
|
15
|
+
const rows = () => db.prepare('SELECT id, degree, profile_url, tier FROM linkedin_connections').all();
|
|
16
|
+
try {
|
|
17
|
+
const before = new Map(rows().map((r) => [r.id, r.tier]));
|
|
18
|
+
const { scored } = rescoreAll();
|
|
19
|
+
const { people, up, down } = tierMoves(rows(), (r) => before.get(r.id), (r) => r.tier, { examples: 0 });
|
|
20
|
+
return { scored, people, moved: up + down, up, down };
|
|
21
|
+
} catch (err) {
|
|
22
|
+
throw new Error(`Saved, but rescoring your network failed (${err.message}). It will try again the next time the map loads.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
const EFFECTS = {
|
|
27
|
+
// Title points are part of everyone's score, so a new ranking rescores everyone, as a new tier scale does.
|
|
28
|
+
titleRanking: {
|
|
29
|
+
changed: (before, after) => rankingFingerprint(before) !== rankingFingerprint(after),
|
|
30
|
+
run: (db) => rescoreCountingMoves(db),
|
|
31
|
+
},
|
|
13
32
|
tierScale: {
|
|
14
33
|
// Tiers are graded when everyone is scored, so a new scale rescores
|
|
15
34
|
// everyone. The answer counts who changed tier, from the stored tiers
|
|
16
35
|
// before and after, with the sector focus's own counter (tierMoves): a
|
|
17
36
|
// person reachable through two bridges is two rows but one person, counted
|
|
18
37
|
// once at their closest degree.
|
|
19
|
-
run(db)
|
|
20
|
-
const rows = () => db.prepare('SELECT id, degree, profile_url, tier FROM linkedin_connections').all();
|
|
21
|
-
try {
|
|
22
|
-
const before = new Map(rows().map((r) => [r.id, r.tier]));
|
|
23
|
-
const { scored } = rescoreAll();
|
|
24
|
-
const { people, up, down } = tierMoves(rows(), (r) => before.get(r.id), (r) => r.tier, { examples: 0 });
|
|
25
|
-
return { scored, people, moved: up + down, up, down };
|
|
26
|
-
} catch (err) {
|
|
27
|
-
throw new Error(`Saved, but rescoring your network failed (${err.message}). It will try again the next time the map loads.`);
|
|
28
|
-
}
|
|
29
|
-
},
|
|
38
|
+
run: (db) => rescoreCountingMoves(db),
|
|
30
39
|
},
|
|
31
40
|
sectorFocus: {
|
|
32
41
|
// Only a change to what scoring uses: a strength with no sectors picked
|
package/lib/settings.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { SECTOR_FOCUS_SETTING, FIELD_ASKED_SETTING } from './sector-focus.js';
|
|
14
14
|
import { TIER_SCALE_SETTING } from './scoring.js';
|
|
15
|
+
import { TITLE_RANKING_SETTING } from './title-ranking.js';
|
|
15
16
|
|
|
16
17
|
export const SETTINGS = {
|
|
17
18
|
// Scores → Your sector: industries whose companies count for more (lib/sector-focus.js).
|
|
@@ -20,6 +21,8 @@ export const SETTINGS = {
|
|
|
20
21
|
fieldAsked: FIELD_ASKED_SETTING,
|
|
21
22
|
// Scores → Tiers: graded on your network's curve, or on the fixed scale (lib/scoring.js).
|
|
22
23
|
tierScale: TIER_SCALE_SETTING,
|
|
24
|
+
// Scores → Titles: how much each kind of title counts, and roles you're looking for (lib/title-ranking.js).
|
|
25
|
+
titleRanking: TITLE_RANKING_SETTING,
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
const KEY = 'settings';
|
package/lib/tier-grid.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// The Filters panel's grid: five tiers down, six degrees across, the same in
|
|
2
|
+
// Network Circle and in Degrees.
|
|
3
|
+
//
|
|
4
|
+
// Blake, 2026-10-02: "keeping the tiers as is but enlarging them and having 6
|
|
5
|
+
// dots to let the user tap on to toggle or not if they want specific degrees
|
|
6
|
+
// showing for whatever tier … it will be universal for both".
|
|
7
|
+
//
|
|
8
|
+
// A grid is { hidden: [tiers switched off], degrees: { S: [1, 2], … } }: a tier
|
|
9
|
+
// row switches on and off as it always did, and remembers which degrees it
|
|
10
|
+
// shows while it's off. Counts are { S: { 1: n, 2: n, … }, … }: how many people
|
|
11
|
+
// there are at each tier and degree, so a dot with nobody behind it can't be
|
|
12
|
+
// tapped, and the last people on screen can't be switched off.
|
|
13
|
+
|
|
14
|
+
export const TIERS = ['S', 'A', 'B', 'C', 'D'];
|
|
15
|
+
export const GRID_DEGREES = [1, 2, 3, 4, 5, 6];
|
|
16
|
+
|
|
17
|
+
const tierOf = (tier) => (TIERS.includes(tier) ? tier : 'D'); // no tier: with D, as the views draw it
|
|
18
|
+
|
|
19
|
+
export const makeGrid = (degrees = GRID_DEGREES) => ({
|
|
20
|
+
hidden: [],
|
|
21
|
+
degrees: Object.fromEntries(TIERS.map((t) => [t, degrees.slice()])),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** Network Circle starts with your own connections; you pick the rest. */
|
|
25
|
+
export const NETWORK_GRID = Object.freeze(makeGrid([1]));
|
|
26
|
+
/** Degrees starts with everything it has. */
|
|
27
|
+
export const DEGREES_GRID = Object.freeze(makeGrid());
|
|
28
|
+
|
|
29
|
+
/** Is someone of this tier, at this degree, on screen? */
|
|
30
|
+
export function shows(grid, tier, degree) {
|
|
31
|
+
const t = tierOf(tier);
|
|
32
|
+
return !grid.hidden.includes(t) && grid.degrees[t].includes(degree);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const countAt = (counts, tier, degree) => counts?.[tier]?.[degree] || 0;
|
|
36
|
+
|
|
37
|
+
/** How many people one tier is showing. */
|
|
38
|
+
export function showingIn(grid, counts, tier) {
|
|
39
|
+
if (grid.hidden.includes(tier)) return 0;
|
|
40
|
+
return grid.degrees[tier].reduce((n, d) => n + countAt(counts, tier, d), 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** How many people the whole grid is showing. */
|
|
44
|
+
export const showing = (grid, counts) => TIERS.reduce((n, t) => n + showingIn(grid, counts, t), 0);
|
|
45
|
+
|
|
46
|
+
// A change that would leave nobody on screen isn't made.
|
|
47
|
+
const keep = (grid, next, counts) => (counts && showing(next, counts) === 0 ? grid : next);
|
|
48
|
+
|
|
49
|
+
/** One dot: that degree, for that tier. Tapping a dot in a tier that's off switches the tier on with it. */
|
|
50
|
+
export function toggleCell(grid, tier, degree, counts) {
|
|
51
|
+
const has = grid.degrees[tier].includes(degree);
|
|
52
|
+
const off = grid.hidden.includes(tier);
|
|
53
|
+
const degrees = off || !has
|
|
54
|
+
? [...new Set([...grid.degrees[tier], degree])].sort((a, b) => a - b)
|
|
55
|
+
: grid.degrees[tier].filter((d) => d !== degree);
|
|
56
|
+
return keep(grid, {
|
|
57
|
+
hidden: off ? grid.hidden.filter((t) => t !== tier) : grid.hidden,
|
|
58
|
+
degrees: { ...grid.degrees, [tier]: degrees },
|
|
59
|
+
}, counts);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A whole tier, off or back on with the degrees it had. */
|
|
63
|
+
export function toggleTier(grid, tier, counts) {
|
|
64
|
+
if (!grid.hidden.includes(tier)) return keep(grid, { ...grid, hidden: [...grid.hidden, tier] }, counts);
|
|
65
|
+
// Back on with nothing to show would look broken: give it what the others show.
|
|
66
|
+
const own = grid.degrees[tier];
|
|
67
|
+
const others = [...new Set(TIERS.flatMap((t) => (t === tier ? [] : grid.degrees[t])))].sort((a, b) => a - b);
|
|
68
|
+
return {
|
|
69
|
+
hidden: grid.hidden.filter((t) => t !== tier),
|
|
70
|
+
degrees: own.length ? grid.degrees : { ...grid.degrees, [tier]: others.length ? others : [1] },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A whole degree: off if every tier with people there is showing it, otherwise on for all of them. */
|
|
75
|
+
export function toggleDegree(grid, degree, counts) {
|
|
76
|
+
const tiers = TIERS.filter((t) => !grid.hidden.includes(t) && (!counts || countAt(counts, t, degree) > 0));
|
|
77
|
+
if (!tiers.length) return grid;
|
|
78
|
+
const allOn = tiers.every((t) => grid.degrees[t].includes(degree));
|
|
79
|
+
const degrees = { ...grid.degrees };
|
|
80
|
+
for (const t of tiers) {
|
|
81
|
+
degrees[t] = allOn ? degrees[t].filter((d) => d !== degree) : [...new Set([...degrees[t], degree])].sort((a, b) => a - b);
|
|
82
|
+
}
|
|
83
|
+
return keep(grid, { ...grid, degrees }, counts);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Every tier back on, each with the degrees it had. */
|
|
87
|
+
export const showAllTiers = (grid) => (grid.hidden.length ? { ...grid, hidden: [] } : grid);
|
|
88
|
+
|
|
89
|
+
/** People by tier and degree, for the dots: `byDegree` is { 1: rows, 2: rows, … }. */
|
|
90
|
+
export function gridCounts(byDegree = {}) {
|
|
91
|
+
const counts = Object.fromEntries(TIERS.map((t) => [t, {}]));
|
|
92
|
+
for (const [degree, rows] of Object.entries(byDegree)) {
|
|
93
|
+
for (const row of rows || []) {
|
|
94
|
+
const t = tierOf(row?.tier);
|
|
95
|
+
counts[t][degree] = (counts[t][degree] || 0) + 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return counts;
|
|
99
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Settings → Scores → Titles: how much each kind of title counts, your way
|
|
2
|
+
// (Blake, 2026-10-02: "let the user change the rankings of titles as well …
|
|
3
|
+
// incase they value certain people or is using this for outreaching new
|
|
4
|
+
// founders", and people who "find more in that role" for outbound or hiring).
|
|
5
|
+
//
|
|
6
|
+
// Two parts, both in title points (the 0–10 a title is worth before the
|
|
7
|
+
// company weighs it, lib/scoring.js LEVELS):
|
|
8
|
+
// points a level's points, yours: Owner / Entrepreneur at 10, say, to put
|
|
9
|
+
// founders of small companies level with CEOs
|
|
10
|
+
// roles words in a title, and what anyone whose title has them counts:
|
|
11
|
+
// "account executive" at 10 puts every AE you can reach in S
|
|
12
|
+
// Nothing set scores exactly as before. The model applies it (scorePerson);
|
|
13
|
+
// this file is the setting and its presets.
|
|
14
|
+
|
|
15
|
+
import { LEVELS } from './scoring.js';
|
|
16
|
+
|
|
17
|
+
/** The ladder shown in Settings, top to bottom (the main kinds of title). */
|
|
18
|
+
export const RANKED_LEVELS = ['csuite', 'owner', 'vp', 'director', 'manager', 'senior', 'ic', 'unknown', 'intern', 'student'];
|
|
19
|
+
export const NO_RANKING = Object.freeze({ points: Object.freeze({}), roles: Object.freeze([]) });
|
|
20
|
+
export const MAX_ROLES = 20;
|
|
21
|
+
const MAX_ROLE_TEXT = 60;
|
|
22
|
+
|
|
23
|
+
const clampPoints = (v) => {
|
|
24
|
+
const n = Number(v);
|
|
25
|
+
if (!Number.isFinite(n) || n < 0 || n > 10) throw new Error('Title points go from 0 to 10.');
|
|
26
|
+
return Math.round(n * 2) / 2;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const TITLE_RANKING_SETTING = {
|
|
30
|
+
default: NO_RANKING,
|
|
31
|
+
parse(value) {
|
|
32
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('A title ranking is { points, roles }.');
|
|
33
|
+
const points = {};
|
|
34
|
+
for (const [key, v] of Object.entries(value.points || {})) {
|
|
35
|
+
if (!Object.hasOwn(LEVELS, key)) throw new Error(`There is no kind of title called '${key}'.`);
|
|
36
|
+
const p = clampPoints(v);
|
|
37
|
+
if (p !== LEVELS[key].points) points[key] = p; // the default needn't be stored
|
|
38
|
+
}
|
|
39
|
+
const roles = [];
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
for (const r of Array.isArray(value.roles) ? value.roles : []) {
|
|
42
|
+
const text = String(r?.text ?? '').trim().toLowerCase().replace(/\s+/g, ' ');
|
|
43
|
+
if (text.length < 2) throw new Error('A role needs at least two letters.');
|
|
44
|
+
if (text.length > MAX_ROLE_TEXT) throw new Error(`Keep a role under ${MAX_ROLE_TEXT} letters.`);
|
|
45
|
+
if (seen.has(text)) continue;
|
|
46
|
+
seen.add(text);
|
|
47
|
+
roles.push({ text, points: clampPoints(r.points) });
|
|
48
|
+
}
|
|
49
|
+
if (roles.length > MAX_ROLES) throw new Error(`Up to ${MAX_ROLES} roles.`);
|
|
50
|
+
return { points, roles };
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** The same ranking always gives the same text, so the stored scores can say which they were made with. */
|
|
55
|
+
export function rankingFingerprint(r = NO_RANKING) {
|
|
56
|
+
const points = Object.keys(r.points || {}).sort().map((k) => `${k}=${r.points[k]}`).join(',');
|
|
57
|
+
const roles = (r.roles || []).map((x) => `${x.text}=${x.points}`).sort().join(',');
|
|
58
|
+
return points || roles ? `${points}|${roles}` : 'none';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const isDefaultRanking = (r) => rankingFingerprint(r) === 'none';
|
|
62
|
+
|
|
63
|
+
/** Starting points for the common reasons to change it. Each replaces the whole ranking; you can adjust from there. */
|
|
64
|
+
export const RANKING_PRESETS = [
|
|
65
|
+
{ key: 'default', label: 'As the app ranks them', ranking: NO_RANKING, note: 'C-suite and founders first, then VPs, directors, managers.' },
|
|
66
|
+
{
|
|
67
|
+
key: 'founders', label: 'Founders first',
|
|
68
|
+
note: 'Owners and entrepreneurs count like CEOs, and anyone who says founder counts as much: for meeting founders, or selling to them.',
|
|
69
|
+
ranking: { points: { owner: 10 }, roles: [{ text: 'founder', points: 10 }, { text: 'co-founder', points: 10 }] },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: 'investors', label: 'Investors',
|
|
73
|
+
note: 'Anyone who invests for a living rises to the top: for raising money.',
|
|
74
|
+
ranking: { points: {}, roles: ['investor', 'venture', 'angel', 'general partner', 'managing partner', 'principal at', 'vc'].map((text) => ({ text, points: 10 })) },
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: 'role', label: 'A role I’m looking for',
|
|
78
|
+
note: 'For outbound or hiring: add the role below, and everyone in it rises, whatever their level.',
|
|
79
|
+
ranking: null, // keeps what you have and opens the role box
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
export { titleHas } from './scoring.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "six-degrees",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Map your professional network as a galaxy — leverage tiers, bridges into new circles, and the shortest path to anyone. Runs locally on your own data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Blake Burford",
|
package/scripts/scrape.py
CHANGED
|
@@ -91,7 +91,7 @@ def _assert_local_target():
|
|
|
91
91
|
if host in ("localhost", "127.0.0.1", "::1", "0.0.0.0"):
|
|
92
92
|
return
|
|
93
93
|
if os.getenv("ALLOW_REMOTE_PUSH") == "1":
|
|
94
|
-
print(f"WARNING: pushing
|
|
94
|
+
print(f"WARNING: pushing scanned data to remote host {host}")
|
|
95
95
|
return
|
|
96
96
|
raise SystemExit(
|
|
97
97
|
f"Refusing to push scraped data to '{APP_URL}'.\n"
|
|
@@ -370,7 +370,7 @@ def _print_sign_in_banner(say):
|
|
|
370
370
|
print(" inside automated browsers, which is why that window comes up")
|
|
371
371
|
print(" greyed out and does nothing.")
|
|
372
372
|
print()
|
|
373
|
-
print(" Nothing is
|
|
373
|
+
print(" Nothing is scanned until you are signed in, and the scan starts")
|
|
374
374
|
print(" by itself the moment you are. Take as long as you need.")
|
|
375
375
|
print(" Close the browser window to cancel.")
|
|
376
376
|
print(" ==================================================================")
|
|
@@ -387,11 +387,11 @@ def _wait_for_sign_in(page, context, timeout_s, say):
|
|
|
387
387
|
# not: signing in legitimately opens and closes tabs.
|
|
388
388
|
try:
|
|
389
389
|
if not [pg for pg in context.pages if not pg.is_closed()]:
|
|
390
|
-
print("\n Browser closed — nothing was
|
|
390
|
+
print("\n Browser closed — nothing was scanned.")
|
|
391
391
|
say("Browser closed before sign-in.")
|
|
392
392
|
return False
|
|
393
393
|
except Exception:
|
|
394
|
-
print("\n Browser closed — nothing was
|
|
394
|
+
print("\n Browser closed — nothing was scanned.")
|
|
395
395
|
return False
|
|
396
396
|
|
|
397
397
|
# The cookie belongs to the whole browser, so it is found no matter which
|
|
@@ -493,7 +493,7 @@ def resolve_active_user():
|
|
|
493
493
|
for u in users:
|
|
494
494
|
if (u.get("name") or "").strip().lower() == wanted:
|
|
495
495
|
_active_user_id = u["id"]
|
|
496
|
-
print(f"
|
|
496
|
+
print(f" Scanning into profile: {u['name']}")
|
|
497
497
|
return _active_user_id
|
|
498
498
|
raise SystemExit(f"No profile named '{os.getenv('SIX_DEGREES_USER')}' in the app.")
|
|
499
499
|
|
|
@@ -510,7 +510,7 @@ def resolve_active_user():
|
|
|
510
510
|
raise SystemExit(f"The app at {APP_URL} did not say which profile to use.")
|
|
511
511
|
|
|
512
512
|
_active_user_id = me["id"]
|
|
513
|
-
print(f"
|
|
513
|
+
print(f" Scanning into profile: {me.get('name')}")
|
|
514
514
|
return _active_user_id
|
|
515
515
|
|
|
516
516
|
|
|
@@ -732,7 +732,7 @@ def push_connections(connections, degree=1, bridge_id=None, user_id=None, on_sav
|
|
|
732
732
|
if p["profile_url"] in d1_urls:
|
|
733
733
|
# Unlock via API route
|
|
734
734
|
requests.post(f"{APP_URL}/api/unlock", json={"connectionId": p["id"]}, headers=app_headers())
|
|
735
|
-
print(f" *** PATH UNLOCKED: {p['name']}! Run --bridge \"{p['name']}\" to
|
|
735
|
+
print(f" *** PATH UNLOCKED: {p['name']}! Run --bridge \"{p['name']}\" to scan their network ***")
|
|
736
736
|
|
|
737
737
|
return inserted
|
|
738
738
|
|
|
@@ -893,8 +893,8 @@ def scrape_full(headless=False):
|
|
|
893
893
|
if searches_left()[0] <= 0:
|
|
894
894
|
raise BudgetReached(0, searches_left()[1])
|
|
895
895
|
|
|
896
|
-
print("\n=== Full
|
|
897
|
-
print("This
|
|
896
|
+
print("\n=== Full account scan ===\n")
|
|
897
|
+
print("This scans ALL connections via the search page (not the connections page).")
|
|
898
898
|
print("Takes 3-5 minutes for ~500 connections.\n")
|
|
899
899
|
|
|
900
900
|
with sync_playwright() as p:
|
|
@@ -1286,6 +1286,18 @@ SAVE_EVERY_PAGES = 10 # save as a long read goes, so a stop loses li
|
|
|
1286
1286
|
# randomised: the point is fewer searches an hour, not looking like a person.
|
|
1287
1287
|
PAGE_PAUSE = 20 # seconds before each next page of results
|
|
1288
1288
|
CHUNK_COOLDOWN = 60 # extra seconds after every SAVE_EVERY_PAGES pages
|
|
1289
|
+
# The Scan page's speed (Blake, 2026-09-30 and 10-03: "slow, medium and fast").
|
|
1290
|
+
# Fast is exactly the pacing above and nothing is ever faster; Medium and Slow
|
|
1291
|
+
# only add waiting. Speed changes how many searches an hour, never how many a
|
|
1292
|
+
# day: the daily budget stays the volume cap. Read from scan-limits.json
|
|
1293
|
+
# ("pace") at the start of every run (apply_pace). lib/scan-pace.js holds the
|
|
1294
|
+
# same numbers for the page, and tests/scan-pace.test.mjs checks they agree.
|
|
1295
|
+
SCAN_PACES = {
|
|
1296
|
+
"fast": {"page_pause": 20, "chunk_cooldown": 60, "profile_gap": 60},
|
|
1297
|
+
"medium": {"page_pause": 45, "chunk_cooldown": 180, "profile_gap": 90},
|
|
1298
|
+
"slow": {"page_pause": 90, "chunk_cooldown": 300, "profile_gap": 120},
|
|
1299
|
+
}
|
|
1300
|
+
DEFAULT_PACE = "fast"
|
|
1289
1301
|
LEGACY_PAGES_READ = 10 # how far every read before 0.1.6 went, at most
|
|
1290
1302
|
|
|
1291
1303
|
# Experimental all-day pacing for Auto-Bridge (--experimental; item 44, Graph
|
|
@@ -1561,6 +1573,27 @@ def _profile_wait(data, now):
|
|
|
1561
1573
|
return 0
|
|
1562
1574
|
|
|
1563
1575
|
|
|
1576
|
+
def scan_pace():
|
|
1577
|
+
"""The speed saved on the Scan page: "fast", "medium" or "slow"."""
|
|
1578
|
+
try:
|
|
1579
|
+
name = json.loads((_home() / "scan-limits.json").read_text()).get("pace")
|
|
1580
|
+
except Exception:
|
|
1581
|
+
name = None
|
|
1582
|
+
return name if name in SCAN_PACES else DEFAULT_PACE
|
|
1583
|
+
|
|
1584
|
+
|
|
1585
|
+
def apply_pace(name=None):
|
|
1586
|
+
"""Set this run's waits from the chosen speed. Never below Fast's."""
|
|
1587
|
+
global PAGE_PAUSE, CHUNK_COOLDOWN, PROFILE_GAP
|
|
1588
|
+
name = name if name in SCAN_PACES else scan_pace()
|
|
1589
|
+
pace = SCAN_PACES[name]
|
|
1590
|
+
fast = SCAN_PACES["fast"]
|
|
1591
|
+
PAGE_PAUSE = max(fast["page_pause"], pace["page_pause"])
|
|
1592
|
+
CHUNK_COOLDOWN = max(fast["chunk_cooldown"], pace["chunk_cooldown"])
|
|
1593
|
+
PROFILE_GAP = max(fast["profile_gap"], pace["profile_gap"])
|
|
1594
|
+
return name
|
|
1595
|
+
|
|
1596
|
+
|
|
1564
1597
|
def search_limits():
|
|
1565
1598
|
try:
|
|
1566
1599
|
data = json.loads((_home() / "scan-limits.json").read_text())
|
|
@@ -3256,7 +3289,7 @@ def scrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES, dee
|
|
|
3256
3289
|
if not urn and profiles_left() <= 0:
|
|
3257
3290
|
raise BudgetReached(0, "profiles")
|
|
3258
3291
|
|
|
3259
|
-
print(f"\n=== {'Carrying on with' if start_page > 1 else '
|
|
3292
|
+
print(f"\n=== {'Carrying on with' if start_page > 1 else 'Scanning'} connections of {bridge_name} ===\n")
|
|
3260
3293
|
|
|
3261
3294
|
read = []
|
|
3262
3295
|
|
|
@@ -3368,7 +3401,7 @@ def scrape_company(company_name, headless=False, log_fn=None):
|
|
|
3368
3401
|
if log_fn:
|
|
3369
3402
|
log_fn(msg)
|
|
3370
3403
|
|
|
3371
|
-
log(f"
|
|
3404
|
+
log(f"Scanning employees at: {company_name}")
|
|
3372
3405
|
|
|
3373
3406
|
# A company scan is people searches too: the same cooldown and budget.
|
|
3374
3407
|
cd = read_cooldown()
|
|
@@ -4045,7 +4078,7 @@ def auto_bridge_all(headless=False, log_fn=None, retry_private=False, max_bridge
|
|
|
4045
4078
|
|
|
4046
4079
|
def rescrape_bridge(bridge_name, headless=False, max_pages=LINKEDIN_MAX_PAGES):
|
|
4047
4080
|
"""Delete all existing cluster data for a bridge and re-scrape from scratch."""
|
|
4048
|
-
print(f"\n===
|
|
4081
|
+
print(f"\n=== Rescanning {bridge_name} (delete + fresh scan) ===\n")
|
|
4049
4082
|
|
|
4050
4083
|
# Check before deleting anything: a cooldown or an empty budget would leave
|
|
4051
4084
|
# their circle deleted and not read again.
|
|
@@ -4349,11 +4382,16 @@ Examples:
|
|
|
4349
4382
|
EXPERIMENT["on"] = True
|
|
4350
4383
|
print("Experimental Auto-Bridge: all-day pacing, and LinkedIn's own data read beside the page text.")
|
|
4351
4384
|
args.max_pages = max(1, min(LINKEDIN_MAX_PAGES, args.max_pages))
|
|
4385
|
+
# The Scan page's speed. Signing in searches nothing, so it isn't said there.
|
|
4386
|
+
_pace = apply_pace()
|
|
4387
|
+
if not args.login:
|
|
4388
|
+
print(f"Speed: {_pace.capitalize()}. {PAGE_PAUSE}s before each page of results, {CHUNK_COOLDOWN}s more after "
|
|
4389
|
+
f"every {SAVE_EVERY_PAGES}, and profiles at least {PROFILE_GAP}s apart.")
|
|
4352
4390
|
|
|
4353
4391
|
# A failed save ends the run with its reason, not a traceback (TRAPS §32).
|
|
4354
4392
|
def _say_why(exc_type, exc, tb):
|
|
4355
4393
|
if issubclass(exc_type, SaveFailed):
|
|
4356
|
-
print(f"\n The app could not save what was
|
|
4394
|
+
print(f"\n The app could not save what was scanned: {exc}\n", file=sys.stderr)
|
|
4357
4395
|
elif issubclass(exc_type, TryLater):
|
|
4358
4396
|
print(f"\n The photos weren't saved: {exc}.\n", file=sys.stderr)
|
|
4359
4397
|
elif issubclass(exc_type, NotSignedIn):
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
module.exports=[70406,(e,t,r)=>{t.exports=e.x("next/dist/compiled/@opentelemetry/api",()=>require("next/dist/compiled/@opentelemetry/api"))},18622,(e,t,r)=>{t.exports=e.x("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js",()=>require("next/dist/compiled/next-server/app-page-turbo.runtime.prod.js"))},56704,(e,t,r)=>{t.exports=e.x("next/dist/server/app-render/work-async-storage.external.js",()=>require("next/dist/server/app-render/work-async-storage.external.js"))},32319,(e,t,r)=>{t.exports=e.x("next/dist/server/app-render/work-unit-async-storage.external.js",()=>require("next/dist/server/app-render/work-unit-async-storage.external.js"))},59043,(e,t,r)=>{t.exports=e.x("next/dist/server/runtime-reacts.external.js",()=>require("next/dist/server/runtime-reacts.external.js"))},93695,(e,t,r)=>{t.exports=e.x("next/dist/shared/lib/no-fallback-error.external.js",()=>require("next/dist/shared/lib/no-fallback-error.external.js"))},74533,(e,t,r)=>{t.exports=e.x("node:child_process",()=>require("node:child_process"))},66680,(e,t,r)=>{t.exports=e.x("node:crypto",()=>require("node:crypto"))},2157,(e,t,r)=>{t.exports=e.x("node:fs",()=>require("node:fs"))},12714,(e,t,r)=>{t.exports=e.x("node:fs/promises",()=>require("node:fs/promises"))},60526,(e,t,r)=>{t.exports=e.x("node:os",()=>require("node:os"))},50227,(e,t,r)=>{t.exports=e.x("node:path",()=>require("node:path"))},81111,(e,t,r)=>{t.exports=e.x("node:stream",()=>require("node:stream"))},10960,e=>{"use strict";var t=e.i(2157),r=e.i(50227),n=e.i(60526);function s(){for(let e of[process.env.SIX_DEGREES_ROOT,process.cwd(),r.default.join(process.cwd(),".."),r.default.join(process.cwd(),"..","..")].filter(Boolean))if((0,t.existsSync)(r.default.join(e,"scripts","scrape.py")))return e;return null}e.s(["dataDir",0,function(){return process.env.SIX_DEGREES_HOME||r.default.join(n.default.homedir(),".six-degrees")},"isGitCheckout",0,function(){let e=s();return!!(e&&(0,t.existsSync)(r.default.join(e,".git")))},"projectRoot",0,s])},88602,e=>{"use strict";var t=e.i(88431);e.s(["networkCounts",0,function(e){let r=Object.fromEntries((0,t.getDb)().prepare("SELECT degree, COUNT(*) AS n FROM linkedin_connections WHERE user_id = ? GROUP BY degree").all(e).map(e=>[e.degree,e.n]));return{first:r[1]||0,second:r[2]||0,third:r[3]||0}},"resolveProfile",0,function({create:e=!0}={}){let[r]=(0,t.getDb)().prepare(`
|
|
2
|
-
SELECT u.*,
|
|
3
|
-
(SELECT COUNT(*) FROM linkedin_connections c WHERE c.user_id = u.id) AS connections
|
|
4
|
-
FROM users u
|
|
5
|
-
ORDER BY connections DESC, u.created_at ASC, u.rowid ASC
|
|
6
|
-
`).all().map(e=>(0,t.decodeRow)("users",e));if(r)return r;if(!e)return null;let n=(0,t.newId)();return(0,t.getDb)().prepare("INSERT INTO users (id, name) VALUES (?, ?)").run(n,"You"),{...(0,t.decodeRow)("users",(0,t.getDb)().prepare("SELECT * FROM users WHERE id = ?").get(n)),connections:0}}])},12506,1348,e=>{"use strict";e.s(["compareVersions",0,function(e,t){let r=e=>String(e||"").replace(/^v/i,"").split(/[-+]/)[0].split(".").map(e=>Number.parseInt(e,10)||0),n=r(e),s=r(t);for(let e=0;e<Math.max(n.length,s.length);e++){let t=(n[e]||0)-(s[e]||0);if(0!==t)return t>0?1:-1}return 0},"installKind",0,function(e=process.env,t=""){return["mac-app","source","npm"].includes(e.SIX_DEGREES_INSTALL)?e.SIX_DEGREES_INSTALL:/\.app\/Contents\/Resources/.test(t||"")?"mac-app":"npm"},"repoSlug",0,function(e){let t=("string"==typeof e?e:e?.url||"").match(/github\.com[/:]([^/]+)\/([^/.#?]+)/);return t?`${t[1]}/${t[2]}`:null},"updateCommand",0,function(e,t,{dataDir:r}={}){if("mac-app"===e)return`curl -fsSL https://raw.githubusercontent.com/${t}/main/install.sh | bash`;if("source"===e)return"git pull && npm ci && npm run build && npm run start:packaged";let n=r&&`'${String(r).replace(/'/g,"'\\''")}'`;return n?`npx six-degrees@latest --data-dir ${n}`:"npx six-degrees@latest"}],12506);let t=Symbol.for("six-degrees.scan-state");function r(){let e=globalThis[t];return e?.running!==!0?null:"string"==typeof e.action&&e.action?e.action:"scan"}function n(e){switch(e){case"install":return"The scanner's packages are being installed";case"setup":return"The scanner is being set up";case"login":return"The LinkedIn sign-in window is open";case"photos":return"Profile photos are being saved";default:return"A scan is running"}}e.s(["busyRefusal",0,function(e,t=r()){return t?`${n(t)}. ${e}`:null},"registerScanState",0,function(e){return globalThis[t]||(globalThis[t]=e),globalThis[t]},"runningNow",0,n,"scannerJob",0,r],1348)},1746,e=>{"use strict";e.s(["keyFor",0,function(e){return String(e?.profile_url||"").split(/[?#]/)[0].replace(/\/+$/,"")||`id:${e?.id}`}])},98199,e=>{"use strict";var t=e.i(50227),r=e.i(12506);let n="Six Degrees.app",s="SHA256SUMS",i=Object.freeze({apiBase:"https://api.github.com",downloadBase:"https://github.com"}),o=(e,t)=>({refusal:{code:e,message:t}}),a={arm64:"Apple Silicon",x64:"Intel"},l={0x1000007:"x64",0x100000c:"arm64"};e.s(["APP_BUNDLE_NAME",0,n,"BUNDLE_ID",0,"com.blakeburford.sixdegrees","CHIP_LABEL",0,a,"GITHUB",0,i,"UPDATE_HANDOFF_EXIT_CODE",0,76,"chipOf",0,function({sysctlArm64:e,processArch:t}={}){return"1"===String(e??"").trim()||"arm64"===t?"arm64":"x64"},"digestsMatch",0,function(e,t){let r=String(e||"").toLowerCase(),n=String(t||"").toLowerCase();return/^[0-9a-f]{64}$/.test(r)&&r===n},"foreignAppProcesses",0,function(e,{bundles:t=[],uid:r}){let n=t.filter(Boolean).map(e=>`${e}/Contents/`);if(!n.length||!Number.isInteger(r))return[];let s=[];for(let t of String(e||"").split("\n")){let e=t.match(/^\s*(\d+)\s+(\d+)\s+(.+)$/);e&&Number(e[1])!==r&&n.some(t=>e[3].startsWith(t))&&s.push(Number(e[2]))}return s},"helperArgs",0,function({target:e,staged:t,keepDir:r,statusFile:n,from:s,to:i,pids:o=[],waitSeconds:a=30,logFile:l,work:u,relaunch:d=[],confirmFile:p,confirmWaitSeconds:c=600}){let f=["--target",e,"--staged",t,"--keep",r,"--status",n,"--from",s,"--to",i,"--wait",String(a)];for(let e of o)Number.isInteger(e)&&e>1&&f.push("--pid",String(e));return p&&f.push("--confirm",p,"--confirm-wait",String(c)),l&&f.push("--log",l),u&&f.push("--work",u),[...f,"--",...d]},"installerTarget",0,function({applicationsWritable:e=!0,home:r=""}={}){return e?t.default.join("/Applications",n):t.default.join(r,"Applications",n)},"lastUpdateReport",0,function(e,{runningVersion:t,now:s=Date.now(),maxAgeDays:i=7}={}){if(!e||"object"!=typeof e||"string"!=typeof e.to)return null;let o=Date.parse(e.at);if(!Number.isFinite(o)||s-o>864e5*i)return null;let a=e.to,l="string"==typeof e.reason&&e.reason.trim()?e.reason.trim().replace(/\.$/,""):null,u={at:new Date(o).toISOString(),from:e.from||null,to:a,previous:"string"==typeof e.previous?e.previous:null},d=t?(0,r.compareVersions)(t,a):null,p=`The update to ${a} didn't finish${l?`: ${l}`:""}.`;switch(e.outcome){case"installed":case"started":if(0===d)return{...u,tone:"ok",text:`Updated to ${a}.`};if(d>0)return null;if("installed"===e.outcome)return{...u,tone:"bad",text:`Version ${a} was installed, but this copy is ${t}. If you have two copies of Six Degrees, open the one in Applications.`};return{...u,tone:"bad",text:`The update to ${a} didn't finish, and it didn't say why.${e.log?` Its log is ${e.log}.`:""}`};case"not-applied":if(null!==d&&d>=0)return null;return{...u,tone:"bad",text:`${p} Nothing was changed.`};case"rolled-back":if(null!==d&&d>=0)return null;return{...u,tone:"bad",text:`${p} Your previous version was put back.`};case"failed":if(null!==d&&d>=0)return null;return{...u,tone:"bad",text:`${p}${e.previous?` Your previous version is safe, in a hidden folder: ${e.previous}. To use it again, rename it to ${n} (in Finder, Shift-Command-. shows hidden files). Or install the newest version: Check for updates, below.`:""}`};default:return null}},"leftoversBeside",0,function(e,t){let r=`.${t}.`,n=[];for(let t of e){if("string"!=typeof t||!t.startsWith(r))continue;let e=t.slice(r.length);if("incoming"===e){n.push({name:t,kind:"incoming",pid:null});continue}let s=e.match(/^(previous|failed)-(\d+)(?:-\d+)?$/);s&&n.push({name:t,kind:s[1],pid:Number(s[2])})}return n},"machoArchs",0,function(e){let t=new Set;if(!e||e.length<8)return t;let r=e=>{l[e>>>0]&&t.add(l[e>>>0])},n=e.readUInt32LE(0);if(0xfeedfacf===n||0xfeedface===n)return r(e.readUInt32LE(4)),t;let s=e.readUInt32BE(0);if(0xcafebabe===s||0xcafebabf===s){let n=e.readUInt32BE(4),i=0xcafebabf===s?32:20;if(n<1||n>16)return t;for(let t=0;t<n;t++){let n=8+t*i;if(n+4>e.length)break;r(e.readUInt32BE(n))}}return t},"parseSha256Sums",0,function(e){let t=new Map;for(let r of String(e||"").split("\n")){let e=r.replace(/\r$/,"").match(/^([0-9a-fA-F]{64}) [ *](.+)$/);if(!e)continue;let[,n,s]=e,i=n.toLowerCase();t.has(s)&&t.get(s)!==i?t.set(s,null):t.set(s,i)}return t},"planFromRelease",0,function(e,{currentVersion:t,chip:n,slug:l,downloadBase:u=i.downloadBase}){if(!e||"object"!=typeof e||!e.tag_name)return o("no-release","GitHub did not describe a release.");let d=String(e.tag_name),p=d.replace(/^v/i,"");if(e.draft||e.prerelease||p.includes("-"))return o("prerelease",`Version ${p} is a test version, so it isn't installed from here.`);if(!/^\d+(\.\d+){1,3}$/.test(p))return o("bad-version",`GitHub's newest release has a version this app doesn't understand (${d}).`);if(0>=(0,r.compareVersions)(p,t))return o("not-newer",`You already have ${t}, and the newest version is ${p}.`);let c=Array.isArray(e.assets)?e.assets:[],f=e=>c.find(t=>t&&t.name===e)||null,m=f(`Six-Degrees-${p}-${n}.dmg`);if(!m)return o("no-download",`Version ${p} has no download for this Mac (${a[n]||n}).`);let g=f(s);if(!g)return o("no-checksums",`Version ${p} was published without its checksum file (${s}), so its download can't be checked.`);let h=`${u}/${l}/releases/download/${encodeURIComponent(d)}/`;for(let e of[m,g])if(e.browser_download_url!==h+encodeURIComponent(e.name))return o("unexpected-address",`GitHub listed ${e.name} at an unexpected address, so it wasn't downloaded.`);let x=Number(m.size)>0?Number(m.size):null;return x&&x>0x40000000?o("too-large",`The download for version ${p} is unexpectedly large, so it wasn't downloaded.`):{plan:{version:p,tag:d,notes:"string"==typeof e.html_url?e.html_url:null,dmg:{name:m.name,url:m.browser_download_url,size:x},sums:{name:g.name,url:g.browser_download_url}}}},"relaunchArgs",0,function({dataDir:e}={}){let r=["--after-update"];return e&&r.push("--data-dir",t.default.resolve(e)),r},"releaseSource",0,function(e={}){let t=e?.SIX_DEGREES_TEST_RELEASES;if(!t)return{...i,test:!1};let r=null;try{r=new URL(String(t))}catch{}return!r||"http:"!==r.protocol||"127.0.0.1"!==r.hostname||""===r.port||"/"!==r.pathname||r.search||r.hash||r.username||r.password?{...i,test:!1}:{apiBase:r.origin,downloadBase:r.origin,test:!0}},"stagingPath",0,function(e){return t.default.join(t.default.dirname(e),`.${t.default.basename(e)}.incoming`)},"terminalFallback",0,function({bundle:e,refusalCode:t=null,installsTo:r,dataDir:n=null}){if("data-inside-app"===t||"database-elsewhere"===t||"not-owner"===t&&e===r)return null;let s=n?{dataDir:n}:{};return e&&e===r?{mode:"replace",...s}:{mode:"elsewhere",installsTo:r,...s}},"versionAtLeast",0,function(e,t){return(0,r.compareVersions)(e,t)>=0}])}];
|
|
7
|
-
|
|
8
|
-
//# sourceMappingURL=%5Broot-of-the-server%5D__17hjtzl._.js.map
|