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