six-degrees 0.7.0 → 0.8.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/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +32 -4
- package/.next/standalone/.next/routes-manifest.json +6 -0
- 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 +16 -16
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- 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.nft.json +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/backup/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/csv/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/restore/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 +3 -3
- 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 +4 -4
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +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.nft.json +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 +1 -1
- 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 +18 -18
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +18 -18
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +19 -19
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/insights/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/insights/page/build-manifest.json +20 -0
- package/.next/standalone/.next/server/app/insights/page/next-font-manifest.json +11 -0
- package/.next/standalone/.next/server/app/insights/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/insights/page/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/insights/page.js +13 -0
- package/.next/standalone/.next/server/app/insights/page.js.map +5 -0
- package/.next/standalone/.next/server/app/insights/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/insights/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/insights.html +1 -0
- package/.next/standalone/.next/server/app/insights.meta +26 -0
- package/.next/standalone/.next/server/app/insights.rsc +35 -0
- package/.next/standalone/.next/server/app/insights.segments/_full.segment.rsc +35 -0
- package/.next/standalone/.next/server/app/insights.segments/_tree.segment.rsc +4 -0
- package/.next/standalone/.next/server/app/insights.segments/insights/__PAGE__.segment.rsc +33 -0
- 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 +18 -18
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +18 -18
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +16 -16
- 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 +19 -19
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +16 -16
- 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 +19 -19
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +16 -16
- 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 +19 -19
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +21 -20
- 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 +18 -18
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +18 -18
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +16 -16
- 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 +19 -19
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +16 -16
- 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 +19 -19
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +16 -16
- 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 +18 -18
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +18 -18
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__063s0x7._.js → [root-of-the-server]__0r-0t-9._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ra6xyg._.js +15 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__20rwfdw._.js → [root-of-the-server]__0rcpjad._.js} +5 -2
- 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/lib_02iz_9q._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_05agvk3._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_0v84u66._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_db-client_0_dch35.js +9 -9
- package/.next/standalone/.next/server/chunks/{lib_rpc_0jnfp2e.js → lib_rpc_00nndai.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_0b17r6v.js → lib_rpc_03ibb-x.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_0doq0t-.js → lib_rpc_03ri3ji.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_0fd2rlw.js → lib_rpc_078ce48.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0a69mgr.js +3 -0
- package/.next/standalone/.next/server/chunks/{lib_rpc_005abye.js → lib_rpc_0cej3t8.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0cmh4g6.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0nx2_pc.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0r79-w1.js +3 -0
- package/.next/standalone/.next/server/chunks/{lib_rpc_0is4qoc.js → lib_rpc_0t552z6.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0yx8ynd.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_12p-o9-.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1466dnb.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1sryxv6.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1trn-x5.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1ygu_mv.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0egamyq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_04am_h8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_07zb7lu._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0_79idq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0gds903._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0klqt9z._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1k9qfsk._.js → _14jak5s._.js} +6 -2
- package/.next/standalone/.next/server/chunks/ssr/_14nxd3q._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1cj2yog._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1hdqw0-._.js +7 -0
- package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0gy11ar._.js → _1r62six._.js} +6 -2
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_insights_page_actions_0-0tf75.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_01o3i5y._.js → app_components_settings_SectorPicker_1c-29qw.js} +2 -6
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_insights_page_1d_chja.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +14 -14
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +3 -3
- 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 +3 -3
- 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/next-font-manifest.js +1 -1
- package/.next/standalone/.next/server/next-font-manifest.json +4 -0
- 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/prefetch-hints.json +14 -0
- 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/02wnf8dkxegxg.js +34 -0
- package/.next/standalone/.next/static/chunks/0838_s6w-i_ik.js +1 -0
- package/.next/{static/chunks/2eo9pqq9gay3r.js → standalone/.next/static/chunks/0ka4prpk3w9wn.js} +4 -4
- package/.next/standalone/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
- package/.next/standalone/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
- package/.next/standalone/.next/static/chunks/112mv-com-mqa.js +21 -0
- package/.next/{static/chunks/44yn0tcd9dfk3.js → standalone/.next/static/chunks/1bkt_hhjzvbh8.js} +1 -1
- package/.next/standalone/.next/static/chunks/1cmphb3kh2ers.css +3 -0
- package/.next/{static/chunks/1qbjermbf68xj.js → standalone/.next/static/chunks/1d8yv3k_atka7.js} +1 -1
- package/.next/standalone/.next/static/chunks/23hc51oa4vkhc.js +5 -0
- package/.next/standalone/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
- package/.next/standalone/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
- package/.next/{static/chunks/2zf4jmz9l3li2.js → standalone/.next/static/chunks/2w-y83mjlpnse.js} +1 -1
- package/.next/standalone/.next/static/chunks/2ws1xf293j-tm.js +5 -0
- package/.next/standalone/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
- package/.next/standalone/.next/static/chunks/3opgg1efrx8q6.js +1 -0
- package/.next/standalone/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
- package/.next/{static/chunks/0an_b6zi1r_qq.js → standalone/.next/static/chunks/3y40z6of3r9py.js} +1 -5
- package/.next/standalone/.next/static/chunks/403dpflln8ah5.js +1 -0
- package/.next/standalone/CHANGELOG.md +60 -0
- package/.next/standalone/README.md +25 -1
- package/.next/standalone/SECURITY.md +8 -0
- package/.next/standalone/app/api/outreach/route.js +13 -73
- package/.next/standalone/app/api/scraper/route.js +97 -4
- package/.next/standalone/app/components/AppHeader.js +22 -6
- package/.next/standalone/app/components/AppTabs.js +4 -0
- package/.next/standalone/app/components/AutoConnect.js +286 -0
- package/.next/standalone/app/components/ChainView.js +75 -2
- package/.next/standalone/app/components/FilterPanel.js +8 -1
- package/.next/standalone/app/components/LinkedInLimits.js +38 -13
- package/.next/standalone/app/components/OutlinkQuest.js +32 -11
- package/.next/standalone/app/components/ScanStatusBar.js +18 -3
- package/.next/standalone/app/components/ScanTrail.js +13 -3
- package/.next/standalone/app/components/SeparationView.js +6 -22
- package/.next/standalone/app/components/Sidebar.js +147 -38
- package/.next/standalone/app/components/insights/Boards.js +275 -0
- package/.next/standalone/app/components/insights/PeopleView.js +266 -0
- package/.next/standalone/app/components/insights/Rail.js +156 -0
- package/.next/standalone/app/components/insights/Report.js +382 -0
- package/.next/standalone/app/components/insights/parts.js +239 -0
- package/.next/standalone/app/components/settings/UsageSection.js +50 -3
- package/.next/standalone/app/globals.css +2 -3
- package/.next/standalone/app/insights/page.js +220 -0
- package/.next/standalone/app/page.js +1 -17
- package/.next/standalone/app/queue/page.js +30 -18
- package/.next/standalone/app/setup/page.js +16 -9
- package/.next/standalone/lib/auto-connect.js +145 -0
- package/.next/standalone/lib/companies.js +1 -1
- package/.next/standalone/lib/in-progress.js +46 -0
- package/.next/standalone/lib/insights-board.js +613 -0
- package/.next/standalone/lib/linkedin-limits.js +36 -11
- package/.next/standalone/lib/requests-client.js +10 -0
- package/.next/standalone/lib/requests.js +124 -0
- package/.next/standalone/lib/scan-state.js +1 -0
- package/.next/standalone/lib/scraper-client.js +18 -5
- package/.next/standalone/lib/settings.js +3 -0
- package/.next/standalone/lib/usage.js +5 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +569 -9
- package/.next/static/chunks/02wnf8dkxegxg.js +34 -0
- package/.next/static/chunks/0838_s6w-i_ik.js +1 -0
- package/.next/{standalone/.next/static/chunks/2eo9pqq9gay3r.js → static/chunks/0ka4prpk3w9wn.js} +4 -4
- package/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
- package/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
- package/.next/static/chunks/112mv-com-mqa.js +21 -0
- package/.next/{standalone/.next/static/chunks/44yn0tcd9dfk3.js → static/chunks/1bkt_hhjzvbh8.js} +1 -1
- package/.next/static/chunks/1cmphb3kh2ers.css +3 -0
- package/.next/{standalone/.next/static/chunks/1qbjermbf68xj.js → static/chunks/1d8yv3k_atka7.js} +1 -1
- package/.next/static/chunks/23hc51oa4vkhc.js +5 -0
- package/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
- package/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
- package/.next/{standalone/.next/static/chunks/2zf4jmz9l3li2.js → static/chunks/2w-y83mjlpnse.js} +1 -1
- package/.next/static/chunks/2ws1xf293j-tm.js +5 -0
- package/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
- package/.next/static/chunks/3opgg1efrx8q6.js +1 -0
- package/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
- package/.next/{standalone/.next/static/chunks/0an_b6zi1r_qq.js → static/chunks/3y40z6of3r9py.js} +1 -5
- package/.next/static/chunks/403dpflln8ah5.js +1 -0
- package/CHANGELOG.md +60 -0
- package/README.md +25 -1
- package/SECURITY.md +8 -0
- package/lib/auto-connect.js +145 -0
- package/lib/companies.js +1 -1
- package/lib/in-progress.js +46 -0
- package/lib/insights-board.js +613 -0
- package/lib/linkedin-limits.js +36 -11
- package/lib/requests-client.js +10 -0
- package/lib/requests.js +124 -0
- package/lib/scan-state.js +1 -0
- package/lib/scraper-client.js +18 -5
- package/lib/settings.js +3 -0
- package/lib/usage.js +5 -0
- package/package.json +1 -1
- package/scripts/scrape.py +569 -9
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_4y8z7._.js +0 -8
- package/.next/standalone/.next/server/chunks/_02twlca._.js +0 -10
- package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +0 -7
- package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +0 -3
- package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +0 -21
- package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
- package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +0 -34
- package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
- package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +0 -57
- package/.next/standalone/.next/static/chunks/260bomtr214m8.js +0 -34
- package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +0 -34
- package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +0 -1
- package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +0 -1
- package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +0 -3
- package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
- package/.next/static/chunks/0e3g0sil79cus.js +0 -21
- package/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
- package/.next/static/chunks/163b-20vwa06i.js +0 -34
- package/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
- package/.next/static/chunks/23_1z47al92nz.js +0 -57
- package/.next/static/chunks/260bomtr214m8.js +0 -34
- package/.next/static/chunks/2xk2ra4piiuod.js +0 -34
- package/.next/static/chunks/3b0-14cxqsprt.js +0 -1
- package/.next/static/chunks/3jarus1_38s2z.js +0 -1
- package/.next/static/chunks/3ssu341hav_sq.css +0 -3
- package/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_ssgManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_buildManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_ssgManifest.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,94657,e=>{"use strict";var t=e.i(43476),n=e.i(71645),r=e.i(7837);let o=e=>`${e.name} ${e.was} → ${e.estimated?"estimated":e.now}`,l=e=>({padding:"6px 12px",borderRadius:7,cursor:"pointer",fontSize:12,fontWeight:700,border:e?"none":"1px solid rgba(var(--sd-ink, 255, 255, 255), 0.15)",background:e?"linear-gradient(135deg, #00ff88, #3498DB)":"transparent",color:e?"#000":"var(--sd-fg-2, #cfd8d8)"});function s({onAnswered:e}){var a;let i,c,[d,u]=(0,n.useState)(null),[f,p]=(0,n.useState)(!1),[m,g]=(0,n.useState)(()=>new Set),[h,y]=(0,n.useState)(!1),[b,x]=(0,n.useState)(null);if((0,n.useEffect)(()=>{let e=!1;return(0,r.csvNetworkSource)().then(t=>t||e?null:fetch("/api/company-scores/legacy").then(e=>e.json())).then(t=>{!e&&t?.offer?.companies?.length&&(u(t.offer),g(new Set(t.offer.companies.map(e=>e.name))))}).catch(()=>{}),()=>{e=!0}},[]),!d)return null;let v=async t=>{y(!0),x(null);try{let n=await fetch("/api/company-scores/legacy",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({keep:t})}),r=await n.json();if(!n.ok&&!r.kept)throw Error(r.error||"Could not save your answer.");u(null),e?.({kept:r.kept||[],scored:r.scored||0,error:n.ok?null:r.error})}catch(e){x(e.message)}finally{y(!1)}};return(0,t.jsxs)("section",{"aria-label":"Built-in scores changed",style:{marginBottom:14,padding:14,borderRadius:10,border:"1px solid rgba(255,215,0,0.35)",background:"rgba(255,215,0,0.06)",fontSize:12.5,color:"var(--sd-fg-2, #cfd8d8)",lineHeight:1.6},children:[(0,t.jsx)("b",{style:{color:"var(--sd-fg-1, #fff)"},children:"Built-in scores changed in this version:"})," ",(i=(a=d.companies).slice(0,3).map(o).join(", "),(c=a.length-3)>0?`${i} and ${c} more`:i),"."," ","Keep any of the old ones as your own?",(0,t.jsx)("div",{style:{marginTop:4,color:"var(--sd-fg-3, #8b9a9a)"},children:"The built-in list now holds only companies most professionals would recognise, scored the same for everyone; anything else is estimated from your network. A score you keep is yours, like one you set below, and Auto hands it back."}),f&&(0,t.jsx)("ul",{style:{listStyle:"none",padding:0,margin:"10px 0 0",display:"grid",gap:4},children:d.companies.map(e=>(0,t.jsx)("li",{children:(0,t.jsxs)("label",{style:{display:"flex",gap:8,alignItems:"baseline",cursor:"pointer"},children:[(0,t.jsx)("input",{type:"checkbox",checked:m.has(e.name),disabled:h,onChange:()=>{let t;return t=e.name,g(e=>{let n=new Set(e);return n.has(t)?n.delete(t):n.add(t),n})}}),(0,t.jsxs)("span",{children:[(0,t.jsx)("b",{style:{color:"var(--sd-fg-1, #fff)"},children:e.name})," ",e.was," → ",e.estimated?`estimated (${e.now})`:e.now,(0,t.jsxs)("span",{style:{color:"var(--sd-fg-4, #778)"},children:[` \xb7 ${e.people} ${1===e.people?"person":"people"} there now`,e.names.length>1&&` \xb7 for ${e.names.join(", ")}`]})]})]})},e.name))}),(0,t.jsxs)("div",{style:{display:"flex",gap:8,marginTop:10,flexWrap:"wrap"},children:[f?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("button",{disabled:h||!m.size,onClick:()=>v([...m]),style:{...l(!0),opacity:h||!m.size?.5:1},children:["Keep ",m.size," ",1===m.size?"score":"scores"]}),(0,t.jsx)("button",{disabled:h,onClick:()=>p(!1),style:l(!1),children:"Back"})]}):(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("button",{disabled:h,onClick:()=>v(d.companies.map(e=>e.name)),style:l(!0),children:"Keep all"}),(0,t.jsx)("button",{disabled:h,onClick:()=>p(!0),style:l(!1),children:"Choose…"})]}),(0,t.jsx)("button",{disabled:h,onClick:()=>v([]),style:l(!1),children:"No thanks"})]}),b&&(0,t.jsx)("div",{role:"alert",style:{marginTop:8,color:"#e74c3c"},children:b})]})}let a="1px solid rgba(var(--sd-ink, 255, 255, 255), 0.1)",i={yours:{label:"your score",color:"var(--sd-green, #00ff88)"},known:{label:"known list",color:"var(--sd-blue, #3498DB)"},data:{label:"public data",color:"#1abc9c"},network:{label:"estimated: many of your people",color:"var(--sd-orange, #FF6B35)"},default:{label:"unknown company",color:"var(--sd-fg-4, #778)"}};async function c(){let e=await fetch("/api/company-scores"),t=await e.json();if(!e.ok)throw Error(t.error||"Could not load company scores");return t.companies}function d(){let[e,t]=(0,n.useState)(null),[r,o]=(0,n.useState)(null);return(0,n.useEffect)(()=>{let e=!1;return c().then(n=>{e||t(n)}).catch(t=>{e||o(t.message)}),()=>{e=!0}},[]),{companies:e,error:r,setScore:(0,n.useCallback)(async(e,n)=>{let r=await fetch("/api/company-scores",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,score:n})}),o=await r.json();if(!r.ok)throw Error(o.error||"Could not save");return t(await c()),o},[]),reload:(0,n.useCallback)(async()=>t(await c()),[])}}function u({company:e,onSet:r,compact:o}){let[l,s]=(0,n.useState)(!1),i=async t=>{s(!0);try{await r(e.name,"auto"===t?null:Number(t))}finally{s(!1)}};return(0,t.jsxs)("select",{disabled:l,value:"yours"===e.source?String(e.score):"auto",onChange:e=>i(e.target.value),title:"Set this company's score. Auto uses the known list or an estimate.",style:{padding:o?"2px 4px":"4px 6px",borderRadius:6,border:a,background:"var(--sd-card, #11131c)",color:"var(--sd-fg-1, #dfe6e9)",fontSize:12},children:[(0,t.jsxs)("option",{value:"auto",children:["Auto (","yours"===e.source?e.suggested??"est.":e.score,")"]}),[10,9,8,7,6,5,4,3,2,1].map(e=>(0,t.jsx)("option",{value:e,children:e},e))]})}e.s(["ScorePicker",0,u,"default",0,function({onRescored:e,version:r=0}){let{companies:o,error:l,setScore:c,reload:f}=d();(0,n.useEffect)(()=>{r&&f().catch(()=>{})},[r,f]);let[p,m]=(0,n.useState)(""),[g,h]=(0,n.useState)("all"),[y,b]=(0,n.useState)(150),[x,v]=(0,n.useState)(null),w=e=>{v(e),setTimeout(()=>v(null),3500)},S=async(t,n)=>{let r=await c(t,n);w(`${t}: ${null==n?"back to auto":`set to ${n}`} \xb7 rescored ${r.scored} people`),e?.()},k=async({kept:t,scored:n,error:r})=>{if(!t.length)return w("Keeping the new built-in scores.");w(r||`Kept ${t.length} old ${1===t.length?"score":"scores"} as your own \xb7 rescored ${n} people`),await f().catch(()=>{}),e?.()},j=(0,n.useMemo)(()=>{if(!o)return[];let e=p.trim().toLowerCase();return o.filter(t=>(!e||t.name.toLowerCase().includes(e))&&("all"===g||t.source===g))},[o,p,g]);if(l)return(0,t.jsx)("div",{style:{padding:24,color:"#e74c3c"},children:l});if(!o)return(0,t.jsx)("div",{style:{padding:24,color:"var(--sd-fg-4, #778)"},children:"Loading companies…"});let C=e=>o.filter(t=>t.source===e).length;return(0,t.jsxs)("div",{style:{flex:1,overflow:"auto",padding:16},children:[(0,t.jsxs)("div",{style:{maxWidth:980,margin:"0 auto"},children:[(0,t.jsx)(s,{onAnswered:k}),(0,t.jsxs)("div",{style:{padding:14,borderRadius:10,border:a,background:"rgba(var(--sd-ink, 255, 255, 255), 0.03)",fontSize:12.5,color:"var(--sd-fg-2, #cfd8d8)",lineHeight:1.6},children:[(0,t.jsx)("b",{style:{color:"var(--sd-fg-1, #fff)"},children:"How a power score works."})," power = ",(0,t.jsx)("b",{children:"title"})," × ",(0,t.jsx)("b",{children:"company weight"})," + bonus. The title (student 1 … founder or C-suite 10) comes from someone’s roles, with former roles at 70%; an audience of their own counts like a title (1M+ followers like a director). The company weight runs from 0.725 for a company we don’t know (or none found), the middle of the scale, to 1.0 (a company scored 10), so seniority counts for more at a bigger company. The bonus is at most +1.5, for investor, YC, an audience in the millions or the top award of any field, and counts half at an unknown company. A 1st-degree person whose circle is unusually strong gets up to +2 more: up to +1 for a high share of it at A or S, or +1 for every 25 people there. Tiers are graded on your network’s curve unless you choose the fixed scale (",(0,t.jsx)("a",{href:"#tiers",style:{color:"var(--sd-blue, #3498DB)"},children:"Tiers"}),", above): your top 3% are S, the next 12% A, the next 25% B (it only lifts, and never lifts anyone under 4 into S or A). On the fixed scale, S ≥ 7.5 · A ≥ 5.5 · B ≥ 4 · C ≥ 2.5.",(0,t.jsxs)("div",{style:{marginTop:6,color:"var(--sd-fg-3, #8b9a9a)"},children:["So a VP at a 10 scores 9.0, a founder at an unknown company 7.3, a director at an unknown company 5.4, and an intern at a 10 scores 2.0. Set a company’s score below and everyone there is rescored. Companies in the sectors you pick (",(0,t.jsx)("a",{href:"#sector",style:{color:"var(--sd-blue, #3498DB)"},children:"Your sector"}),", above) get +1 or +2; a score you set is never changed."]})]}),(0,t.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap",margin:"14px 0 10px"},children:[(0,t.jsx)("input",{value:p,onChange:e=>m(e.target.value),placeholder:"Find a company",style:{padding:"6px 10px",borderRadius:7,border:a,background:"rgba(var(--sd-ink, 255, 255, 255), 0.05)",color:"var(--sd-fg-1, #fff)",fontSize:13,minWidth:180}}),[["all",`All ${o.length}`],["known",`Known list ${C("known")}`],["data",`Public data ${C("data")}`],["yours",`Yours ${C("yours")}`],["network",`Estimated ${C("network")}`],["default",`Unknown ${C("default")}`]].map(([e,n])=>(0,t.jsx)("button",{onClick:()=>h(e),style:{padding:"5px 10px",borderRadius:7,border:a,cursor:"pointer",fontSize:11.5,fontWeight:600,background:g===e?"rgba(52,152,219,0.25)":"transparent",color:g===e?"var(--sd-fg-1, #cfe6f7)":"var(--sd-fg-3, #8b9a9a)"},children:n},e))]}),(0,t.jsxs)("div",{style:{borderRadius:10,border:a,overflow:"hidden"},children:[(0,t.jsxs)("div",{style:{display:"grid",gridTemplateColumns:"1fr 70px 70px 70px 60px 180px 110px",gap:8,padding:"8px 12px",fontSize:10.5,letterSpacing:.6,color:"var(--sd-fg-3, #8b9a9a)",background:"rgba(var(--sd-ink, 255, 255, 255), 0.04)"},children:[(0,t.jsx)("span",{children:"COMPANY"}),(0,t.jsx)("span",{style:{textAlign:"right"},children:"PEOPLE"}),(0,t.jsx)("span",{style:{textAlign:"right"},children:"YOU KNOW"}),(0,t.jsx)("span",{style:{textAlign:"right"},children:"DIRECTOR+"}),(0,t.jsx)("span",{style:{textAlign:"right"},children:"SCORE"}),(0,t.jsx)("span",{children:"SOURCE"}),(0,t.jsx)("span",{children:"SET"})]}),j.slice(0,y).map(e=>(0,t.jsxs)("div",{style:{display:"grid",gridTemplateColumns:"1fr 70px 70px 70px 60px 180px 110px",gap:8,padding:"7px 12px",alignItems:"center",borderTop:"1px solid rgba(var(--sd-ink, 255, 255, 255), 0.05)",fontSize:12.5},children:[(0,t.jsxs)("span",{style:{display:"flex",alignItems:"center",gap:8,minWidth:0},children:[(0,t.jsx)("span",{title:`${e.industry.label} (inferred)`,style:{width:8,height:8,borderRadius:"50%",background:e.industry.color,flexShrink:0}}),(0,t.jsx)("span",{style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.name})]}),(0,t.jsx)("span",{style:{textAlign:"right"},children:e.people}),(0,t.jsx)("span",{style:{textAlign:"right",color:e.d1?"var(--sd-green, #00ff88)":"var(--sd-fg-5, #556)"},children:e.d1}),(0,t.jsx)("span",{style:{textAlign:"right",color:e.senior?"var(--sd-gold, #FFD700)":"var(--sd-fg-5, #556)"},children:e.senior}),(0,t.jsx)("b",{style:{textAlign:"right",fontSize:15},children:e.score}),(0,t.jsxs)("span",{style:{fontSize:11,color:i[e.source]?.color},children:[i[e.source]?.label,e.sectorBonus>0&&(0,t.jsxs)("span",{style:{color:"var(--sd-green, #00ff88)"},children:[" + ",e.sectorBonus," your sector"]})]}),(0,t.jsx)(u,{company:e,onSet:S,compact:!0})]},e.name))]}),j.length>y&&(0,t.jsxs)("button",{onClick:()=>b(e=>e+300),style:{marginTop:10,padding:"7px 14px",borderRadius:8,border:a,background:"rgba(var(--sd-ink, 255, 255, 255), 0.05)",color:"var(--sd-fg-1, #dfe6e9)",cursor:"pointer",fontSize:12},children:["Show more (",j.length-y," left)"]})]}),x&&(0,t.jsx)("div",{role:"status",style:{position:"fixed",bottom:24,left:"50%",transform:"translateX(-50%)",padding:"10px 18px",borderRadius:24,background:"linear-gradient(135deg, #00ff88, #3498DB)",color:"#000",fontWeight:800,fontSize:13,zIndex:50},children:x})]})},"useCompanyScores",0,d],94657)},39160,84278,e=>{"use strict";var t=e.i(7214),n=e.i(7837);function r(e){let t=String(e||"").trim();if(!t)return null;let n=t.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?(?:\s*UTC)?$/);if(n)return Date.UTC(+n[1],n[2]-1,+n[3],+n[4],+n[5],+(n[6]||0));if(n=t.match(/^(\d{1,2})\/(\d{1,2})\/(\d{2,4}),?\s+(\d{1,2}):(\d{2})\s*(AM|PM)?$/i)){let e=100>+n[3]?2e3+ +n[3]:+n[3],t=n[4]%12;return"PM"===(n[6]||"").toUpperCase()&&(t+=12),Date.UTC(e,n[1]-1,+n[2],t,+n[5])}if(n=t.match(/^([A-Za-z]{3,9})\s+(\d{4})$/)){let e=Date.parse(`1 ${n[1]} ${n[2]} UTC`);return Number.isNaN(e)?null:e}let r=Date.parse(/\d{4}$/.test(t)?`${t} UTC`:t);return Number.isNaN(r)?null:r}let o=e=>(0,t.keyFor)({profile_url:e});function l(e=[]){let t=new Map;for(let n of e){let e=o(n["sender profile url"]),r=n["conversation id"];r&&e&&(t.has(e)||t.set(e,new Set),t.get(e).add(r))}let n=null;for(let[e,r]of t)(!n||r.size>t.get(n).size)&&(n=e);return n}let s=e=>{let t=f(e);return t?`name:${t}`:null},a=/sponsored/i,i=/in\s?mail/i,c=/linkedin\.com\/(company|showcase|school)\//i,d=e=>/^(yes|true|1)$/i.test(String(e||"").trim());function u(e,t){if(!e||!e.total)return"never";let n=t-e.last;return n<=2592e6||e.recent>=3?"warm":n<=31536e6?"cool":"dormant"}let f=e=>String(e||"").split(",")[0].normalize("NFD").replace(/[̀-ͯ]/g,"").toLowerCase().replace(/[^a-z0-9 ]+/g," ").replace(/\s+/g," ").trim();e.s(["buildConversations",0,function(e=[],{keepText:t=!1}={}){let n=l(e),u=new Map;for(let t of e)n&&o(t["sender profile url"])===n&&t.from&&u.set(t.from,(u.get(t.from)||0)+1);let p=f([...u].sort((e,t)=>t[1]-e[1])[0]?.[0]||""),m=e=>!!e&&(e===n||!!p&&e===`name:${p}`),g={},h=new Map,y=0,b=0;for(let n of e){let e=n["conversation id"],l=r(n.date);if(!e||null==l){b+=1;continue}if(d(n["is message draft"])){y+=1;continue}let u=o(n["sender profile url"])||s(n.from);u&&n.from&&!g[u]&&(g[u]=String(n.from).trim());let f=String(n["recipient profile urls"]||"").split(/[,;\s]+/).filter(Boolean).map(o),p=String(n.to||"").split(",").map(e=>e.trim()).filter(Boolean),x=f.length?f:p.map(s).filter(Boolean);f.length&&f.length===p.length&&f.forEach((e,t)=>{g[e]||(g[e]=p[t])}),f.length||p.forEach(e=>{let t=s(e);t&&!g[t]&&(g[t]=e)}),h.has(e)||h.set(e,{id:e,title:"",everyone:new Set,messages:[],kind:null,folder:"",newest:-1/0});let v=h.get(e);u&&v.everyone.add(u),x.forEach(e=>v.everyone.add(e)),v.kind=v.kind||function(e){let t=String(e.folder||""),n=String(e["conversation title"]||"");return a.test(t)||a.test(n)||c.test(String(e["sender profile url"]||""))?"sponsored":i.test(t)||i.test(n)?"inmail":null}(n),l>=v.newest&&(v.newest=l,v.folder=String(n.folder||"").trim().toLowerCase().slice(0,30));let w={t:l,fromMe:m(u)};t&&(w.text=[n.subject,n.content].map(e=>String(e||"").trim()).filter(Boolean).join("\n\n"),!v.title&&n["conversation title"]&&(v.title=String(n["conversation title"]).trim())),v.messages.push(w)}let x=[];for(let e of h.values()){let n=[...e.everyone].filter(e=>e&&!m(e)).sort();e.messages.sort((e,t)=>e.t-t.t);let r={id:e.id,people:n,group:n.length>1,kind:e.kind,folder:e.folder,messages:e.messages};t&&(r.title=e.title),x.push(r)}for(let e of Object.keys(g))m(e)&&delete g[e];return{self:n,conversations:x,names:g,drafts:y,skipped:b}},"buildInvitations",0,function(e=[],{keepText:t=!1}={}){let n=[];for(let l of e){let e=String(l.direction||"").toUpperCase();if("INCOMING"!==e&&"OUTGOING"!==e)continue;let a="INCOMING"===e?"in":"out",i=String(("in"===a?l.from:l.to)||"").trim(),c=o("in"===a?l.inviterprofileurl:l.inviteeprofileurl),d=(c&&/\/in\//.test(c)?c:null)||s(i);if(!d)continue;let u=r(l["sent at"]),f={id:`${a}|${d}|${u??""}`,dir:a,key:d,name:i.slice(0,120),t:u};t&&String(l.message||"").trim()&&(f.note=String(l.message).trim().slice(0,2e3)),n.push(f)}return n.sort((e,t)=>(t.t||0)-(e.t||0))},"careerChapters",0,function(e=[],t=[],n=Date.now()){let l=e.map(e=>({company:e["company name"]||"",title:e.title||"",from:r(e["started on"]),to:r(e["finished on"])})).filter(e=>e.company&&null!=e.from).sort((e,t)=>e.from-t.from),s=t.map(e=>({url:o(e.url||e.profile_url),t:r(e["connected on"]||e.connected_on)})).filter(e=>null!=e.t);return l.map(e=>{let t=e.to??n,r=s.filter(n=>n.t>=e.from&&n.t<t);return{...e,to:e.to,made:r.length,people:r.map(e=>e.url)}})},"conversationSummary",0,function(e){let t=e.messages[e.messages.length-1];return{id:e.id,people:e.people,group:e.group,kind:e.kind||null,folder:e.folder||"",last:t?t.t:null,lastFromThem:t?!t.fromMe:null,count:e.messages.length,mine:e.messages.filter(e=>e.fromMe).length}},"invitationSplit",0,function(e=[]){let t=0,n=0;for(let r of e){let e=String(r.direction||"").toUpperCase();"INCOMING"===e?t+=1:"OUTGOING"===e&&(n+=1)}return{incoming:t,outgoing:n,total:t+n}},"messageStats",0,function(e=[]){let t=l(e),n=new Map;for(let t of e){let e=o(t["sender profile url"]),l=t["conversation id"];if(!l||!e)continue;let s=String(t["recipient profile urls"]||"").split(/[,;\s]+/).filter(Boolean).map(o),a=r(t.date);if(null==a)continue;n.has(l)||n.set(l,{people:new Set,msgs:[]});let i=n.get(l);i.people.add(e),s.forEach(e=>i.people.add(e)),i.msgs.push({from:e,t:a})}let s=0,a=new Map;for(let e of n.values()){let n=[...e.people].filter(e=>e!==t);if(1!==n.length||!e.people.has(t))continue;let r=n[0],o=a.get(r)||{last:0,lastFromThem:!1,total:0,times:[]};for(let n of e.msgs)o.total+=1,o.times.push(n.t),n.t>=o.last&&(o.last=n.t,o.lastFromThem=n.from!==t),n.t>s&&(s=n.t);a.set(r,o)}for(let e of a.values())e.recent=e.times.filter(e=>e>s-7776e6).length,delete e.times;return{self:t,asOf:s||null,people:a}},"nameKey",0,f,"postingEffect",0,function(e=[],t=[]){let n=e.map(e=>({t:r(e.date),link:e.sharelink||e["share link"]||""})).filter(e=>null!=e.t).sort((e,t)=>e.t-t.t),o=t.map(e=>r(e["connected on"]||e.connected_on)).filter(e=>null!=e).sort((e,t)=>e-t);if(!o.length)return{posts:[],usualWeek:0,afterPosts:0};let l=Math.max(6048e5,o[o.length-1]-o[0]),s=o.length/l*6048e5,a=n.map(e=>({...e,week:o.filter(t=>t>=e.t&&t<e.t+6048e5).length})),i=a.length?a.reduce((e,t)=>e+t.week,0)/a.length:0;return{posts:a,usualWeek:s,afterPosts:i}},"readTable",0,function(e,t){let r=(0,n.parseCsv)(String(e||"")),o=r.findIndex(e=>e.some(e=>e.trim().toLowerCase()===t.toLowerCase()));if(o<0)return[];let l=r[o].map(e=>e.trim().toLowerCase());return r.slice(o+1).filter(e=>e.some(e=>e.trim())).map(e=>Object.fromEntries(l.map((t,n)=>[t,(e[n]??"").trim()])))},"warmthOf",0,u],84278);let p=["gravity","rings","push","pull","distance","orbit","dotSize","lines","sizeBy","names","branch","colourBy","speed","loop"],m=e=>"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e)&&1e3>=Math.abs(e)||"string"==typeof e&&/^[a-z]{1,20}$/i.test(e);function g(e){if(!Array.isArray(e))return[];let t=new Map;for(let n of e){let e="string"==typeof n?.name?n.name.trim().slice(0,40):"";if(!e||!n.settings||"object"!=typeof n.settings||Array.isArray(n.settings))continue;let r={};for(let e of p)Object.hasOwn(n.settings,e)&&m(n.settings[e])&&(r[e]=n.settings[e]);t.set(e,{name:e,settings:r})}return[...t.values()].slice(0,50)}var h=e.i(56899),y=e.i(40827);let b="six-degrees-galaxy-lab",x="six-degrees-galaxy-layouts",v=Object.freeze({on:!0,gravity:0,rings:1,push:15,pull:1,distance:1,dotSize:1,lines:1,sizeBy:"power",orbit:0,names:"key",branch:!1,colourBy:"tier",speed:15,loop:!1,labels:!0,v:2}),w=["gravity","rings","push","pull","distance"],S=Object.freeze({gravity:.8,rings:0,push:6,pull:6,distance:.15,sizeBy:"links"}),k=Object.freeze({rings:{lines:1},orbit:{lines:1},clusters:{lines:3}}),j=Object.freeze({gravity:0,rings:3,push:6,pull:4,distance:.22,sizeBy:"power"}),C=new Set,O=null;function T(){return O||(O=function(){try{let e=JSON.parse(localStorage.getItem(b)||"null");if(!e||"object"!=typeof e)return v;return{...v,...e,...e.v===v.v?{}:{branch:!1},v:v.v,on:!0}}catch{return v}}()),O}function F(e){O=null===e?{...v,labels:T().labels}:{...T(),...e,on:!0};try{localStorage.setItem(b,JSON.stringify(O))}catch{}C.forEach(e=>e())}let A=null,N=new Set;function M(){if(!A){try{A=JSON.parse(localStorage.getItem(x)||"[]")}catch{A=[]}Array.isArray(A)||(A=[])}return A}function $(e,{save:t=!0}={}){A=e;try{localStorage.setItem(x,JSON.stringify(e))}catch{}N.forEach(e=>e()),t&&(0,h.saveSettings)({galaxyLayouts:e})}let B=new Set,E={at:null,playing:!1,min:null,max:null,of:0,find:"",fly:0,found:0,fit:0,social:void 0},z=null;function L(e){let t={...E,...e};Object.keys(e).every(e=>Object.is(t[e],E[e]))||(E=t,B.forEach(e=>e()))}function D(){cancelAnimationFrame(z),z=null,L({playing:!1})}let I=null;function R(e){let n=new Map;for(let[r,o]of Object.entries(e?.people||{}))n.set((0,t.keyFor)({profile_url:r}),{...o});for(let[r,o]of Object.entries(e?.live||{})){let e=(0,t.keyFor)({profile_url:r}),l=n.get(e)||{total:0,recent:0};o.last&&(!l.last||o.last>l.last)&&(l.last=o.last,"boolean"==typeof o.lastFromThem&&(l.lastFromThem=o.lastFromThem)),l.unread=o.unread,l.total=Math.max(l.total||0,1),n.set(e,l)}let r=Math.max(e?.asOf||0,e?.liveAt&&Date.parse(e.liveAt)||0);for(let e of n.values())(e.last||0)>r&&(r=e.last);return{merged:n,asOf:r,chapters:e?.chapters||[],posts:e?.posts?.posts||[]}}let U={1:"#4FC3F7",2:"#BA68C8",3:"#FFB74D"},P=["#FF6B6B","#4ECDC4","#FFD93D","#A29BFE","#00B894","#FD79A8","#74B9FF","#E17055"],K={warm:"#FF7043",cool:"#4FC3F7",dormant:"#8D6E63",never:"#546E7A"},W="#4a5568",_=["#2b1b6b","#6a1b9a","#c2185b","#f4511e","#ffb300","#fff3b0"];function J(e){let t=Math.max(0,Math.min(1,Number(e)||0))*(_.length-1),n=Math.min(_.length-2,Math.floor(t)),r=t-n,o=_[n],l=_[n+1];return`#${[1,3,5].map(e=>Math.round(parseInt(o.slice(e,e+2),16)*(1-r)+parseInt(l.slice(e,e+2),16)*r).toString(16).padStart(2,"0")).join("")}`}function Y(e=[],t=()=>0){let n=[...new Set(e.map(t))].sort((e,t)=>e-t),r=new Map(n.map((e,t)=>[e,n.length>1?t/(n.length-1):1]));return e=>r.get(t(e))??0}let G=e=>String(e||"").trim().toLowerCase();e.s(["CLUSTERS",0,S,"FORCE_KEYS",0,w,"LAB_DEFAULTS",0,v,"LAYOUT_LOOKS",0,k,"ORBIT",0,j,"adoptSavedLayouts",0,function(e){let t=g(M()),{use:n,save:r,changed:o}=(0,y.reconcileSaved)({saved:null==e?null:g(e),cached:t,isDefault:e=>0===e.length,same:(e,t)=>JSON.stringify(e)===JSON.stringify(t)});r&&(0,h.saveSettings)({galaxyLayouts:t}),o&&$(n,{save:!1})},"applyLayout",0,function(e){let t=M().find(t=>t.name===e);t&&F(Object.fromEntries(p.filter(e=>e in t.settings).map(e=>[e,t.settings[e]])))},"bornTimes",0,function(e,t){let n=new Map(e.map(e=>[e.id,e])),r=new Map,o=(e,l=0)=>{if(r.has(e.id))return r.get(e.id);let s=-1/0,a=t.get(e.id);if(1===e.degree||null==a||l>6){let t=e.connected_date?Date.parse(e.connected_date):NaN;s=Number.isNaN(t)?-1/0:t}return null!=a&&n.has(a)&&l<=6&&(s=Math.max(s,o(n.get(a),l+1))),r.set(e.id,s),s};return e.map(e=>o(e))},"chapterAt",0,function(e,t){let n=null;for(let r of e?.chapters||[])Number.isFinite(r.from)&&r.from<=t&&(!n||r.from>n.from)&&(n=r);return n&&(!Number.isFinite(n.to)||n.to>t)?n:null},"clockNow",0,()=>E,"colourScheme",0,function(e,n,r,o){if("degree"===e)return{of:e=>U[Math.min(e.degree||1,3)],legend:[["1st",U[1]],["2nd",U[2]],["3rd+",U[3]]]};if("company"===e){let e=new Map;for(let t of n){let n=G(t.company);if(!n)continue;let r=e.get(n)||{name:String(t.company).trim(),n:0};r.n+=1,e.set(n,r)}let t=[...e.entries()].filter(([,e])=>e.n>1).sort((e,t)=>t[1].n-e[1].n).slice(0,P.length),r=new Map(t.map(([e],t)=>[e,P[t]]));return{of:e=>r.get(G(e.company))||W,legend:[...t.map(([,e],t)=>[e.name,P[t]]),["Other",W]]}}if("heat"===e){let e=function(e=[]){return Y(e,t.score)}(n);return{of:t=>J(e(t)),heat:e,legend:[["Cold",J(.1)],["Warm",J(.45)],["Hot",J(.75)],["Hottest",J(1)]]}}return"warmth"===e&&o?{of:e=>1===(e.degree||1)?K[u(o.merged.get((0,t.keyFor)(e)),o.asOf)]:"#2d3748",legend:[["Warm",K.warm],["Cool",K.cool],["Dormant",K.dormant],["Never messaged",K.never]]}:{of:e=>r[e.tier]||"#666",legend:Object.entries(r)}},"effectiveLab",0,e=>e.on?e:e.labels===v.labels?v:{...v,labels:e.labels},"findMatches",0,function(e,t){let n=String(t||"").trim().toLowerCase();if(n.length<2)return null;let r=new Set;for(let t of e)[t.name,t.company,t.role,t.headline].some(e=>e&&String(e).toLowerCase().includes(n))&&r.add(t.id);return r},"forgetLayout",0,function(e){$(M().filter(t=>t.name!==e))},"heatBy",0,Y,"heatColour",0,J,"labNow",0,T,"layoutOf",0,function(e){let t=t=>Object.entries(t).every(([t,n])=>e[t]===n);return w.every(t=>e[t]===v[t])?"rings":t(S)?"clusters":t(j)?"orbit":null},"layoutsNow",0,M,"loadSocial",0,function(){return I||(I=fetch("/api/social").then(e=>e.ok?e.json():{}).then(e=>{L({social:e.social?R(e.social):null})}).catch(()=>L({social:null}))),I},"mergeSocial",0,R,"milestones",0,function(e){let t=[];for(let n of e?.chapters||[])Number.isFinite(n.from)&&t.push({t:n.from,kind:"job",label:n.company});for(let n of e?.posts||[])Number.isFinite(n.t)&&t.push({t:n.t,kind:"post",label:"You posted"});return t.sort((e,t)=>e.t-t.t)},"pause",0,D,"play",0,function e(){if(null==E.min||null==E.max||E.max<=E.min)return;let t=E.max-E.min,n=1e3*Math.max(1,Number(T().speed)||15),r=null==E.at||E.at>=E.max?E.min:E.at,o=performance.now()-(r-E.min)/t*n;L({at:r,playing:!0});let l=r=>{let s=Math.min(E.max,E.min+(r-o)/n*t);if(L({at:s}),s>=E.max){z=null,L({playing:!1}),T().loop&&setTimeout(()=>{z||E.at!==E.max||(L({at:E.min}),e())},1200);return}z=requestAnimationFrame(l)};cancelAnimationFrame(z),z=requestAnimationFrame(l)},"reachCounts",0,function(e,t){let n=new Map(e.map(e=>[e.id,0]));for(let r of e){let e=t.get(r.id);for(let r=0;null!=e&&r<7;r++)n.set(e,(n.get(e)||0)+1),e=t.get(e)}return n},"saveLayout",0,function(e){let t=String(e||"").trim().slice(0,40);if(!t)return;let n=T(),r=Object.fromEntries(p.map(e=>[e,n[e]]));$([...M().filter(e=>e.name!==t),{name:t,settings:r}])},"setClock",0,L,"setLab",0,F,"stopReplay",0,function(){D(),L({at:null})},"watchClock",0,function(e){return B.add(e),()=>B.delete(e)},"watchLab",0,function(e){C.add(e);let t=t=>{t.key===b&&(O=null,e())};return window.addEventListener("storage",t),()=>{C.delete(e),window.removeEventListener("storage",t)}},"watchLayouts",0,function(e){return N.add(e),()=>N.delete(e)}],39160)}]);
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,66 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.8.0] - 2026-10-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Insights: your network ranked by power.** A new tab between Paths and Outlink. The Power
|
|
13
|
+
Index ranks everyone within two steps of you, each person once, with shared ranks for ties and
|
|
14
|
+
a line saying why a big tie shares its score; filter by degree, tier and rarity, search by name,
|
|
15
|
+
company or who knows them, and from any row open their circle, scan it, or ask (their LinkedIn
|
|
16
|
+
profile opens and the request is marked sent, as on their card). Beside it: Untapped (S and A
|
|
17
|
+
in your 2nd degree with no request out), Kingmakers (the connections with the most S and A
|
|
18
|
+
behind them), Gatekeepers (who your S and A reach hangs on), Hidden giants (S-tier with one way
|
|
19
|
+
in), Company power, How sure (how much of the ranking rests on a guess), and Richest?, which
|
|
20
|
+
says plainly that the app never sees money. Kingmakers, Gatekeepers, Companies, Industries and
|
|
21
|
+
a scrolling Report are views of their own in the notch. Every number says where it came from;
|
|
22
|
+
a CSV import gets the boards that need no circles, and says so for the rest. On your own network every 2nd-degree row offers Auto beside Ask.
|
|
23
|
+
- **Auto: one press sends someone a connection request for you.** Next to every Connect (a person's
|
|
24
|
+
card, and Outlink's lists and next best moves) there's now an Auto button, the bright one. Press it
|
|
25
|
+
and the scanner's Chrome opens their profile, presses Connect and sends the request without a note,
|
|
26
|
+
then shows "Request sent" and marks it pending everywhere in the app. Connect still opens their
|
|
27
|
+
profile for you to do it yourself. One press is one person: never a batch, never on a timer, and it
|
|
28
|
+
waits while a scan runs. It asks once before the first one. When LinkedIn wants their email address
|
|
29
|
+
first, Auto closes that and sends nothing ("Use Connect to add them yourself"); when LinkedIn offers
|
|
30
|
+
a personal note (Premium), it sends without one. It only calls a request sent once their profile
|
|
31
|
+
shows it pending, and says so plainly when it can't tell. It stops at 15 requests in any 24 hours and
|
|
32
|
+
80 in any 7 days (LinkedIn doesn't publish its limit; about 100 a week is commonly reported), opens
|
|
33
|
+
their profile once like a scan does, holds during a pause after LinkedIn pushed back, and shows in
|
|
34
|
+
Settings → LinkedIn usage as Connection requests (Auto).
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- **A scan that stopped partway says so, on the person's card and on the Scan page.** Open someone
|
|
38
|
+
whose circle scan didn't finish and a line by their name says which page it stopped after, with
|
|
39
|
+
Resume right there; their scan box now reads Scan in progress, with Resume from page N as its main
|
|
40
|
+
button, instead of Cluster active. The card's Insights offer Resume for them too, not a scan that
|
|
41
|
+
reads the list again from page 1, and an empty circle in Bridge Chains whose scan stopped partway
|
|
42
|
+
says so, with Resume from page N, instead of saying their list has been read. On the Scan page, everyone stopped partway is in a new In progress
|
|
43
|
+
card right after step 4, with Resume for each person and Resume all, and each name opens their circle
|
|
44
|
+
on the map. It used to be folded away under Fine-tune the scanner.
|
|
45
|
+
- **The tabs sit on the header's line, and the notch hangs from them.** On a computer the row of
|
|
46
|
+
tabs rests on the line under the header, so a page's own buttons in the notch below join onto
|
|
47
|
+
it. The header is the same height on every tab: the Pending count that added a row on Degrees
|
|
48
|
+
and Separation is gone (sent requests are still under Outlink → Pending).
|
|
49
|
+
- **Separation starts with its slider.** The line counting people two steps away and the tier
|
|
50
|
+
strip under it are gone; the search box stays at the top.
|
|
51
|
+
- **The notch comes with the page.** Switching tabs, it waits for the next page to appear instead
|
|
52
|
+
of hanging over a blank screen while it loads.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
- **A company called "Foods" or "Beverages" is in Retail, Consumer & Hospitality.** The word list
|
|
56
|
+
had "food" and "beverage" but not the plurals, so a company like Northwind Foods took its
|
|
57
|
+
industry from its people's headlines instead (tech, on a test network). Scores are worked out
|
|
58
|
+
again once after the update, since a company's industry can change its estimate and your sector.
|
|
59
|
+
- **Scan dots are whole on Paths.** The dots along the header's line while a scan runs were cut in
|
|
60
|
+
half above Paths' left panel; they also stop short of the tabs now that the tabs sit on the line.
|
|
61
|
+
- **The 3rd-degree dot in Degrees and Separation says why it can't be tapped.** It said "No one at
|
|
62
|
+
3rd degree yet" even with people found by company scans; it now says those are drawn on Network
|
|
63
|
+
Circle.
|
|
64
|
+
- **The ring round a scanned connection's dot shows how much of their list was read.** It showed 2
|
|
65
|
+
of 5 bars, "partly read", for everyone scanned, even when their whole list was in, and the card's
|
|
66
|
+
Insights offered to finish lists that were finished. The app now takes in how far each list was
|
|
67
|
+
read, which the scanner had been noting all along.
|
|
68
|
+
|
|
9
69
|
## [0.7.0] - 2026-10-03
|
|
10
70
|
|
|
11
71
|
### Added
|
package/README.md
CHANGED
|
@@ -193,6 +193,16 @@ in, and its most powerful people.
|
|
|
193
193
|
|
|
194
194
|
<img src="docs/img/paths.png" alt="Paths → Map with the company analyzer open: companies grouped by industry, and one company's people by level and ways in" width="100%">
|
|
195
195
|
|
|
196
|
+
**Insights.** Everyone within two steps of you in one table, ranked by power, each person
|
|
197
|
+
once: the **Power Index**, with filters for degree, tier and rarity, and Open circle, Scan
|
|
198
|
+
circle or Ask on every row. Beside it, and each a board of its own in the notch:
|
|
199
|
+
**Kingmakers** (your connections with the most S and A people behind them), **Gatekeepers**
|
|
200
|
+
(who your S and A reach hangs on), **Hidden giants** (S-tier with one way in), **Untapped**
|
|
201
|
+
(S and A with no request out), **Company power** and **Industries**. **Report** is the same
|
|
202
|
+
numbers as a page you scroll. Every number says where it came from, and nothing is about
|
|
203
|
+
money: *Richest?* says plainly that the app never sees it. Power ranks reachability, not
|
|
204
|
+
people.
|
|
205
|
+
|
|
196
206
|
**Scores.** How a power score is worked out, and the three things you can change about
|
|
197
207
|
it, in one place: your field (the sectors you work in), how tiers are graded (on your
|
|
198
208
|
network's curve or the fixed scale), and every scanned company's score.
|
|
@@ -202,6 +212,15 @@ circle offers its best five people at a time; mark invites as sent to fill the r
|
|
|
202
212
|
unlock the next five. It also shows the next best moves, levels and points (from
|
|
203
213
|
invites sent and people who accepted, never from browsing).
|
|
204
214
|
|
|
215
|
+
**Auto** *(needs a scan)*. Next to every Connect (a person's card, Outlink), Auto sends
|
|
216
|
+
that one person a connection request for you, from the scanner's Chrome, without a note.
|
|
217
|
+
One press, one person: never a batch, never on a timer. It asks once before the first one.
|
|
218
|
+
If LinkedIn wants their email address first, Auto closes that and sends nothing (use
|
|
219
|
+
Connect to add them yourself); if it offers a personal note (Premium), Auto sends without
|
|
220
|
+
one. It calls a request sent only once their profile shows it pending. It stops at 15 in
|
|
221
|
+
any 24 hours and 80 in any 7 days, opens their profile once (a profile view), and counts in
|
|
222
|
+
Settings → LinkedIn usage.
|
|
223
|
+
|
|
205
224
|
**Scan.** The guided scanner. It shows progress, the budget that's left, the cooldown
|
|
206
225
|
lock, and a **Paused** list you can carry on from.
|
|
207
226
|
|
|
@@ -287,6 +306,10 @@ Something out of date, or unfair to another tool? [Open an issue](https://github
|
|
|
287
306
|
>
|
|
288
307
|
> The app asks for an explicit "I understand" once, before the first scan.
|
|
289
308
|
>
|
|
309
|
+
> **The scanner reads; the one thing it ever sends is a connection request, and only when
|
|
310
|
+
> you press Auto** on a person (one each press, without a note, at most 15 a day and 80 a
|
|
311
|
+
> week). Auto asks once before its first request.
|
|
312
|
+
>
|
|
290
313
|
> Six Degrees is not affiliated with or endorsed by LinkedIn.
|
|
291
314
|
|
|
292
315
|
## First run
|
|
@@ -395,7 +418,8 @@ It estimates **network position**, not what anyone is worth as a person.
|
|
|
395
418
|
data folder as `csv-network.json` (names, positions, companies, profile links and when
|
|
396
419
|
you connected; never email addresses) until you click × beside *Your CSV*.
|
|
397
420
|
- The app contacts only these, and only when you act:
|
|
398
|
-
- **LinkedIn**, while you scan
|
|
421
|
+
- **LinkedIn**, while you scan, and when you press **Auto** (one connection request,
|
|
422
|
+
sent from the scanner's Chrome). Each profile photo is saved on your computer as the
|
|
399
423
|
scan reads it, and the app shows photos only from there, so looking at your network
|
|
400
424
|
never contacts LinkedIn. (Photos an older version kept as links to LinkedIn show
|
|
401
425
|
initials until the next scan, or *Save photos* on the Scan page, saves them.)
|
package/SECURITY.md
CHANGED
|
@@ -129,6 +129,14 @@ Everything stays local:
|
|
|
129
129
|
If you run the scanner, `chrome-profile/` in the data directory holds a live
|
|
130
130
|
logged-in LinkedIn session. Never copy, sync, or commit it.
|
|
131
131
|
|
|
132
|
+
The scanner reads LinkedIn. The one thing it ever sends there is a connection request,
|
|
133
|
+
and only when you press **Auto** on a person: one request per press, without a note, at
|
|
134
|
+
most 15 in any 24 hours and 80 in any 7 days. The app looks the person up by the id the
|
|
135
|
+
page sends and passes the scanner their profile URL from the database, never a URL or name
|
|
136
|
+
from the request, and refuses before anything starts during a cooldown, past those caps,
|
|
137
|
+
or before Auto's one-time question has been answered (`app/api/scraper/route.js`,
|
|
138
|
+
`lib/auto-connect.js`).
|
|
139
|
+
|
|
132
140
|
### Moving your data (Settings → Your data)
|
|
133
141
|
|
|
134
142
|
- **An export** is one `.sixdegrees` file the user saves and carries. It is
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Auto: one connection request, sent for you from the scanner's Chrome.
|
|
2
|
+
//
|
|
3
|
+
// Blake, 2026-10-03: "auto add and basically adds the person for them in the
|
|
4
|
+
// card or wherever its available. now this will count in scans obviously for
|
|
5
|
+
// usage and one thing to prevent problems is that sometimes when adding it'll
|
|
6
|
+
// prompt the user to have a email from their work or to send a personal note
|
|
7
|
+
// if they have premium so if thats the case then have it close out of that in
|
|
8
|
+
// the scanner for adding but it should be seamless." Then: "i just want it next
|
|
9
|
+
// to connect button but say auto and makes it the better pick visually".
|
|
10
|
+
//
|
|
11
|
+
// That reverses the standing rule against automated LinkedIn actions, for this
|
|
12
|
+
// one action only: one press, one person. Never a batch, never on a timer. The
|
|
13
|
+
// scanner does the pressing (scripts/scrape.py --connect, connect_person); the
|
|
14
|
+
// app looks the person up by id and refuses before anything opens
|
|
15
|
+
// (app/api/scraper/route.js); this file holds what both sides must agree on:
|
|
16
|
+
// the caps, the result names the scanner prints, and the words the page shows.
|
|
17
|
+
//
|
|
18
|
+
// No Node imports: the page, the route and the tests all read it.
|
|
19
|
+
|
|
20
|
+
// Invitations in any 24 hours and in any 7 days (scripts/scrape.py
|
|
21
|
+
// INVITE_DAY_CAP, INVITE_WEEK_CAP; tests/auto-connect.test.mjs checks they
|
|
22
|
+
// match). LinkedIn doesn't publish its invitation limit; people commonly report
|
|
23
|
+
// about 100 a week, so the week stays well under that, and the day keeps a
|
|
24
|
+
// whole week from going in one sitting.
|
|
25
|
+
export const INVITE_CAPS = { day: 15, week: 80 };
|
|
26
|
+
|
|
27
|
+
// The weekly number people report, shown as a range people report, never as LinkedIn's.
|
|
28
|
+
export const REPORTED_WEEKLY_INVITES = 100;
|
|
29
|
+
|
|
30
|
+
export const AUTO_EXPLAIN = 'Sends the request for you from the scanner’s Chrome, without a note. Counts toward your LinkedIn usage.';
|
|
31
|
+
|
|
32
|
+
export const AUTO_CONFIRM = 'Auto sends connection requests from your LinkedIn account, one each time you press it. '
|
|
33
|
+
+ 'LinkedIn limits how many invitations an account can send, and may restrict accounts that send many.';
|
|
34
|
+
|
|
35
|
+
// The server's answer until the one-time yes is given (the page then asks it).
|
|
36
|
+
export const AUTO_REFUSAL = 'Auto asks once before its first request. Press Auto again and confirm.';
|
|
37
|
+
|
|
38
|
+
/** When Auto's one-time question was answered yes (ISO time), or null. Saved in Settings (lib/settings.js). */
|
|
39
|
+
export const AUTO_ACCEPTED_SETTING = {
|
|
40
|
+
default: null,
|
|
41
|
+
parse(value) {
|
|
42
|
+
if (value === null) return null;
|
|
43
|
+
if (typeof value !== 'string' || Number.isNaN(Date.parse(value))) {
|
|
44
|
+
throw new Error('When Auto was first allowed is a date and time.');
|
|
45
|
+
}
|
|
46
|
+
return new Date(value).toISOString();
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// The one line the scanner prints to say how a request went (scrape.py
|
|
51
|
+
// connect_result), read by the route as the job's log arrives.
|
|
52
|
+
export const CONNECT_RESULT = /^Connect result: ([a-z-]+)$/;
|
|
53
|
+
|
|
54
|
+
/** The result named on a log line, or null. */
|
|
55
|
+
export function connectResultIn(line) {
|
|
56
|
+
const m = String(line ?? '').trim().match(CONNECT_RESULT);
|
|
57
|
+
return m && Object.hasOwn(OUTCOMES, m[1]) ? m[1] : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const day = INVITE_CAPS.day;
|
|
61
|
+
const week = INVITE_CAPS.week;
|
|
62
|
+
|
|
63
|
+
// Every result the scanner can print (scrape.py CONNECT_RESULTS; the test
|
|
64
|
+
// checks both lists are the same). `marks`: the request is marked as sent here,
|
|
65
|
+
// everywhere at once. Only a send the profile then showed as Pending, or one
|
|
66
|
+
// that was already pending, does that; anything unclear does not (TRAPS §7).
|
|
67
|
+
export const OUTCOMES = {
|
|
68
|
+
sent: { ok: true, marks: true, text: 'Request sent' },
|
|
69
|
+
'already-pending': { ok: true, marks: true, text: 'A request to them was already pending on LinkedIn, so nothing new was sent. It’s marked as sent here.' },
|
|
70
|
+
'email-needed': { text: 'LinkedIn wants their email address before it sends this one. Use Connect to add them yourself.' },
|
|
71
|
+
'no-connect': { text: 'LinkedIn shows no Connect for them here, only Follow or Message, or a request is already out. Nothing was sent.' },
|
|
72
|
+
'not-sent': { text: 'LinkedIn asked something Auto doesn’t answer for you, so it closed that and sent nothing. Use Connect to add them yourself.' },
|
|
73
|
+
unclear: { text: 'Auto pressed Send but couldn’t see the request go through, so it isn’t marked as sent. Use Connect to check their profile.' },
|
|
74
|
+
'not-loaded': { text: 'Their profile didn’t load, so nothing was sent. Try again in a while.' },
|
|
75
|
+
unavailable: { text: 'LinkedIn says their profile isn’t available, so nothing was sent.' },
|
|
76
|
+
'linkedin-limit': { text: 'LinkedIn says this account has reached its invitation limit for now. Nothing was sent.' },
|
|
77
|
+
pushback: { text: 'LinkedIn pushed back, so scanning and Auto are paused for a day. Settings, LinkedIn usage says when it ends.' },
|
|
78
|
+
stopped: { text: 'Stopped before anything was sent.' },
|
|
79
|
+
'not-signed-in': { text: 'LinkedIn isn’t signed in in the scanner’s Chrome. Sign in on the Scan page (step 2), then try again.' },
|
|
80
|
+
cooldown: { text: 'Scanning is paused after LinkedIn pushed back, and Auto waits with it. Nothing was sent.' },
|
|
81
|
+
'invites-day': { text: `Auto has sent ${day} requests in the last 24 hours, its limit. Nothing was sent.` },
|
|
82
|
+
'invites-week': { text: `Auto has sent ${week} requests in the last 7 days, its limit. Nothing was sent.` },
|
|
83
|
+
profiles: { text: 'Today’s profile views are used, and Auto opens their profile to send. Nothing was sent.' },
|
|
84
|
+
'not-found': { text: 'That person isn’t in your network on this computer, so nothing was sent.' },
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const RESULT_NAMES = Object.keys(OUTCOMES);
|
|
88
|
+
|
|
89
|
+
/** Does this result mark the request as sent? */
|
|
90
|
+
export const connectMarks = (result) => Boolean(OUTCOMES[result]?.marks);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* What the page says once an Auto job has ended: { result, ok, marks, text }.
|
|
94
|
+
* `end` is how the job ended (lib/scraper-client.js runScrape): its `outcome`
|
|
95
|
+
* (the route's reading of the result line), else its failure, else a plain
|
|
96
|
+
* "couldn't finish". Never says sent without the scanner saying so.
|
|
97
|
+
*/
|
|
98
|
+
export function connectOutcome(end) {
|
|
99
|
+
const result = end?.outcome && Object.hasOwn(OUTCOMES, end.outcome) ? end.outcome : null;
|
|
100
|
+
if (result) {
|
|
101
|
+
const o = OUTCOMES[result];
|
|
102
|
+
return { result, ok: Boolean(o.ok), marks: Boolean(o.marks), text: o.text };
|
|
103
|
+
}
|
|
104
|
+
const why = Array.isArray(end?.failure) ? end.failure.filter(Boolean).slice(-2).join(' ') : '';
|
|
105
|
+
return {
|
|
106
|
+
result: null, ok: false, marks: false,
|
|
107
|
+
text: why ? `Auto couldn’t finish, and nothing is marked as sent: ${why}` : 'Auto couldn’t finish, and nothing is marked as sent.',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Why the route won't start an Auto request because of a cap, or null.
|
|
113
|
+
* `li` is lib/linkedin-limits.js linkedinState: invitesToday, invitesWeek,
|
|
114
|
+
* invitesFreeAt / invitesWeekFreeAt (ms) and profilesLeftToday. `when` turns a
|
|
115
|
+
* time into words.
|
|
116
|
+
*/
|
|
117
|
+
export function inviteRefusal(li, when = (ms) => new Date(ms).toLocaleString()) {
|
|
118
|
+
if (!li) return null;
|
|
119
|
+
if (li.unreadable) return 'The record of what Six Degrees has asked of LinkedIn couldn’t be read, so Auto waits until it can.';
|
|
120
|
+
if (li.invitesToday >= day) {
|
|
121
|
+
return `Auto has sent ${day} requests in the last 24 hours, its limit.${li.invitesFreeAt ? ` The next one frees up ${when(li.invitesFreeAt)}.` : ''}`;
|
|
122
|
+
}
|
|
123
|
+
if (li.invitesWeek >= week) {
|
|
124
|
+
return `Auto has sent ${week} requests in the last 7 days, its limit.${li.invitesWeekFreeAt ? ` The next one frees up ${when(li.invitesWeekFreeAt)}.` : ''}`;
|
|
125
|
+
}
|
|
126
|
+
if (li.profilesLeftToday === 0) {
|
|
127
|
+
return 'Today’s profile views are used, and Auto opens their profile to send the request. Try again once one frees up (Settings, LinkedIn usage).';
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Why this person can't be sent a request from here, or null when they can.
|
|
134
|
+
* `person` is their row; `connected` is true when they're one of your own
|
|
135
|
+
* connections (any copy of them at degree 1); `requested` when a request to
|
|
136
|
+
* them is already out (any copy marked sent or pending).
|
|
137
|
+
*/
|
|
138
|
+
export function personRefusal(person, { connected = false, requested = false, accepted = false } = {}) {
|
|
139
|
+
if (!person) return { status: 400, error: 'That person could not be found.' };
|
|
140
|
+
const name = person.name || 'They';
|
|
141
|
+
if (connected || accepted || person.degree === 1) return { status: 409, error: `${name} is already one of your connections.` };
|
|
142
|
+
if (requested) return { status: 409, error: `A request to ${name} is already out.` };
|
|
143
|
+
if (!person.profile_url) return { status: 400, error: `There is no LinkedIn profile on file for ${name}.` };
|
|
144
|
+
return null;
|
|
145
|
+
}
|
package/lib/companies.js
CHANGED
|
@@ -52,7 +52,7 @@ export const INDUSTRIES = [
|
|
|
52
52
|
{ key: 'realestate', label: 'Real Estate & Construction', color: '#d35400',
|
|
53
53
|
words: /\b(real estate|realtor|realty|property|properties|construction|architecture|architect|mortgage|homes|housing|builders?)\b/i },
|
|
54
54
|
{ key: 'consumer', label: 'Retail, Consumer & Hospitality', color: '#ff79c6',
|
|
55
|
-
words: /\b(retail|e-?commerce|shopify|consumer|cpg|fashion|apparel|beauty|cosmetics|
|
|
55
|
+
words: /\b(retail|e-?commerce|shopify|consumer|cpg|fashion|apparel|beauty|cosmetics|foods?|beverages?|restaurant|hospitality|hotel|travel|tourism|coca-cola|nike|store|shop)\b/i },
|
|
56
56
|
{ key: 'industry', label: 'Manufacturing, Energy & Logistics', color: '#95a5a6',
|
|
57
57
|
words: /\b(manufacturing|energy|oil|gas|solar|utilities|automotive|tesla|siemens|ge|general electric|honeywell|3m|logistics|supply chain|shipping|freight|fedex|ups|industrial|mining|chemicals?)\b/i },
|
|
58
58
|
{ key: 'nonprofit', label: 'Nonprofit & Community', color: '#bdc3c7',
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// What the app says about a circle scan that stopped partway. Blake,
|
|
2
|
+
// 2026-10-03: "if someone was scanned but not finished it needs to show that in
|
|
3
|
+
// their profile card if clicked showing resume scanner in that actual card as we
|
|
4
|
+
// need to have some sort of in progress ones in the scanner as well to see all
|
|
5
|
+
// the ones they stopped and would like to resume".
|
|
6
|
+
//
|
|
7
|
+
// The person card (app/components/Sidebar.js) and the Scan page's In progress
|
|
8
|
+
// card (app/components/LinkedInLimits.js PausedList) both word it from here, so
|
|
9
|
+
// the two never say it differently. Who is stopped partway, and where Resume
|
|
10
|
+
// carries on, is lib/paused.js's to decide; this only says it. No Node imports:
|
|
11
|
+
// the browser loads it.
|
|
12
|
+
|
|
13
|
+
/** The scan box's heading on a person card: a read that stopped partway says so first. */
|
|
14
|
+
export function scanBoxTitle({ resume, hasCluster }) {
|
|
15
|
+
if (resume) return 'SCAN IN PROGRESS';
|
|
16
|
+
return hasCluster ? 'CLUSTER ACTIVE' : 'CREATE CLUSTER';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The line by their name on the card, from GET /api/scraper?resume=<id>
|
|
21
|
+
* ({ nextPage, pagesRead, legacy }). null when there is nothing to carry on with.
|
|
22
|
+
*/
|
|
23
|
+
export function stoppedLine(resume) {
|
|
24
|
+
if (!resume) return null;
|
|
25
|
+
const read = Number(resume.pagesRead) || 0;
|
|
26
|
+
return read > 0 ? `Scan stopped after page ${read}` : 'Scan stopped before its first page';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The In progress card's line on the Scan page, from the status answer's
|
|
31
|
+
* `paused` (lib/paused.js pausedList). null when nobody is. People mapped
|
|
32
|
+
* before 0.1.6 kept notes (`legacy`) were read to page 10 and no further, which
|
|
33
|
+
* is why they're here at all, so it says so.
|
|
34
|
+
*/
|
|
35
|
+
export function inProgressSummary(paused = []) {
|
|
36
|
+
const list = Array.isArray(paused) ? paused : [];
|
|
37
|
+
const n = list.length;
|
|
38
|
+
if (!n) return null;
|
|
39
|
+
const legacy = list.filter((p) => p?.legacy).length;
|
|
40
|
+
const line = n === 1
|
|
41
|
+
? '1 person stopped partway. Resume carries on from the page they stopped at.'
|
|
42
|
+
: `${n} people stopped partway. Resume carries on from the page each one stopped at.`;
|
|
43
|
+
if (!legacy) return line;
|
|
44
|
+
const old = n === 1 ? 'Their list was' : legacy === 1 ? '1 of them was' : `${legacy} of them were`;
|
|
45
|
+
return `${line} ${old} read to page 10, before whole lists were read.`;
|
|
46
|
+
}
|