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
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
// and the scanner also keeps any two a minute apart. Searches did too, at 373 in
|
|
14
14
|
// 24 hours: the page asks before a risky search budget (lib/search-risk.js).
|
|
15
15
|
//
|
|
16
|
+
// Auto's connection requests (lib/auto-connect.js) are a third list in the same
|
|
17
|
+
// record, "invites", with caps of their own: 15 in any 24 hours, 80 in any 7 days.
|
|
18
|
+
//
|
|
16
19
|
// They belong to the LinkedIn account, not to a profile in the app. TRAPS §35.
|
|
17
20
|
|
|
18
21
|
import {
|
|
@@ -22,12 +25,14 @@ import path from 'node:path';
|
|
|
22
25
|
import { durable } from './durable.js';
|
|
23
26
|
import { PACES, DEFAULT_PACE } from './scan-pace.js';
|
|
24
27
|
import { countAfter } from './usage.js';
|
|
28
|
+
import { INVITE_CAPS } from './auto-connect.js';
|
|
25
29
|
|
|
26
30
|
export const DEFAULT_LIMITS = { daily: 50, monthly: 250, profiles: 50, pace: DEFAULT_PACE };
|
|
27
31
|
export const DAILY_CHOICES = [25, 50, 100, 200, 500];
|
|
28
32
|
export const MONTHLY_CHOICES = [100, 250, 500, 1000, 0]; // 0 = no monthly cap (Premium)
|
|
29
33
|
export const PROFILE_CHOICES = [10, 25, 50, 100]; // profile views a day; never unlimited
|
|
30
34
|
const DAY = 24 * 3600;
|
|
35
|
+
const WEEK = 7 * DAY;
|
|
31
36
|
const PACIFIC = 'America/Los_Angeles';
|
|
32
37
|
|
|
33
38
|
function readJson(file) {
|
|
@@ -102,17 +107,16 @@ export function writeLimits(dir, { daily, monthly, profiles, pace } = {}) {
|
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
/**
|
|
105
|
-
* The scanner's record as
|
|
106
|
-
*
|
|
110
|
+
* The scanner's record as three lists of times (epoch seconds): searches,
|
|
111
|
+
* profile views and Auto's connection requests. null when it can't be read. No
|
|
112
|
+
* record at all is nothing done yet.
|
|
107
113
|
*/
|
|
108
114
|
function readActivity(dir) {
|
|
109
115
|
const file = path.join(dir, 'linkedin-activity.json');
|
|
110
116
|
const data = existsSync(file) ? readJson(file) : { searches: [], profiles: [] };
|
|
111
117
|
if (!data || typeof data !== 'object') return null;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
profiles: (Array.isArray(data.profiles) ? data.profiles : []).map(Number),
|
|
115
|
-
};
|
|
118
|
+
const times = (v) => (Array.isArray(v) ? v : []).map(Number);
|
|
119
|
+
return { searches: times(data.searches), profiles: times(data.profiles), invites: times(data.invites) };
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
/**
|
|
@@ -123,14 +127,19 @@ function readActivity(dir) {
|
|
|
123
127
|
*/
|
|
124
128
|
export function usage(dir, now = Date.now()) {
|
|
125
129
|
const rec = readActivity(dir);
|
|
126
|
-
if (!rec)
|
|
127
|
-
|
|
130
|
+
if (!rec) {
|
|
131
|
+
return { searchesToday: Infinity, searchesMonth: 0, profilesToday: Infinity, invitesToday: Infinity, invitesWeek: Infinity, unreadable: true };
|
|
132
|
+
}
|
|
133
|
+
const { searches: s, profiles: p, invites: i } = rec;
|
|
128
134
|
const dayAgo = now / 1000 - DAY;
|
|
129
135
|
const month0 = monthStartPacific(now) / 1000;
|
|
130
136
|
return {
|
|
131
137
|
searchesToday: s.filter((t) => t > dayAgo).length,
|
|
132
138
|
searchesMonth: s.filter((t) => t >= month0).length,
|
|
133
139
|
profilesToday: p.filter((t) => t > dayAgo).length,
|
|
140
|
+
// Auto's connection requests (lib/auto-connect.js), against their own caps.
|
|
141
|
+
invitesToday: i.filter((t) => t > dayAgo).length,
|
|
142
|
+
invitesWeek: i.filter((t) => t > now / 1000 - WEEK).length,
|
|
134
143
|
};
|
|
135
144
|
}
|
|
136
145
|
|
|
@@ -177,12 +186,18 @@ export function linkedinState(dir, now = Date.now()) {
|
|
|
177
186
|
const limits = readLimits(dir);
|
|
178
187
|
const use = usage(dir, now);
|
|
179
188
|
const left = (limit, used) => (limit ? Math.max(0, limit - used) : null);
|
|
189
|
+
// When Auto may send again once a cap is reached: the request that stops
|
|
190
|
+
// counting soonest frees the next (freesAt below).
|
|
191
|
+
const invites = use.unreadable ? null : readActivity(dir)?.invites || [];
|
|
180
192
|
return {
|
|
181
193
|
...use,
|
|
182
194
|
limits,
|
|
183
195
|
leftToday: left(limits.daily, use.searchesToday),
|
|
184
196
|
leftMonth: left(limits.monthly, use.searchesMonth),
|
|
185
197
|
profilesLeftToday: left(limits.profiles, use.profilesToday),
|
|
198
|
+
inviteCaps: INVITE_CAPS,
|
|
199
|
+
invitesFreeAt: invites ? freesAt(invites, now, INVITE_CAPS.day) : null,
|
|
200
|
+
invitesWeekFreeAt: invites ? freesAt(invites, now, INVITE_CAPS.week, WEEK) : null,
|
|
186
201
|
monthResets: nextMonthStartPacific(now),
|
|
187
202
|
cooldown: readCooldown(dir, now),
|
|
188
203
|
};
|
|
@@ -325,6 +340,9 @@ export function linkedinUsage(dir, now = Date.now()) {
|
|
|
325
340
|
now,
|
|
326
341
|
searchesToday: rec ? state.searchesToday : null,
|
|
327
342
|
profilesToday: rec ? state.profilesToday : null,
|
|
343
|
+
// Auto's connection requests: the last 24 hours and the last 7 days, against INVITE_CAPS.
|
|
344
|
+
invitesToday: rec ? state.invitesToday : null,
|
|
345
|
+
invitesWeek: rec ? state.invitesWeek : null,
|
|
328
346
|
searchesMonth: rec ? state.searchesMonth : null,
|
|
329
347
|
leftMonth: rec ? state.leftMonth : null,
|
|
330
348
|
searchesLastHour: known((r) => countWithin(r.searches, now, 3600)),
|
|
@@ -363,7 +381,7 @@ const isTime = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0;
|
|
|
363
381
|
export function budgetFileProblem(name, data) {
|
|
364
382
|
if (!isObject(data)) return 'it is not a JSON object';
|
|
365
383
|
if (name === ACTIVITY) {
|
|
366
|
-
if (!isTimeList(data.searches) || !isTimeList(data.profiles)) return 'its record of searches is not a list of times';
|
|
384
|
+
if (!isTimeList(data.searches) || !isTimeList(data.profiles) || !isTimeList(data.invites)) return 'its record of searches is not a list of times';
|
|
367
385
|
} else if (name === COOLDOWN) {
|
|
368
386
|
if (!isTime(data.until)) return 'its pause has no end time';
|
|
369
387
|
if (data.reason !== undefined && typeof data.reason !== 'string') return 'its pause has a reason that is not text';
|
|
@@ -415,7 +433,8 @@ const hasFile = (p) => {
|
|
|
415
433
|
/**
|
|
416
434
|
* Merge the budget files in `from` (an import's, already checked with
|
|
417
435
|
* budgetFileProblem) into the data folder `dir`:
|
|
418
|
-
* linkedin-activity.json both computers' searches
|
|
436
|
+
* linkedin-activity.json both computers' searches, profile views and Auto's
|
|
437
|
+
* connection requests (mergeTimes)
|
|
419
438
|
* linkedin-cooldown.json whichever pause ends later
|
|
420
439
|
* scan-limits.json this computer's, if it has one; the copy's otherwise.
|
|
421
440
|
* The limits are a choice someone made here, and an
|
|
@@ -437,10 +456,16 @@ export function mergeBudgetFiles({ dir, from }) {
|
|
|
437
456
|
const mine = hasFile(here(ACTIVITY)) ? readJson(here(ACTIVITY)) : {};
|
|
438
457
|
const searches = isObject(mine) ? readableTimes(mine.searches) : null;
|
|
439
458
|
const profiles = isObject(mine) ? readableTimes(mine.profiles) : null;
|
|
440
|
-
|
|
459
|
+
const invites = isObject(mine) ? readableTimes(mine.invites) : null;
|
|
460
|
+
if (searches && profiles && invites) {
|
|
461
|
+
// Auto's requests too, or a copy brought in would forget the ones sent here
|
|
462
|
+
// (and Auto's caps would start again from nothing). Written only when
|
|
463
|
+
// either side has any, so a record from before Auto keeps its old layout.
|
|
464
|
+
const sent = mergeTimes(invites, activity.invites);
|
|
441
465
|
durable.writeJsonDurably(here(ACTIVITY), {
|
|
442
466
|
searches: mergeTimes(searches, activity.searches),
|
|
443
467
|
profiles: mergeTimes(profiles, activity.profiles),
|
|
468
|
+
...(sent.length ? { invites: sent } : {}),
|
|
444
469
|
});
|
|
445
470
|
}
|
|
446
471
|
}
|
|
@@ -96,6 +96,16 @@ export async function markRequested(row, { bridgeId } = {}) {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* A request the app has already saved, shown everywhere at once with nothing
|
|
101
|
+
* posted: Auto's, which the server marks itself once LinkedIn shows it pending
|
|
102
|
+
* (app/api/scraper/route.js, lib/requests.js).
|
|
103
|
+
*/
|
|
104
|
+
export function sawRequested(row) {
|
|
105
|
+
mine.set(keyFor(row), true);
|
|
106
|
+
publish();
|
|
107
|
+
}
|
|
108
|
+
|
|
99
109
|
/** Take a request back, everywhere. */
|
|
100
110
|
export async function undoRequest(row) {
|
|
101
111
|
const key = keyFor(row);
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Requests you've sent, kept per PERSON: the server side of
|
|
2
|
+
// lib/requests-client.js.
|
|
3
|
+
//
|
|
4
|
+
// A person has one row per bridge whose circle holds them (the table's key is
|
|
5
|
+
// profile + bridge + user), and a request used to mark only the copy that was
|
|
6
|
+
// clicked. The other copies still offered "Add", the Pending count counted
|
|
7
|
+
// copies, and every click awarded XP again. Now a request marks every copy of
|
|
8
|
+
// the person, in both columns, the bridge you asked through is kept (so once
|
|
9
|
+
// they accept, their card and circle say who introduced you: lib/promote.js
|
|
10
|
+
// keeps it), XP comes once per person, and Undo takes back all of it.
|
|
11
|
+
//
|
|
12
|
+
// Two callers: POST /api/outreach (a Connect pressed on a page), and the
|
|
13
|
+
// scanner's route once Auto has sent a request and LinkedIn showed it pending
|
|
14
|
+
// (app/api/scraper/route.js; Blake, 2026-10-03). Both go through markSent here,
|
|
15
|
+
// so a request sent by Auto is the same request everywhere as one sent by hand.
|
|
16
|
+
|
|
17
|
+
import { db as supabase } from './db.js';
|
|
18
|
+
import { resolveProfile } from './profile.js';
|
|
19
|
+
|
|
20
|
+
// XP rewards by tier
|
|
21
|
+
export const XP_SEND = { S: 25, A: 15, B: 10, C: 5, D: 2 };
|
|
22
|
+
export const XP_ACCEPT = { S: 100, A: 60, B: 35, C: 15, D: 5 };
|
|
23
|
+
const TIER_ORDER = ['S', 'A', 'B', 'C', 'D'];
|
|
24
|
+
|
|
25
|
+
export async function awardXP(userId, amount) {
|
|
26
|
+
if (!userId || !amount) return;
|
|
27
|
+
// Upsert: create if not exists, change if exists. Never below zero: an Undo
|
|
28
|
+
// takes back what a send gave, including sends from before sends gave any.
|
|
29
|
+
const { data: existing } = await supabase.from('user_stats').select('xp').eq('id', userId).single();
|
|
30
|
+
if (existing) {
|
|
31
|
+
await supabase.from('user_stats').update({ xp: Math.max(0, (existing.xp || 0) + amount) }).eq('id', userId);
|
|
32
|
+
} else if (amount > 0) {
|
|
33
|
+
await supabase.from('user_stats').insert([{ id: userId, xp: amount, level: 1 }]).catch(() => {});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The profile a request belongs to: the one sent, else this machine's. */
|
|
38
|
+
export function whose(userId) {
|
|
39
|
+
if (userId) return String(userId);
|
|
40
|
+
try { return resolveProfile({ create: false })?.id || null; } catch { return null; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Every copy of one person: by the clicked row's profile URL, or the URL given. */
|
|
44
|
+
export function copiesOf(db, { connectionId, profileUrl, userId }) {
|
|
45
|
+
let url = profileUrl ? String(profileUrl) : null;
|
|
46
|
+
let user = userId ? String(userId) : null;
|
|
47
|
+
let clicked = null;
|
|
48
|
+
if (connectionId != null) {
|
|
49
|
+
clicked = db.prepare('SELECT id, profile_url, user_id FROM linkedin_connections WHERE id = ?').get(String(connectionId)) || null;
|
|
50
|
+
if (clicked) {
|
|
51
|
+
url = clicked.profile_url || url;
|
|
52
|
+
user = user || clicked.user_id || null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const cols = 'id, degree, tier, outreach_status, unlock_status, unlocked_from_bridge_id';
|
|
56
|
+
let rows = [];
|
|
57
|
+
if (url) {
|
|
58
|
+
rows = user
|
|
59
|
+
? db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE profile_url = ? AND user_id = ?`).all(url, user)
|
|
60
|
+
: db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE profile_url = ?`).all(url);
|
|
61
|
+
} else if (clicked) {
|
|
62
|
+
rows = db.prepare(`SELECT ${cols} FROM linkedin_connections WHERE id = ?`).all(clicked.id);
|
|
63
|
+
}
|
|
64
|
+
return { url, user, rows };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const inList = (ids) => ids.map(() => '?').join(', ');
|
|
68
|
+
export const bestTier = (rows) => TIER_ORDER.find((t) => rows.some((r) => r.tier === t)) || 'D';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Mark a request to this person as sent, on every copy of them, through
|
|
72
|
+
* `bridgeId` (one of your own connections) when given: the rows only, at once.
|
|
73
|
+
* Returns { saved, xp, already, user }: `saved` is how many rows (0 when nobody
|
|
74
|
+
* is on file, as for the sample network, which lives in the browser), `xp` what
|
|
75
|
+
* the send is worth (once per person), for markSent or the caller to award.
|
|
76
|
+
*/
|
|
77
|
+
export function markSentRows(db, { connectionId, profileUrl, userId, bridgeId } = {}) {
|
|
78
|
+
const { user, rows } = copiesOf(db, { connectionId, profileUrl, userId });
|
|
79
|
+
if (!rows.length) return { saved: 0, xp: 0, already: false, user };
|
|
80
|
+
const ids = rows.map((r) => r.id);
|
|
81
|
+
const now = new Date().toISOString();
|
|
82
|
+
const already = rows.some((r) => r.outreach_status === 'sent' || r.outreach_status === 'accepted');
|
|
83
|
+
db.prepare(`UPDATE linkedin_connections SET outreach_status = 'sent', updated_at = ?
|
|
84
|
+
WHERE id IN (${inList(ids)}) AND (outreach_status IS NULL OR outreach_status NOT IN ('sent', 'accepted'))`).run(now, ...ids);
|
|
85
|
+
db.prepare(`UPDATE linkedin_connections SET unlock_status = 'pending', updated_at = ?
|
|
86
|
+
WHERE id IN (${inList(ids)}) AND (unlock_status IS NULL OR unlock_status = 'locked')`).run(now, ...ids);
|
|
87
|
+
// Who you asked through: one of your own connections, or nothing.
|
|
88
|
+
if (bridgeId != null) {
|
|
89
|
+
const bridge = db.prepare(
|
|
90
|
+
`SELECT id, name FROM linkedin_connections WHERE id = ? AND degree = 1${user ? ' AND user_id = ?' : ''}`,
|
|
91
|
+
).get(...[String(bridgeId), ...(user ? [user] : [])]);
|
|
92
|
+
if (bridge) {
|
|
93
|
+
db.prepare(`UPDATE linkedin_connections SET unlocked_from_bridge_id = ?, unlocked_from_name = ?, updated_at = ?
|
|
94
|
+
WHERE id IN (${inList(ids)}) AND degree = 2 AND unlocked_from_bridge_id IS NULL`).run(bridge.id, bridge.name, now, ...ids);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const xp = already ? 0 : XP_SEND[bestTier(rows)] || 2;
|
|
98
|
+
return { saved: ids.length, xp, already, user };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** markSentRows, and its XP awarded: { saved, xp, already }. */
|
|
102
|
+
export async function markSent(db, request = {}) {
|
|
103
|
+
const { saved, xp, already, user } = markSentRows(db, request);
|
|
104
|
+
if (saved) await awardXP(whose(user), xp);
|
|
105
|
+
return { saved, xp, already };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Where this person stands for a new request, over every copy of them:
|
|
110
|
+
* { connected, requested, accepted }. `connected`: a copy of them is one of
|
|
111
|
+
* your own connections; `requested`: a request is out (sent or pending);
|
|
112
|
+
* `accepted`: one was accepted (or their path is unlocked).
|
|
113
|
+
*/
|
|
114
|
+
export function requestStanding(db, { profileUrl, userId }) {
|
|
115
|
+
if (!profileUrl) return { connected: false, requested: false, accepted: false };
|
|
116
|
+
const rows = userId
|
|
117
|
+
? db.prepare('SELECT degree, outreach_status, unlock_status FROM linkedin_connections WHERE profile_url = ? AND user_id = ?').all(String(profileUrl), String(userId))
|
|
118
|
+
: db.prepare('SELECT degree, outreach_status, unlock_status FROM linkedin_connections WHERE profile_url = ?').all(String(profileUrl));
|
|
119
|
+
return {
|
|
120
|
+
connected: rows.some((r) => r.degree === 1),
|
|
121
|
+
requested: rows.some((r) => r.outreach_status === 'sent' || r.unlock_status === 'pending'),
|
|
122
|
+
accepted: rows.some((r) => r.outreach_status === 'accepted' || r.unlock_status === 'unlocked'),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -41,6 +41,7 @@ export function runningNow(job) {
|
|
|
41
41
|
case 'setup': return 'The scanner is being set up';
|
|
42
42
|
case 'login': return 'The LinkedIn sign-in window is open';
|
|
43
43
|
case 'photos': return 'Profile photos are being saved';
|
|
44
|
+
case 'connect': return 'A connection request is being sent';
|
|
44
45
|
default: return 'A scan is running';
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// What the Scan page's first step says and offers, from the checks GET
|
|
2
2
|
// /api/scraper reports (app/api/scraper/route.js, lib/scanner-python.js),
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// (
|
|
3
|
+
// its optional item on a Mac about macOS's App Management question
|
|
4
|
+
// (appManagementStep), whether it asks for your field once your connections
|
|
5
|
+
// are in (askForField), and two answers the server gives that step: is Google
|
|
6
|
+
// Chrome here (chromeInstalled), and are you signed in to LinkedIn (signedInFrom).
|
|
6
7
|
// Plain data in, plain data out: the page draws it, and
|
|
7
8
|
// tests/scanner-setup.test.mjs checks every state without a browser. No Node
|
|
8
9
|
// imports here; the page runs in the browser.
|
|
@@ -178,6 +179,84 @@ function stepOnly(status) {
|
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
181
|
|
|
182
|
+
// ── macOS's App Management question ─────────────────────────────────────────
|
|
183
|
+
// The scanner starts Google Chrome, and Chrome's own updater may then try to
|
|
184
|
+
// update /Applications/Google Chrome.app. macOS puts that down to the app that
|
|
185
|
+
// started Chrome and asks whether Six Degrees may manage apps (System Settings
|
|
186
|
+
// → Privacy & Security → App Management). Saying no is harmless: Chrome updates
|
|
187
|
+
// itself the next time you open it, and scanning works either way. Now that
|
|
188
|
+
// the Mac app is signed, a yes lasts across updates (unsigned copies lost it
|
|
189
|
+
// every update). "shouldnt we have in the onboarding for scan … a button where
|
|
190
|
+
// the user is basically brought to the app management and enables it like
|
|
191
|
+
// Flow does" (Blake, 2026-10-04). So step 1 offers the pane, once, and never
|
|
192
|
+
// waits for it: the app can't read whether it was allowed, and it's optional.
|
|
193
|
+
|
|
194
|
+
// The pane's address, as macOS 26 has it (checked on 26.5.1; macOS 15 knows it
|
|
195
|
+
// too), and as macOS 13 and 14 have it. desktop/lib.mjs SETTINGS_URLS lets
|
|
196
|
+
// exactly these two out of the Mac app; tests/desktop.test.mjs keeps them equal.
|
|
197
|
+
export const APP_MANAGEMENT_URL = 'x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_AppBundles';
|
|
198
|
+
export const APP_MANAGEMENT_URL_OLDER = 'x-apple.systempreferences:com.apple.preference.security?Privacy_AppBundles';
|
|
199
|
+
export const APP_MANAGEMENT_URLS = Object.freeze([APP_MANAGEMENT_URL, APP_MANAGEMENT_URL_OLDER]);
|
|
200
|
+
export const APP_MANAGEMENT_TITLE = 'Let Six Degrees open Chrome without macOS asking';
|
|
201
|
+
export const APP_MANAGEMENT_WHERE = 'System Settings → Privacy & Security → App Management';
|
|
202
|
+
|
|
203
|
+
/** The Scan page's App Management item, once answered: 'done' or 'skipped' (null: not yet). */
|
|
204
|
+
export const APP_MANAGEMENT_SETTING = {
|
|
205
|
+
default: null,
|
|
206
|
+
parse(value) {
|
|
207
|
+
if (value === null || value === 'done' || value === 'skipped') return value;
|
|
208
|
+
throw new Error('The App Management answer is done, skipped or null.');
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* macOS's major version from the Darwin kernel's (os.release() on the
|
|
214
|
+
* server): Darwin 25 is macOS 26, when the numbers jumped to the year, and 20
|
|
215
|
+
* to 24 are macOS 11 to 15. null off a Mac, or when it can't be read.
|
|
216
|
+
*/
|
|
217
|
+
export function macosVersion(platform, release) {
|
|
218
|
+
if (platform !== 'darwin') return null;
|
|
219
|
+
const darwin = Number.parseInt(String(release ?? ''), 10);
|
|
220
|
+
if (!Number.isInteger(darwin) || darwin < 1) return null;
|
|
221
|
+
if (darwin >= 25) return darwin + 1;
|
|
222
|
+
if (darwin >= 20) return darwin - 9;
|
|
223
|
+
return 10; // Catalina and before were all 10.x
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Step 1's optional item about App Management: { title, text, href, where }
|
|
228
|
+
* to show it, false not to, null while that isn't known yet (the page leaves
|
|
229
|
+
* it out until it is, as with askForField).
|
|
230
|
+
*
|
|
231
|
+
* Only on a Mac (`status.checks.mac`: { version, app }, null elsewhere and
|
|
232
|
+
* from an older server), and from macOS 13, which brought App Management.
|
|
233
|
+
* Never once answered, Done or Skip (`settings.appManagement`), and not when
|
|
234
|
+
* settings couldn't be read, since the answer couldn't be kept either. The
|
|
235
|
+
* button opens the pane for that macOS (an unknown version gets the newest
|
|
236
|
+
* address), and `where` says where it is by hand.
|
|
237
|
+
*
|
|
238
|
+
* In the Mac app macOS asks about Six Degrees. Started with npx, Chrome's
|
|
239
|
+
* starter is the app Six Degrees was started from (Terminal, for example), and
|
|
240
|
+
* that is the one macOS names.
|
|
241
|
+
*/
|
|
242
|
+
export function appManagementStep(status, settings) {
|
|
243
|
+
if (!status) return null;
|
|
244
|
+
const mac = status.checks?.mac;
|
|
245
|
+
if (!mac || (mac.version != null && mac.version < 13)) return false;
|
|
246
|
+
if (settings === undefined) return null;
|
|
247
|
+
if (!settings || settings.appManagement) return false;
|
|
248
|
+
const asked = mac.app ? 'Six Degrees' : 'the app you started Six Degrees from (Terminal, for example)';
|
|
249
|
+
const allow = mac.app ? 'Allow Six Degrees' : 'Allow that app';
|
|
250
|
+
return {
|
|
251
|
+
title: APP_MANAGEMENT_TITLE,
|
|
252
|
+
text: `When the scanner starts Google Chrome, macOS may ask whether ${asked} can manage apps. `
|
|
253
|
+
+ 'That’s Chrome updating itself while the scanner uses it. '
|
|
254
|
+
+ `${allow} once in App Management and macOS won’t ask again. Scanning works either way.`,
|
|
255
|
+
href: mac.version != null && mac.version < 15 ? APP_MANAGEMENT_URL_OLDER : APP_MANAGEMENT_URL,
|
|
256
|
+
where: APP_MANAGEMENT_WHERE,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
181
260
|
/**
|
|
182
261
|
* Whether the Scan page asks for your field (app/components/FieldStep.js):
|
|
183
262
|
* once, when your own connections are in (step 3) and before who they know is
|
|
@@ -39,7 +39,13 @@ export async function startScrape(action, name, extra = {}) {
|
|
|
39
39
|
body: JSON.stringify(scanRequest(action, { ...(name ? { name } : {}), ...extra })),
|
|
40
40
|
});
|
|
41
41
|
const d = await r.json().catch(() => ({}));
|
|
42
|
-
if (!r.ok)
|
|
42
|
+
if (!r.ok) {
|
|
43
|
+
// The answer's flags travel with the error (needsAutoAcceptance, say), so a
|
|
44
|
+
// button can tell a question to ask from a reason to show.
|
|
45
|
+
const err = new Error(d.error || 'Could not start the scanner.');
|
|
46
|
+
err.details = d;
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
43
49
|
return d;
|
|
44
50
|
}
|
|
45
51
|
|
|
@@ -55,12 +61,13 @@ export async function resumePoint(id) {
|
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
/** Nothing noted: before the first answer, and for the sample or a CSV. */
|
|
58
|
-
export const NO_SCAN_NOTES = Object.freeze({ skips: [], read: [] });
|
|
64
|
+
export const NO_SCAN_NOTES = Object.freeze({ skips: [], read: [], lists: {} });
|
|
59
65
|
|
|
60
66
|
/**
|
|
61
67
|
* What the scanner noted, for lib/reach.js: `skips`, the hidden lists
|
|
62
|
-
* ([{ profileUrl, … }]),
|
|
63
|
-
* read
|
|
68
|
+
* ([{ profileUrl, … }]), `read`, the profile URLs of every list it has
|
|
69
|
+
* read, and `lists`, how far each was read ({ url: { pages, more, total } }).
|
|
70
|
+
* Never throws: with no answer (the app restarting, say) nothing is
|
|
64
71
|
* noted, so a hidden list shows as ready rather than anyone going missing.
|
|
65
72
|
*/
|
|
66
73
|
export async function loadScanNotes() {
|
|
@@ -68,7 +75,11 @@ export async function loadScanNotes() {
|
|
|
68
75
|
const r = await fetch('/api/scraper?reach=1');
|
|
69
76
|
if (!r.ok) return NO_SCAN_NOTES;
|
|
70
77
|
const d = await r.json();
|
|
71
|
-
|
|
78
|
+
// `lists` is how far each list was read, for the rings round a dot
|
|
79
|
+
// (lib/reach.js scanBars). Dropped here until 10/3, every scanned
|
|
80
|
+
// connection drew 2 of 5 bars, "partly read", even with the whole list in.
|
|
81
|
+
const lists = d.lists && typeof d.lists === 'object' && !Array.isArray(d.lists) ? d.lists : {};
|
|
82
|
+
return { skips: Array.isArray(d.skips) ? d.skips : [], read: Array.isArray(d.read) ? d.read : [], lists };
|
|
72
83
|
} catch {
|
|
73
84
|
return NO_SCAN_NOTES;
|
|
74
85
|
}
|
|
@@ -314,6 +325,8 @@ export function busyReason(job = now) {
|
|
|
314
325
|
case 'full':
|
|
315
326
|
case 'refresh':
|
|
316
327
|
return 'Your own connections are being scanned';
|
|
328
|
+
case 'connect':
|
|
329
|
+
return who ? `A request to ${who} is being sent` : 'A connection request is being sent';
|
|
317
330
|
default:
|
|
318
331
|
return runningNow(job.action);
|
|
319
332
|
}
|
|
@@ -14,8 +14,10 @@ import { SECTOR_FOCUS_SETTING, FIELD_ASKED_SETTING } from './sector-focus.js';
|
|
|
14
14
|
import { TIER_SCALE_SETTING } from './scoring.js';
|
|
15
15
|
import { TITLE_RANKING_SETTING } from './title-ranking.js';
|
|
16
16
|
import { SCAN_RISK_SETTING } from './scan-risk.js';
|
|
17
|
+
import { AUTO_ACCEPTED_SETTING } from './auto-connect.js';
|
|
17
18
|
import { THEME_SETTING } from './themes.js';
|
|
18
19
|
import { GALAXY_LAYOUTS_SETTING } from './galaxy-layouts.js';
|
|
20
|
+
import { APP_MANAGEMENT_SETTING } from './scanner-setup.js';
|
|
19
21
|
|
|
20
22
|
export const SETTINGS = {
|
|
21
23
|
// Scores → Your sector: industries whose companies count for more (lib/sector-focus.js).
|
|
@@ -28,6 +30,10 @@ export const SETTINGS = {
|
|
|
28
30
|
titleRanking: TITLE_RANKING_SETTING,
|
|
29
31
|
// The Scan page's "I understand" before the first scan: when it was given (lib/scan-risk.js).
|
|
30
32
|
scanRiskAccepted: SCAN_RISK_SETTING,
|
|
33
|
+
// The Scan page's optional App Management item on a Mac: Done or Skip, so it's offered once (lib/scanner-setup.js).
|
|
34
|
+
appManagement: APP_MANAGEMENT_SETTING,
|
|
35
|
+
// Auto's one-time question before its first connection request: when it was answered yes (lib/auto-connect.js).
|
|
36
|
+
autoConnectAccepted: AUTO_ACCEPTED_SETTING,
|
|
31
37
|
// Settings → Appearance: the look (lib/themes.js). Here so it travels with a
|
|
32
38
|
// backup or a restore; the browser keeps a copy to paint with first (lib/theme-store.js).
|
|
33
39
|
theme: THEME_SETTING,
|
|
@@ -193,3 +193,8 @@ export function hoursText([from, to] = AUTO.hours) {
|
|
|
193
193
|
const h = (n) => `${n % 12 || 12} ${n < 12 ? 'AM' : 'PM'}`;
|
|
194
194
|
return `${h(from)} to ${h(to)}`;
|
|
195
195
|
}
|
|
196
|
+
|
|
197
|
+
// Auto's connection requests have caps of their own, in the same record
|
|
198
|
+
// (lib/auto-connect.js, scripts/scrape.py INVITE_DAY_CAP and INVITE_WEEK_CAP):
|
|
199
|
+
// the usage page shows them beside the searches and profile views.
|
|
200
|
+
export { INVITE_CAPS, REPORTED_WEEKLY_INVITES } from './auto-connect.js';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "six-degrees",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "six-degrees",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "1.0.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"os": [
|
|
12
12
|
"darwin",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "six-degrees",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Map your professional network as a galaxy — leverage tiers, bridges into new circles, and the shortest path to anyone. Runs locally on your own data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Blake Burford",
|