six-degrees 0.4.6 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
- 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 +2 -2
- 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 +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +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.nft.json +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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +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.nft.json +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.nft.json +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.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.nft.json +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.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.nft.json +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/favicon.ico/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/favicon.ico.body +0 -0
- 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 +15 -15
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +15 -15
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +15 -15
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +15 -15
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +15 -15
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +15 -15
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +15 -15
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1mxn_lb._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00d8snt._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00g61b8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08lf3gy._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hg5gib._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ho-u0w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ze473p._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1cnxxc9._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jeztd-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ll97ry._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m0255u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1w9z0zv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_005q9n1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/15qbli9udo0vh.js +1 -0
- package/.next/{static/chunks/2_zu7rmb8flzl.js → standalone/.next/static/chunks/1z-zp93cyoxvz.js} +1 -1
- package/.next/standalone/.next/static/chunks/24tvdun1cb3g1.js +1 -0
- package/.next/standalone/.next/static/chunks/3q3pmt5vv-4pi.js +6 -0
- package/.next/standalone/.next/static/media/favicon.23vm2o6v3er2z.ico +0 -0
- package/.next/standalone/CHANGELOG.md +42 -0
- package/.next/standalone/CONTRIBUTING.md +5 -0
- package/.next/standalone/README.md +168 -56
- package/.next/standalone/app/api/scraper/route.js +3 -0
- package/.next/standalone/app/favicon.ico +0 -0
- package/.next/standalone/app/paths/page.js +25 -18
- package/.next/standalone/app/setup/page.js +18 -0
- package/.next/standalone/lib/scoring.js +42 -1
- package/.next/standalone/lib/scraper-client.js +5 -1
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/build-site.mjs +537 -0
- package/.next/standalone/scripts/site-markdown.mjs +188 -0
- package/.next/standalone/scripts/site-news.mjs +311 -0
- package/.next/static/chunks/15qbli9udo0vh.js +1 -0
- package/.next/{standalone/.next/static/chunks/2_zu7rmb8flzl.js → static/chunks/1z-zp93cyoxvz.js} +1 -1
- package/.next/static/chunks/24tvdun1cb3g1.js +1 -0
- package/.next/static/chunks/3q3pmt5vv-4pi.js +6 -0
- package/.next/static/media/favicon.23vm2o6v3er2z.ico +0 -0
- package/CHANGELOG.md +42 -0
- package/README.md +168 -56
- package/lib/scoring.js +42 -1
- package/lib/scraper-client.js +5 -1
- package/package.json +1 -1
- package/.next/standalone/.next/static/chunks/04a3zockahetx.js +0 -1
- package/.next/standalone/.next/static/chunks/24m745q_85u34.js +0 -6
- package/.next/standalone/.next/static/chunks/3p_pwu5gx2i9h.js +0 -1
- package/.next/standalone/.next/static/media/favicon.0zdzw5kurn8m-.ico +0 -0
- package/.next/static/chunks/04a3zockahetx.js +0 -1
- package/.next/static/chunks/24m745q_85u34.js +0 -6
- package/.next/static/chunks/3p_pwu5gx2i9h.js +0 -1
- package/.next/static/media/favicon.0zdzw5kurn8m-.ico +0 -0
- /package/.next/standalone/.next/static/{-QUFjmCQWXW5PCoTA1G12 → T0eWMsKjgSiPnmx1E0q3G}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{-QUFjmCQWXW5PCoTA1G12 → T0eWMsKjgSiPnmx1E0q3G}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{-QUFjmCQWXW5PCoTA1G12 → T0eWMsKjgSiPnmx1E0q3G}/_ssgManifest.js +0 -0
- /package/.next/static/{-QUFjmCQWXW5PCoTA1G12 → T0eWMsKjgSiPnmx1E0q3G}/_buildManifest.js +0 -0
- /package/.next/static/{-QUFjmCQWXW5PCoTA1G12 → T0eWMsKjgSiPnmx1E0q3G}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{-QUFjmCQWXW5PCoTA1G12 → T0eWMsKjgSiPnmx1E0q3G}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,68863,e=>{"use strict";var n=e.i(43476),t=e.i(71645),i=e.i(18566),o=e.i(2776),s=e.i(22016),r=e.i(40996);let a={install:"Install",setup:"Set up the scanner"};var l=e.i(64745),d=e.i(48235);let c={daily:50,monthly:250},h="1px solid rgba(255,255,255,0.1)",p={S:"#FFD700",A:"#9B59B6",B:"#3498DB",C:"#95A5A6",D:"#BDC3C7"},u=[25,50,100,200,500],g=[100,250,500,1e3,0],f=[10,25,50,100],y=e=>new Date(e).toLocaleDateString(void 0,{month:"short",day:"numeric"});function x({cooldown:e,onLift:i,disabled:o}){let[s,r]=(0,t.useState)(!1);return e?(0,n.jsxs)("div",{role:"status",style:{padding:"12px 14px",borderRadius:8,fontSize:13,lineHeight:1.6,background:"rgba(255,80,80,0.08)",border:"1px solid rgba(255,80,80,0.35)",color:"#f3c9c9"},children:[(0,n.jsxs)("b",{style:{color:"#ff8080"},children:["Scanning is paused until ",new Date(e.until).toLocaleString(void 0,{weekday:"short",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"}),"."]})," ",e.reason,". Nothing that searches LinkedIn will run until then, so the account can recover.",(0,n.jsx)("div",{style:{marginTop:8,display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:s?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("span",{style:{fontSize:12.5},children:"Only if people search works normally for you on linkedin.com right now."}),(0,n.jsx)("button",{onClick:()=>{r(!1),i()},style:{...v,color:"#ff8080"},children:"Yes, lift it"}),(0,n.jsx)("button",{onClick:()=>r(!1),style:v,children:"Keep it"})]}):(0,n.jsx)("button",{onClick:()=>r(!0),disabled:o,style:v,children:"Lift it early…"})})]}):null}function b({used:e,cap:t}){let i=t?Math.min(100,Math.round(e/t*100)):0;return(0,n.jsx)("div",{style:{height:5,borderRadius:3,background:"rgba(255,255,255,0.07)",overflow:"hidden",marginTop:4},children:(0,n.jsx)("div",{style:{height:"100%",width:`${i}%`,background:i>=100?"#ff6b6b":i>=75?"#FFD700":"#3498DB"}})})}function m({li:e,onSetLimits:t,disabled:i}){if(!e)return null;let{limits:o}=e,s=e.unreadable?"?":e.searchesToday,r=e.unreadable?"?":e.profilesToday,a=e=>t({...o,...e}),l=e=>{let n=function(e={},n={}){return Number(n.daily)>100&&Number(n.daily)>Number(e.daily)?`${n.daily} searches a day is more than LinkedIn has put up with. A real account was restricted after 373 searches in 24 hours, run back to back. The default is ${c.daily} a day.
|
|
2
|
+
|
|
3
|
+
Set ${n.daily} a day anyway?`:0===n.monthly&&0!==e.monthly?`With no monthly cap, only the daily budget stops a scan. A real account was restricted after 373 searches in 24 hours with no monthly cap set.
|
|
4
|
+
|
|
5
|
+
Turn the monthly cap off anyway?`:null}(o,{...o,...e});(!n||window.confirm(n))&&a(e)},d=function({daily:e,monthly:n}={}){if(!(({daily:e,monthly:n}={})=>Number(e)>100||0===n)({daily:e,monthly:n}))return null;let t=["A real account was restricted after 373 searches in 24 hours."];return Number(e)>100&&t.push(`${e} a day can use up a free account's month in a day or two.`),0===n&&t.push("With no monthly cap, nothing but the daily budget stops a long run."),t.join(" ")}(o);return(0,n.jsxs)("div",{style:{padding:"12px 14px",borderRadius:8,border:h,background:"rgba(255,255,255,0.03)",fontSize:12.5,color:"#b8c4c4"},children:[(0,n.jsxs)("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(210px, 1fr))",gap:14},children:[(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("b",{style:{color:"#fff"},children:s})," of ",o.daily||"no limit"," searches in the last 24 hours"]}),(0,n.jsx)(b,{used:e.searchesToday||0,cap:o.daily})]}),(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("b",{style:{color:"#fff"},children:e.searchesMonth})," of ",o.monthly||"no limit"," this month · resets ",y(e.monthResets)]}),(0,n.jsx)(b,{used:e.searchesMonth,cap:o.monthly})]}),(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("b",{style:{color:"#fff"},children:r})," of ",o.profiles," profile views today"]}),(0,n.jsx)(b,{used:e.unreadable?o.profiles:e.profilesToday,cap:o.profiles})]})]}),(0,n.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap",marginTop:10},children:[(0,n.jsx)("span",{children:"Budget:"}),(0,n.jsx)("select",{value:o.daily,disabled:i,onChange:e=>l({daily:Number(e.target.value)}),style:k,children:u.map(e=>(0,n.jsxs)("option",{value:e,children:[e," a day"]},e))}),(0,n.jsx)("select",{value:o.monthly,disabled:i,onChange:e=>l({monthly:Number(e.target.value)}),style:k,children:g.map(e=>(0,n.jsx)("option",{value:e,children:e?`${e} a month`:"no monthly cap (Premium)"},e))}),(0,n.jsx)("select",{value:o.profiles,disabled:i,onChange:e=>a({profiles:Number(e.target.value)}),style:k,children:f.map(e=>(0,n.jsxs)("option",{value:e,children:[e," profile views a day"]},e))})]}),(0,n.jsx)("div",{style:{marginTop:8,color:"#778",lineHeight:1.6},children:"Every page of someone’s connections is one search. LinkedIn limits a free account’s people searches by the month (it doesn’t say how many; reports put it around 250–350), resetting on the 1st. When a budget is used, a scan saves what it read and stops; the next one carries on from the same page."}),d&&(0,n.jsxs)("div",{style:{marginTop:8,display:"flex",gap:10,alignItems:"center",flexWrap:"wrap",color:"#FFD700",lineHeight:1.6},children:[(0,n.jsx)("b",{style:{flex:1,minWidth:200},children:d}),(0,n.jsxs)("button",{onClick:()=>a(c),disabled:i,style:{...k,cursor:i?"not-allowed":"pointer",color:"#FFD700",borderColor:"rgba(255,215,0,0.4)"},children:["Back to ",c.daily," a day, ",c.monthly," a month"]})]}),(0,n.jsx)("div",{style:{marginTop:6,color:"#778",lineHeight:1.6},children:"Each circle scan opens the person’s profile once, which is one profile view. Profile views are what LinkedIn restricted an account for, after about 20 in an hour. The scanner opens at most one a minute, and once today’s are used, a scan stops before the next profile and tries that person next time."})]})}function w({paused:e=[],onResume:i,onResumeAll:o,disabled:s}){let[r,a]=(0,t.useState)(!1);if(!e.length)return null;let l=e.filter(e=>e.legacy).length,d=r?e:e.slice(0,8);return(0,n.jsxs)("div",{style:{borderRadius:8,border:h,background:"rgba(255,255,255,0.03)"},children:[(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:10,padding:"10px 14px",borderBottom:h,flexWrap:"wrap"},children:[(0,n.jsxs)("div",{style:{flex:1,minWidth:200},children:[(0,n.jsxs)("b",{style:{fontSize:13.5},children:["Paused — ",e.length," ",1===e.length?"list":"lists"," with more to read"]}),(0,n.jsxs)("div",{style:{fontSize:12,color:"#8b9a9a",marginTop:2},children:["Each carries on from the page it stopped at",l?`; ${l} were read to page 10 before whole lists were read`:"","."]})]}),(0,n.jsx)("button",{onClick:o,disabled:s,style:{...j,opacity:s?.4:1,cursor:s?"not-allowed":"pointer",background:s?"rgba(255,255,255,0.06)":"linear-gradient(135deg, #3498DB, #9B59B6)"},children:"Resume all"})]}),(0,n.jsx)("div",{children:d.map(e=>(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:10,padding:"8px 14px",borderBottom:"1px solid rgba(255,255,255,0.04)"},children:[(0,n.jsx)("span",{style:{width:8,height:8,borderRadius:"50%",background:p[e.tier]||"#667",flexShrink:0}}),(0,n.jsxs)("div",{style:{flex:1,minWidth:0},children:[(0,n.jsx)("div",{style:{fontSize:13,fontWeight:600,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.name}),(0,n.jsxs)("div",{style:{fontSize:11.5,color:"#8b9a9a"},children:["Read to page ",e.pagesRead," · carries on at ",e.nextPage,e.at?` \xb7 ${y(e.at)}`:"",e.unclear>=2?" · came back unclear twice, so it waits at the back of Resume all":""]})]}),(0,n.jsx)("button",{onClick:()=>i(e),disabled:s,style:{...j,padding:"6px 12px",fontSize:12,opacity:s?.4:1,cursor:s?"not-allowed":"pointer"},children:"Resume"})]},e.id))}),e.length>8&&(0,n.jsx)("button",{onClick:()=>a(!r),style:{...v,padding:"10px 14px"},children:r?"Show fewer":`Show all ${e.length}`})]})}let j={padding:"8px 14px",borderRadius:7,fontSize:13,fontWeight:700,cursor:"pointer",border:h,background:"rgba(255,255,255,0.08)",color:"#fff"},v={background:"none",border:"none",color:"#8fb8d6",cursor:"pointer",fontSize:12.5,fontWeight:600,padding:0},k={padding:"6px 8px",borderRadius:6,fontSize:12.5,fontWeight:600,background:"rgba(255,255,255,0.08)",color:"#fff",border:h};var S=e.i(8819),T=e.i(98197);e.i(77170);var I=e.i(69083),C=e.i(56899);let B={color:"#3498DB"};function z({onDone:e}){let[i,o]=(0,t.useState)([]),[s,r]=(0,t.useState)(null),[a,l]=(0,t.useState)(null);async function d(){r("pick"),l(null);let n=await (0,C.saveSettings)({sectorFocus:{sectors:i,strength:"lean"},fieldAsked:!0});n.settings?e(n.settings.sectorFocus.sectors):(l(n.error),r(null))}async function c(){r("skip"),l(null),await (0,C.saveSettings)({fieldAsked:!0}),e([])}return(0,n.jsxs)("section",{"aria-labelledby":"field-question","data-field-question":!0,style:{margin:"8px 0 0",padding:"20px 22px 0",borderRadius:12,border:S.LINE,background:"rgba(255,255,255,0.03)"},children:[(0,n.jsx)("div",{style:{fontSize:12,color:"#788",textTransform:"uppercase",letterSpacing:.6},children:"Before you scan · optional"}),(0,n.jsx)("h2",{id:"field-question",style:{fontSize:18,fontWeight:700,margin:"6px 0 4px"},children:"What field are you in?"}),(0,n.jsx)(S.Body,{style:{marginTop:0,marginBottom:14},children:"Pick up to three. Companies in your field count for more when your network is scored, so the people there rank higher. You can change it anytime on the Scores tab."}),(0,n.jsx)(T.default,{sectors:i,onChange:o,disabled:!!s}),(0,n.jsxs)("div",{style:{position:"sticky",bottom:0,margin:"16px -22px 0",padding:"12px 22px 16px",borderTop:S.LINE,borderRadius:"0 0 11px 11px",background:"#111121"},children:[(0,n.jsxs)("div",{style:{display:"flex",gap:8,flexWrap:"wrap"},children:[(0,n.jsx)(S.Btn,{primary:!0,onClick:d,disabled:!i.length||!!s,children:"pick"===s?"Saving…":"Continue"}),(0,n.jsx)(S.Btn,{onClick:c,disabled:!!s,children:"Skip for now"})]}),a&&(0,n.jsx)(S.Status,{tone:"bad",children:a})]})]})}function F({sectors:e}){return e.length?(0,n.jsxs)(S.Status,{tone:"ok",children:["Your field: ",e.map(e=>(0,I.sectorByKey)(e).label).join(" · "),". Your first scan is scored with it. Change it anytime on ",(0,n.jsx)(s.default,{href:"/scores#sector",style:B,children:"Scores"}),"."]}):(0,n.jsxs)(S.Status,{children:["No field for now. You can pick one anytime on ",(0,n.jsx)(s.default,{href:"/scores#sector",style:B,children:"Scores"}),"."]})}let R="1px solid rgba(255,255,255,0.1)",W={photos:"Saving profile photos to this computer",install:"Installing the scanner",setup:"Setting up the scanner",login:"Waiting for you to sign in",full:"Scanning your whole network",refresh:"Checking for new connections","auto-bridge":"Mapping 2nd-degree connections","auto-bridge-retry":"Mapping 2nd degree, hidden ones included",resume:"Carrying on with one paused list","resume-all":"Carrying on with every paused list"};function D(){let e;(0,t.useEffect)(()=>{"updates"===new URLSearchParams(window.location.search).get("check")&&window.location.replace("/settings?check=updates")},[]);let[o,c]=(0,t.useState)(null),[h,p]=(0,t.useState)(!1),[u,g]=(0,t.useState)(10),[f,y]=(0,t.useState)(["S","A"]),[b,j]=$("six-degrees-bridge-order","newest"),[v,k]=$("six-degrees-bridge-pages-v2",100),[S,T]=$("six-degrees-bridge-finish",!0),[I,C]=$("six-degrees-experimental-auto",!1),[B,D]=$("six-degrees-hide-chrome",!1),[P,U]=(0,t.useState)(null),G=(0,t.useRef)(null),[Y,K]=(0,t.useState)(void 0),[q,X]=(0,t.useState)(null),J=(0,i.useSearchParams)().get("scan"),[V,Q]=(0,t.useState)(null);(0,t.useEffect)(()=>{if(!J)return;let e=!0;return(0,r.pickedPerson)(J).then(n=>{e&&Q(n?.person?n:null)},()=>{}),()=>{e=!1}},[J]);let Z=(0,t.useCallback)(async()=>{try{let e=await fetch("/api/scraper");c(await e.json())}catch{c(e=>e||{ready:!1,checks:{},log:[]})}},[]);async function ee(){U(null),await (0,r.stopScrape)().catch(e=>U(e.message)),Z()}async function en(e,n={}){U(null),p(!0);try{let t=await fetch("/api/scraper",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:e,...n})}),i=await t.json();t.ok||U(i.error||"Could not start.")}catch(e){U(e.message)}finally{p(!1),Z()}}(0,t.useEffect)(()=>{Z();let e=setInterval(Z,1500);return()=>clearInterval(e)},[Z]),(0,t.useEffect)(()=>{if(l.IS_DEMO)return;let e=!1;return fetch("/api/settings").then(e=>e.ok?e.json():null).then(n=>{e||K(n?.settings||null)}).catch(()=>{e||K(null)}),()=>{e=!0}},[]),(0,t.useEffect)(()=>{G.current&&(G.current.scrollTop=G.current.scrollHeight)},[o?.log?.length]);let et=o?.checks||{},ei=o?.running,eo={...e=function(e){let n=e?.checks||{},t=(n,t,i)=>e?.running&&e.action===n?i:t;if(n.dependencies)return"bundled"===n.pythonSource?{done:!0,text:"Ready. The scanner and its Python come with the app, so there is nothing to install.",button:null}:"custom"===n.pythonSource?{done:!0,text:`Ready, on the Python named in SIX_DEGREES_PYTHON (${n.pythonPath}).`,button:null}:{done:!0,text:"Installed.",button:null};let i=n.ownPython&&"bundled"!==n.ownPython.source?`The Python named in SIX_DEGREES_PYTHON didn't work (${n.ownPython.problem}), so the scanner needs setting up another way. `:"";if(n.installFrom)return{done:!1,text:i+("downloaded"===n.installFrom.source?"Its own Python is already here. One more step, about a minute: it installs the browser-automation packages into it.":"One-time, about a minute. Downloads the browser-automation packages."),button:{action:"install",label:t("install","Install","Installing…")}};if(n.download){let e=Math.round(n.download.size/1048576);return{done:!1,text:`${i}${function(e){if(!e?.version)return"This computer has no Python the scanner can use.";let[n,t]=String(e.version).split(".").map(Number);return 3===n&&t>=10&&t<=14&&!1===e.venv?`This computer's Python ${e.version} can't make the scanner's own environment (on Ubuntu or Debian, python3-venv isn't installed).`:`This computer has Python ${e.version}, and the scanner needs 3.10 to 3.14.`}(n.systemPython)} Set up the scanner downloads its own Python ${n.download.version} (${e} MB, from GitHub) and the scanner's packages (from PyPI) into your data folder. Nothing else on this computer changes. About two minutes.`,button:{action:"setup",label:t("setup","Set up the scanner","Setting up…")}}}return{done:!1,text:`${i}No Python 3.10 to 3.14 was found on this machine. Install one from python.org, then reload.`,button:null}}(o),note:function(e,{done:n,button:t}){if(e?.source!=="bundled")return null;let i=/SIGKILL|code signature|not valid for use in process|quarantine|not permitted/i.test(e.problem||""),o=[`The Python that comes with the app didn't work (${e.problem}).`];return i&&o.push("macOS may have blocked it."),!1===e.retry&&o.push("Six Degrees won't try it again until you restart it."),n?o.push("The scanner uses another Python on this computer instead."):t?o.push(`To scan now, use ${a[t.action]} below: it gives the scanner a Python of its own in your data folder.`):o.push("To scan now, install Python 3.10 to 3.14 from python.org, then reload."),o.join(" ")}(o?.checks?.ownPython,e)};o&&et.dependencies;let es=o&&!et.chrome,er=o&&!et.scriptsFound,ea=o?.ready&&!ei,el=o?.linkedin,ed=!!el?.cooldown,ec=ea&&!ed,eh=o?.network||{first:0,second:0,third:0},ep=eh.first,eu=!l.IS_DEMO&&!q&&function(e,n){if(!e)return null;let t=e.network;return!!t&&!t.first&&!t.second&&!t.third&&!e.running&&(void 0===n?null:!!(n&&!n.fieldAsked&&!n.sectorFocus?.sectors?.length))}(o,Y),eg=o&&!ei&&null!=o.exitCode&&0!==o.exitCode,ef=eg?o.failure?.length?o.failure.join("\n"):[...o.log||[]].reverse().find(e=>!/^Stopped \(exit/.test(e)&&!/Warning|warnings\.warn/.test(e)):null;return(0,n.jsxs)("div",{style:{minHeight:"100vh",background:"#0a0a1a",color:"#fff",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'},children:[(0,n.jsxs)("header",{style:{padding:"16px 24px",borderBottom:R,display:"flex",alignItems:"center",gap:16},children:[(0,n.jsx)(s.default,{href:"/",style:{display:"flex",alignItems:"center",gap:6,color:"#888",textDecoration:"none",fontSize:13,fontWeight:600,padding:"6px 14px",borderRadius:6,background:"rgba(255,255,255,0.06)",border:R},children:"← Back to Map"}),(0,n.jsx)("h1",{style:{fontSize:22,fontWeight:700,margin:0,background:"linear-gradient(135deg, #FFD700, #9B59B6, #3498DB)",WebkitBackgroundClip:"text",WebkitTextFillColor:"transparent"},children:"Scan your network"})]}),(0,n.jsxs)("div",{style:{maxWidth:720,margin:"0 auto",padding:"32px 24px 64px"},children:[(0,n.jsxs)("p",{style:{color:"#9aa",fontSize:15,lineHeight:1.6,marginTop:0},children:["This reads your own LinkedIn connections in a real Chrome window on this machine and saves them here. Nothing leaves your computer, and you will never be asked for your password — you sign in yourself, once.",!ep&&(eu?" One optional question, then three steps and your galaxy appears.":" Three steps and your galaxy appears; each one ticks itself off.")]}),null===eu&&(0,n.jsx)("p",{style:{color:"#778",fontSize:13},children:"Checking your setup…"}),eu&&(0,n.jsx)(z,{onDone:function(e){X({sectors:e}),window.scrollTo(0,0)}}),q&&(0,n.jsx)(F,{sectors:q.sectors}),!1===eu&&(0,n.jsxs)(n.Fragment,{children:[V?.person.id===J&&(0,n.jsx)(L,{pick:V,pages:v,setPages:k,li:el,running:ei,s:o,canSearch:ec,busy:h,onUnpick:function(){Q(null);try{window.history.replaceState(null,"",window.location.pathname)}catch{}},onStart:()=>en("bridge",{id:V.person.id,maxPages:v})}),ep>0&&!ei&&(0,n.jsx)(H,{children:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:14,flexWrap:"wrap"},children:[(0,n.jsxs)("div",{style:{flex:1,minWidth:200},children:[(0,n.jsxs)("b",{children:[ep.toLocaleString()," connections"]}),eh.second>0&&(0,n.jsxs)(n.Fragment,{children:[", plus ",eh.second.toLocaleString()," people in their circles"]}),eh.third>0&&(0,n.jsxs)(n.Fragment,{children:[" and ",eh.third.toLocaleString()," from company scans"]}),"."," ",(0,n.jsx)("span",{style:{color:"#9aa"},children:"Your galaxy is ready."})]}),(0,n.jsx)(s.default,{href:"/",style:{padding:"9px 18px",borderRadius:7,fontSize:13.5,fontWeight:700,color:"#0a0a1a",textDecoration:"none",background:"linear-gradient(135deg, #FFD700, #FF6B35)"},children:"See your network →"})]})}),o?.photosWaiting>0&&!ei&&(0,n.jsx)(H,{children:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:14,flexWrap:"wrap"},children:[(0,n.jsxs)("div",{style:{flex:1,minWidth:200},children:[(0,n.jsxs)("b",{children:[o.photosWaiting.toLocaleString()," ",1===o.photosWaiting?"photo isn’t":"photos aren’t"," saved on this computer yet."]})," ",(0,n.jsx)("span",{style:{color:"#9aa"},children:"An older version kept them as links to LinkedIn. The app doesn’t load photos from LinkedIn while you browse, so those people show initials until the photos are saved here: now, or at the end of your next scan. An expired link can’t be saved; that person’s photo comes back when they’re next scanned."}),o&&!et.dependencies&&(0,n.jsx)("div",{style:{color:"#8b9a9a",fontSize:12.5,marginTop:6},children:"Set up the scanner first (step 1 below): it saves them."})]}),(0,n.jsx)(M,{onClick:()=>en("photos"),disabled:h||!et.dependencies,children:"Save photos"})]})}),eg&&(0,n.jsxs)(H,{tone:"bad",children:[(0,n.jsx)("b",{children:"The last run stopped before it finished."}),ef&&(0,n.jsx)("div",{style:{marginTop:6,color:"#e8c4c4",whiteSpace:"pre-wrap",fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:12.5},children:ef}),(0,n.jsx)("div",{style:{color:"#9aa",marginTop:6},children:"The full log is below."})]}),er&&(0,n.jsxs)(H,{tone:"bad",children:[(0,n.jsx)("b",{children:"Can’t find the scanner files."}),(0,n.jsx)("br",{}),(0,n.jsxs)("span",{style:{color:"#9aa"},children:["Expected ",(0,n.jsx)("code",{style:E,children:"scripts/scrape.py"})," next to the app. If you downloaded a zip, run the app from inside the project folder."]})]}),es&&(0,n.jsxs)(H,{tone:"bad",children:[(0,n.jsx)("b",{children:"Google Chrome isn’t installed."}),(0,n.jsx)("br",{}),(0,n.jsxs)("span",{style:{color:"#9aa"},children:["The scanner drives your real Chrome. Install it from"," ",(0,n.jsx)("a",{href:"https://www.google.com/chrome/",target:"_blank",rel:"noreferrer",style:{color:"#3498DB"},children:"google.com/chrome"}),", then reload this page."]})]}),o&&eo.note&&(0,n.jsx)(H,{tone:eo.done?void 0:"bad",children:eo.note}),(0,n.jsx)(A,{n:1,done:eo.done,title:"Set up the scanner",body:o?eo.text:"",action:o&&eo.button&&(0,n.jsx)(M,{onClick:()=>en(eo.button.action),disabled:h||ei,children:eo.button.label})}),(0,n.jsx)(A,{n:2,done:!!et.signedIn,title:"Sign into LinkedIn",body:et.signedIn?"Signed in on this machine. If LinkedIn ever asks for a security check, or a scan says you were signed out, open LinkedIn here and finish it by hand.":"Opens a Chrome window. Sign in with your email and password — “Continue with Google” cannot work here, because Google blocks its sign-in inside automated browsers.",action:et.dependencies&&(0,n.jsx)(M,{onClick:()=>en("login"),disabled:h||ei,children:ei&&"login"===o.action?"Waiting for you…":"Open LinkedIn"})}),(0,n.jsx)(A,{n:3,done:ep>0,title:"1st degree — the people you know",body:"The first scan walks your whole connections list, about a minute and a half for 750 people. After that, Check for new only looks at what has been added since.",action:(0,n.jsxs)("div",{style:{display:"flex",gap:10,flexWrap:"wrap"},children:[(0,n.jsx)(M,{onClick:()=>en("full"),disabled:!ec,primary:!0,children:ei&&"full"===o.action?"Scanning…":"Scan my whole network"}),(0,n.jsx)(M,{onClick:()=>en("refresh"),disabled:!ec,children:ei&&"refresh"===o.action?"Checking…":"Check for new"})]})}),(0,n.jsx)(A,{n:4,done:!1,title:"2nd degree — the people they know",body:(0,n.jsxs)(n.Fragment,{children:["This is what fills ",(0,n.jsx)("b",{children:"Degrees"})," and ",(0,n.jsx)("b",{children:"Outlink"}),": it opens each of your connections in turn and reads who ",(0,n.jsx)("i",{children:"they"})," know. Most people hide their connections — those are noted and never tried again.",(0,n.jsx)("br",{}),(0,n.jsx)("br",{}),"It is slow on purpose, about two minutes between each person, because this is the part LinkedIn notices. During development a real account was temporarily restricted after roughly ",(0,n.jsx)("b",{children:"19 people in one sitting"}),". Run a batch, leave it for a day, run another — and stop the moment LinkedIn mentions unusual activity. It always picks up where it left off: anyone still without a mapped circle, in the order you choose below — your newest connections first, or highest tier first. Each person’s list is read to the end, saving every 10 pages, and a read that is stopped carries on from the same page next time."]}),action:(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:12},children:[(0,n.jsx)(x,{cooldown:el?.cooldown,disabled:h,onLift:()=>en("lift-cooldown")}),(0,n.jsx)(m,{li:el,disabled:h,onSetLimits:e=>en("set-limits",e)}),"score"===b&&(0,n.jsxs)("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:[(0,n.jsx)("span",{style:{fontSize:12,color:"#8b9a9a"},children:"Work through"}),["S","A","B","C","D"].map(e=>{let t=f.includes(e);return(0,n.jsx)("button",{onClick:()=>y(n=>n.includes(e)?n.filter(n=>n!==e):[...n,e]),disabled:ei,style:{width:30,height:28,borderRadius:7,fontSize:12,fontWeight:700,cursor:ei?"not-allowed":"pointer",border:R,background:t?"rgba(52,152,219,0.22)":"rgba(255,255,255,0.05)",color:t?"#cfe6f7":"#667"},children:e},e)}),(0,n.jsx)("span",{style:{fontSize:11.5,color:"#667"},children:f.length?"":"pick at least one"})]}),"newest"===b&&(0,n.jsxs)("div",{style:{fontSize:12,color:"#8b9a9a",lineHeight:1.6},children:["Goes by the date you connected, newest first, across every tier. Run"," ",(0,n.jsx)("b",{children:"Check for new"})," first so your latest connections are in the list."]}),(0,n.jsxs)("div",{style:{display:"flex",gap:10,flexWrap:"wrap",alignItems:"center"},children:[(0,n.jsx)("span",{style:{fontSize:12,color:"#8b9a9a"},children:"Start with"}),(0,n.jsxs)("select",{value:b,onChange:e=>j(e.target.value),disabled:ei,style:N,children:[(0,n.jsx)("option",{value:"newest",children:"Newest connections first"}),(0,n.jsx)("option",{value:"score",children:"Highest tier first"})]}),(0,n.jsx)("span",{style:{fontSize:12,color:"#8b9a9a"},children:"Read up to"}),(0,n.jsxs)("select",{value:v,onChange:e=>k(Number(e.target.value)),disabled:ei,style:N,children:[(0,n.jsx)("option",{value:100,children:"every page, to the end of their list"}),(0,n.jsx)("option",{value:50,children:"50 pages each"}),(0,n.jsx)("option",{value:25,children:"25 pages each"}),(0,n.jsx)("option",{value:10,children:"10 pages (~100 people) each"})]})]}),(0,n.jsxs)("label",{style:{display:"flex",gap:8,alignItems:"center",fontSize:12.5,color:"#cfd8d8",cursor:ei?"not-allowed":"pointer"},children:[(0,n.jsx)("input",{type:"checkbox",checked:S,onChange:e=>T(e.target.checked),disabled:ei}),"Also finish people already mapped, from the page each one stopped at"]}),(0,n.jsxs)("label",{style:{display:"flex",gap:8,alignItems:"flex-start",fontSize:12.5,color:"#b8c4c4",cursor:ei?"default":"pointer"},children:[(0,n.jsx)("input",{type:"checkbox",checked:I,disabled:ei,style:{marginTop:3},onChange:e=>{C(e.target.checked),window.dispatchEvent(new Event("six-degrees:experimental"))}}),(0,n.jsxs)("span",{children:[(0,n.jsx)("b",{style:{color:"#FFD700"},children:"Experimental:"})," all-day pacing. Up to 10 pages in a sitting, then a 45-minute rest; searches only from 9:00 to 19:00; at the daily budget it waits for it to free up instead of stopping; and it saves every page. It also reads LinkedIn’s own data beside the page text, to fill gaps and measure how the two compare. It keeps running while the app is open."]})]}),(0,n.jsxs)("label",{style:{display:"flex",gap:8,alignItems:"flex-start",fontSize:12.5,color:"#b8c4c4",cursor:ei?"default":"pointer"},children:[(0,n.jsx)("input",{type:"checkbox",checked:B,disabled:ei,style:{marginTop:3},onChange:e=>D(e.target.checked)}),(0,n.jsxs)("span",{children:[(0,n.jsx)("b",{style:{color:"#FFD700"},children:"Hide the Chrome window while scanning."})," Every scan runs with no window popping up; the status bar still shows what it’s doing, and Stop still works. Signing in always opens the window.",(0,n.jsx)("span",{style:{display:"block",marginTop:4,color:"#e0a080"},children:"The risks: a hidden Chrome is easier for LinkedIn to tell apart from a person, so it may make a warning or restriction more likely. If LinkedIn asks you to check it’s you (a code, a puzzle, signing in again) you won’t see it, and the scan will stop instead of waiting for you. Untick this and scan again to see what LinkedIn wants. It changes nothing about pacing or your daily budget."})]})]}),(0,n.jsxs)("div",{style:{fontSize:12,color:"#FFD700",lineHeight:1.6},children:["Every page is a LinkedIn search, so it rests 20 seconds before each one and a minute after every 10, and a long list can take"," ","about ",(0,d.circleScanCost)(v).minutes," minutes a person. LinkedIn shows 100 pages of anyone’s connections at most. Free accounts have a monthly search limit: if LinkedIn says it has been reached, the scan saves what it read and stops, and carries on from that page next time. Keep batches small."]}),(0,n.jsxs)("div",{style:{display:"flex",gap:10,flexWrap:"wrap",alignItems:"center"},children:[(0,n.jsx)(M,{onClick:()=>en("auto-bridge",{maxBridges:u,tiers:"score"===b?f:[],order:b,maxPages:v,deeper:S,experimental:I}),disabled:!ec||"score"===b&&!f.length,primary:!0,children:ei&&"auto-bridge"===o.action?"Mapping…":"Map 2nd degree"}),(0,n.jsxs)("select",{value:u,onChange:e=>g(Number(e.target.value)),disabled:ei,style:N,children:[(0,n.jsx)("option",{value:5,children:"5 people"}),(0,n.jsx)("option",{value:10,children:"10 people"}),(0,n.jsx)("option",{value:25,children:"25 people"}),(0,n.jsx)("option",{value:0,children:"everyone — not advised"})]}),(0,n.jsx)(M,{onClick:()=>en("auto-bridge-retry",{maxBridges:u,tiers:"score"===b?f:[],order:b,maxPages:v,deeper:S}),disabled:!ec||"score"===b&&!f.length,children:"Retry hidden ones"})]}),(0,n.jsx)(w,{paused:o?.paused||[],disabled:!ec,onResume:e=>en("resume",{id:e.id}),onResumeAll:()=>en("resume-all",{maxBridges:u})})]})}),ei&&(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:12,marginTop:20,padding:"14px 16px",borderRadius:8,background:"rgba(255,255,255,0.05)",border:R},children:[(0,n.jsx)(_,{}),(0,n.jsxs)("div",{style:{flex:1,fontSize:13.5},children:[(0,n.jsx)("b",{children:W[o.action]||"Working"}),o.progress&&(0,n.jsx)(O,{p:o.progress,action:o.action}),(0,n.jsx)("div",{style:{color:"#8b9a9a",fontSize:12.5,marginTop:2},children:"Stopping closes the browser cleanly and keeps everything found so far."})]}),(0,n.jsx)(M,{onClick:ee,tone:"bad",children:"Stop"})]}),P&&(0,n.jsx)(H,{tone:"bad",children:P}),o?.log?.length>0&&(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:10,margin:"28px 0 8px",fontSize:12,color:"#788",textTransform:"uppercase",letterSpacing:.6},children:[ei&&(0,n.jsx)(_,{}),ei?"Working":"Last run"]}),(0,n.jsx)("pre",{ref:G,style:{background:"rgba(0,0,0,0.45)",border:R,borderRadius:8,padding:14,maxHeight:280,overflow:"auto",margin:0,fontSize:12.5,lineHeight:1.7,color:"#b9c6c6",fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",whiteSpace:"pre-wrap",wordBreak:"break-word"},children:o.log.join("\n")})]})]}),(0,n.jsxs)("div",{style:{marginTop:32,paddingTop:20,borderTop:R,fontSize:13.5,color:"#8b9a9a"},children:["Checking for updates has moved to"," ",(0,n.jsx)(s.default,{href:"/settings#updates",style:{color:"#3498DB"},children:"Settings →"})]}),(0,n.jsxs)("p",{style:{color:"#667",fontSize:12.5,lineHeight:1.7,marginTop:32},children:["Automating LinkedIn may go against its User Agreement, and accounts have been restricted for it. This runs locally against your own account, at your own risk. LinkedIn’s own CSV export is the supported route and needs none of this —"," ",(0,n.jsx)(s.default,{href:"/import",style:{color:"#3498DB"},children:"import a CSV instead"}),"."]})]}),(0,n.jsx)("style",{children:`@keyframes spin { to { transform: rotate(360deg) } }
|
|
6
|
+
@keyframes slide { 0% { transform: translateX(-100%) } 100% { transform: translateX(300%) } }`})]})}function L({pick:e,pages:t,setPages:i,li:o,running:r,s:a,canSearch:l,busy:c,onStart:h,onUnpick:p}){let{person:u,circle:g}=e,f=String(u.name||"").trim().split(/\s+/)[0]||"them",y=(0,d.circleScanCost)(t),x=r&&a?.target?.id===u.id,b=o?.leftToday!=null&&o.leftToday<y.searches;return(0,n.jsxs)("div",{style:{margin:"16px 0",padding:"16px 18px",borderRadius:10,fontSize:13.5,lineHeight:1.6,background:"rgba(0,255,136,0.05)",border:"1px solid rgba(0,255,136,0.3)"},children:[(0,n.jsx)("div",{style:{fontSize:11,fontWeight:700,color:"#00ff88",letterSpacing:.6,textTransform:"uppercase",marginBottom:4},children:"Scan one circle"}),(0,n.jsxs)("div",{style:{fontSize:16,fontWeight:700},children:[u.name," ",(0,n.jsxs)("span",{style:{fontSize:12,color:P[u.tier]||"#888"},children:[u.tier,"-tier"]})]}),u.unlocked_from_name&&(0,n.jsxs)("div",{style:{fontSize:12.5,color:"#9aa"},children:["Was in ",u.unlocked_from_name,"’s circle"]}),x?(0,n.jsxs)("div",{style:{marginTop:10},children:[(0,n.jsxs)("b",{children:["Scanning ",f,"’s circle."]})," It saves every 10 pages, and"," ",(0,n.jsx)(s.default,{href:`/?chain=${encodeURIComponent(u.id)}`,style:{color:"#00ff88"},children:"Bridge Chains shows it filling in →"})]}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{style:{color:"#b8c4c4",marginTop:10},children:["It costs ",(0,n.jsxs)("b",{children:[y.profileViews," profile view"]})," to find their list, then ",(0,n.jsx)("b",{children:"one LinkedIn search for every page"})," of it: up to ",y.searches," ",1===y.searches?"page":"pages",", about ",y.minutes," minutes.",o?.leftToday!=null&&(0,n.jsxs)(n.Fragment,{children:[" ",(0,n.jsx)("b",{children:o.leftToday})," of your ",o.limits?.daily," searches a day are left",b?"; the scan stops when they run out, and can carry on from that page later":"","."]})]}),"scanned"===g&&(0,n.jsx)("div",{style:{color:"#FFD700",fontSize:12.5,marginTop:6},children:"Their circle is already scanned. This reads their whole list again from page 1; to carry on from where a read stopped, use Resume on their card or in the Paused list below."}),"hidden"===g&&(0,n.jsx)("div",{style:{color:"#FFD700",fontSize:12.5,marginTop:6},children:"Their list was hidden last time it was tried. Trying again costs a profile view."}),(0,n.jsxs)("div",{style:{display:"flex",gap:10,alignItems:"center",flexWrap:"wrap",marginTop:12},children:[(0,n.jsxs)(M,{onClick:h,disabled:!l||c,primary:!0,children:["Scan ",f,"’s circle"]}),(0,n.jsxs)("select",{value:t,onChange:e=>i(Number(e.target.value)),disabled:r,style:N,"aria-label":"How many pages of their list to read",children:[(0,n.jsx)("option",{value:100,children:"every page, to the end of their list"}),(0,n.jsx)("option",{value:50,children:"up to 50 pages"}),(0,n.jsx)("option",{value:25,children:"up to 25 pages"}),(0,n.jsx)("option",{value:10,children:"up to 10 pages (~100 people)"})]}),(0,n.jsx)(M,{onClick:p,children:"Not now"})]}),!l&&(0,n.jsx)("div",{style:{fontSize:12.5,color:"#8b9a9a",marginTop:8},children:r?"Something else is running. One scan at a time: this one can start when it finishes.":o?.cooldown?"Scanning is paused for now (see below).":"The scanner isn’t ready yet: finish the steps below first."})]})]})}let P={S:"#FFD700",A:"#9B59B6",B:"#3498DB",C:"#95A5A6",D:"#BDC3C7"};function $(e,n){let[i,o]=(0,t.useState)(n);return(0,t.useEffect)(()=>{try{let n=localStorage.getItem(e);null!==n&&o(JSON.parse(n))}catch{}},[e]),[i,n=>{o(n);try{localStorage.setItem(e,JSON.stringify(n))}catch{}}]}let E={background:"rgba(255,255,255,0.08)",padding:"1px 5px",borderRadius:4},N={padding:"9px 10px",borderRadius:7,fontSize:13.5,fontWeight:600,background:"rgba(255,255,255,0.08)",color:"#fff",border:R};function A({n:e,done:t,title:i,body:o,action:s}){return(0,n.jsxs)("div",{style:{display:"flex",gap:16,padding:"20px 0",borderBottom:R,alignItems:"flex-start"},children:[(0,n.jsx)("div",{style:{flexShrink:0,width:28,height:28,borderRadius:"50%",display:"grid",placeItems:"center",fontSize:13,fontWeight:700,background:t?"rgba(0,255,136,0.15)":"rgba(255,255,255,0.08)",color:t?"#00ff88":"#889",border:`1px solid ${t?"rgba(0,255,136,0.4)":"rgba(255,255,255,0.14)"}`},children:t?"✓":e}),(0,n.jsxs)("div",{style:{flex:1,minWidth:0},children:[(0,n.jsx)("div",{style:{fontSize:15.5,fontWeight:650,marginBottom:4},children:i}),(0,n.jsx)("div",{style:{fontSize:13.5,color:"#8b9a9a",lineHeight:1.6,marginBottom:12*!!s},children:o}),s]})]})}function M({children:e,onClick:t,disabled:i,primary:o,tone:s}){return(0,n.jsx)("button",{onClick:t,disabled:i,style:{padding:"9px 18px",borderRadius:7,fontSize:13.5,fontWeight:650,color:i?"#667":"#fff",background:i?"rgba(255,255,255,0.05)":"bad"===s?"rgba(255,80,80,0.15)":o?"linear-gradient(135deg, #9B59B6, #3498DB)":"rgba(255,255,255,0.08)",border:R,cursor:i?"not-allowed":"pointer"},children:e})}function H({children:e,tone:t}){return(0,n.jsx)("div",{style:{margin:"16px 0",padding:"14px 16px",borderRadius:8,fontSize:13.5,lineHeight:1.6,background:"bad"===t?"rgba(255,80,80,0.08)":"rgba(255,255,255,0.05)",border:`1px solid ${"bad"===t?"rgba(255,80,80,0.3)":"rgba(255,255,255,0.12)"}`},children:e})}function O({p:e,action:t}){let i;if("saving"===e.kind)return(0,n.jsxs)("div",{style:{margin:"8px 0 6px"},children:[(0,n.jsx)("div",{role:"progressbar","aria-label":"Saving",style:{height:8,borderRadius:4,overflow:"hidden",background:"rgba(255,255,255,0.08)",marginBottom:6},children:(0,n.jsx)("div",{style:{width:"35%",height:"100%",borderRadius:4,background:"linear-gradient(90deg, #9B59B6, #3498DB)",animation:"slide 1.2s ease-in-out infinite"}})}),(0,n.jsx)("div",{style:{fontSize:12.5,color:"#cfe6f7"},children:"Saving to your network and fetching photos — this can take a minute or two."})]});let o=Math.max(0,Math.min(100,Math.round(e.done/e.total*100)));return i="download"===e.kind?`Downloading Python: ${e.done.toFixed(1)} of ${e.total.toFixed(1)} MB \xb7 ${o}%`:"batch"===e.kind?`Person ${e.current} of ${e.total}`:"refresh"===t?`Looked at ${e.done.toLocaleString()} so far — stops once it reaches people already saved`:`${e.done.toLocaleString()} of ${e.total.toLocaleString()} connections \xb7 ${o}%`,(0,n.jsxs)("div",{style:{margin:"8px 0 6px"},children:["refresh"!==t&&(0,n.jsx)("div",{role:"progressbar","aria-valuemin":0,"aria-valuemax":e.total,"aria-valuenow":e.done,"aria-label":i,style:{height:8,borderRadius:4,overflow:"hidden",background:"rgba(255,255,255,0.08)",marginBottom:6},children:(0,n.jsx)("div",{style:{width:`${o}%`,height:"100%",borderRadius:4,background:"linear-gradient(90deg, #9B59B6, #3498DB)",transition:"width 0.6s ease"}})}),(0,n.jsx)("div",{style:{fontSize:12.5,color:"#cfe6f7"},children:i})]})}function _(){return(0,n.jsx)("span",{style:{width:11,height:11,borderRadius:"50%",display:"inline-block",border:"2px solid rgba(255,255,255,0.2)",borderTopColor:"#3498DB",animation:"spin 0.8s linear infinite"}})}e.s(["default",0,function(){return(0,n.jsx)(o.default,{children:(0,n.jsx)(t.Suspense,{children:(0,n.jsx)(D,{})})})}],68863)}]);
|
|
Binary file
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.8] - 2026-09-29
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **A new icon.** A 6 drawn in one gold line around you, a glowing core, ending in a degree
|
|
13
|
+
ring, with your tier rings fading behind it. It's the app's icon, the favicon and the
|
|
14
|
+
website's.
|
|
15
|
+
- **The website is a proper site now, and the README matches.** The home page tells one story,
|
|
16
|
+
feature by feature, each with a real screenshot of the invented sample network: see your
|
|
17
|
+
network, find your way in, know your network, watch it grow, and scan carefully, then privacy,
|
|
18
|
+
a fair comparison with LinkedIn's own search, spreadsheets and CRMs, and SocNetV and Gephi,
|
|
19
|
+
and a strip of true, checkable numbers. It has one download button, for your computer: the Mac
|
|
20
|
+
download on a Mac, and on Linux and Windows a dimmed *Coming soon* button, never a link, with
|
|
21
|
+
`npx six-degrees` offered on Linux. New pages: **/download/** (every platform, install steps,
|
|
22
|
+
checksums, requirements, troubleshooting), **/docs/** (getting started, your data, privacy,
|
|
23
|
+
questions), **/releases/** (every version in this changelog), **/blog/** with four posts on how
|
|
24
|
+
it works, **/roadmap/** and **/about/**, with Atom feeds for the blog and releases and every
|
|
25
|
+
page in the sitemap. `scripts/build-site.mjs` builds it all from `site/`, and the website's
|
|
26
|
+
deploy runs it, so the news and numbers never go stale (`site/README.md` says how to add a
|
|
27
|
+
post or a page). The README has badges, a download table for all three platforms, features,
|
|
28
|
+
latest news and why Six Degrees.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **Paths opens about 9× faster on a large network** (Map 19.8 s → 2.2 s, Industries 25.8 s →
|
|
32
|
+
1.7 s, at 1,500 connections and 25,000 people in their circles). Each headline and company
|
|
33
|
+
name is now read once, not once per pass: Paths read everyone's headline five or more times,
|
|
34
|
+
and each read tries the whole company list. The same fix makes Scores open in under a
|
|
35
|
+
second (was 11 s), and setting a company's score rescores everyone in a third of a second
|
|
36
|
+
(was 6 s). Scores themselves are unchanged.
|
|
37
|
+
- **Opening a company's path to the top in Paths no longer hangs.** Every row compared itself
|
|
38
|
+
with everyone at the company each time the page drew: at a company of 1,900 people that took
|
|
39
|
+
minutes. It now opens in a fifth of a second, and a row works out what adding them would do
|
|
40
|
+
only when you open it.
|
|
41
|
+
|
|
42
|
+
## [0.4.7] - 2026-09-29
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- **Hide the Chrome window while scanning** (Scan page). Scans run with no window popping up;
|
|
46
|
+
the status bar and Stop work as before, and signing in always opens the window. The Scan page
|
|
47
|
+
says the risks: a hidden Chrome is easier for LinkedIn to tell from a person, and a check
|
|
48
|
+
LinkedIn asks for (a code, a puzzle, signing in again) can't be seen, so the scan stops instead
|
|
49
|
+
of waiting. Off by default.
|
|
50
|
+
|
|
9
51
|
## [0.4.6] - 2026-09-29
|
|
10
52
|
|
|
11
53
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,24 +1,68 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://sixdegreesapp.com/"><img src="desktop/icon/icon.svg" width="
|
|
2
|
+
<a href="https://sixdegreesapp.com/"><img src="desktop/icon/icon.svg" width="112" height="112" alt="Six Degrees website"></a>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">Six Degrees</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<
|
|
9
|
-
and
|
|
8
|
+
<strong>See your LinkedIn network as a galaxy.</strong><br>
|
|
9
|
+
Who can introduce you, who only one of your connections can reach, and the shortest path to<br>
|
|
10
|
+
someone you haven't met. Free, open source, and it runs on your own computer.
|
|
10
11
|
</p>
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://github.com/blakeb056/six-degrees/releases/latest"><img src="https://img.shields.io/github/v/release/blakeb056/six-degrees?label=release&color=ffd700" alt="Latest release"></a>
|
|
15
|
+
<a href="https://github.com/blakeb056/six-degrees/releases/latest"><img src="https://img.shields.io/github/release-date/blakeb056/six-degrees?label=released&color=3ee08f" alt="Release date"></a>
|
|
16
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/blakeb056/six-degrees?color=9b59b6" alt="MIT licence"></a>
|
|
17
|
+
<a href="https://www.npmjs.com/package/six-degrees"><img src="https://img.shields.io/npm/v/six-degrees?label=npm&color=3498db" alt="npm version"></a>
|
|
18
|
+
<a href="https://github.com/blakeb056/six-degrees/actions/workflows/ci.yml"><img src="https://github.com/blakeb056/six-degrees/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
19
|
+
</p>
|
|
13
20
|
|
|
14
|
-
<p align="center"
|
|
21
|
+
<p align="center">
|
|
22
|
+
<a href="https://sixdegreesapp.com/"><strong>Website</strong></a> ·
|
|
23
|
+
<a href="#download">Download</a> ·
|
|
24
|
+
<a href="#features">Features</a> ·
|
|
25
|
+
<a href="#latest-news">News</a> ·
|
|
26
|
+
<a href="#why-six-degrees">Why Six Degrees</a> ·
|
|
27
|
+
<a href="#docs">Docs</a> ·
|
|
28
|
+
<a href="https://sixdegreesapp.com/blog/">Blog</a> ·
|
|
29
|
+
<a href="https://sixdegreesapp.com/roadmap/">Roadmap</a> ·
|
|
30
|
+
<a href="#contributing">Contributing</a>
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
<a href="https://sixdegreesapp.com/"><img src="site/img/og-image.jpg" alt="Six Degrees on a Mac: a sample network drawn as rings around you, the most powerful people closest" width="100%"></a>
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
<p align="center"><sub>Every person shown in this README is invented — see <a href="scripts/gen-synthetic.mjs"><code>gen-synthetic.mjs</code></a>.</sub></p>
|
|
38
|
+
|
|
39
|
+
Your network has a shape, and you can't see it. Six Degrees draws it: everyone you
|
|
40
|
+
know placed on rings by how much they can open up for you, the handful of people
|
|
41
|
+
whose own circles reach the furthest, and the shortest chain from you to a stranger
|
|
42
|
+
worth meeting.
|
|
43
|
+
|
|
44
|
+
It's a Mac app (or `npx six-degrees` on Linux). It runs on your computer, against your own data, with no account and
|
|
45
|
+
no server.
|
|
46
|
+
|
|
47
|
+
## Download
|
|
15
48
|
|
|
16
49
|
<p align="center">
|
|
17
50
|
<a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Apple-Silicon.dmg"><img src="docs/img/download-apple-silicon.png" width="346" alt="Download Six Degrees for a Mac with Apple Silicon (M1 or newer)"></a>
|
|
18
51
|
<a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Intel.dmg"><img src="docs/img/download-intel.png" width="346" alt="Download Six Degrees for a Mac with an Intel chip"></a>
|
|
19
52
|
</p>
|
|
20
53
|
|
|
21
|
-
|
|
54
|
+
| Platform | Download | Needs |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| **macOS**, Apple Silicon (M1 or newer) | [Six-Degrees-Mac-Apple-Silicon.dmg](https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Apple-Silicon.dmg) | macOS 13.5 (Ventura) or later |
|
|
57
|
+
| **macOS**, Intel | [Six-Degrees-Mac-Intel.dmg](https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Intel.dmg) | macOS 13.5 (Ventura) or later |
|
|
58
|
+
| **Linux** | App: *coming soon.* Run it today from the Terminal: `npx six-degrees` ([how](https://sixdegreesapp.com/download/#linux)) | Node 22.13 or later |
|
|
59
|
+
| **Windows** | *Coming soon.* [Watch releases](https://github.com/blakeb056/six-degrees) (Watch → Custom → Releases) to hear when it's out | — |
|
|
60
|
+
|
|
61
|
+
<sub>Every release is on [GitHub Releases](https://github.com/blakeb056/six-degrees/releases) with a
|
|
62
|
+
`SHA256SUMS` file ([latest](https://github.com/blakeb056/six-degrees/releases/latest/download/SHA256SUMS)); the Terminal install below checks the download against it.
|
|
63
|
+
The links above always fetch the newest release.</sub>
|
|
64
|
+
|
|
65
|
+
## Install it
|
|
22
66
|
|
|
23
67
|
**Which download?** Apple menu → **About This Mac**. If it says "Chip: Apple M…", take
|
|
24
68
|
**Apple Silicon**. If it says "Processor: …Intel…", take **Intel**. It needs **macOS 13.5
|
|
@@ -109,21 +153,104 @@ Scanning LinkedIn also needs Google Chrome, and Python 3.10 to 3.14 or the Scan
|
|
|
109
153
|
**Set up the scanner** (see below).
|
|
110
154
|
</details>
|
|
111
155
|
|
|
112
|
-
##
|
|
156
|
+
## Features
|
|
113
157
|
|
|
114
|
-
|
|
115
|
-
|
|
158
|
+
**Network Circle.** Everyone you know, on rings by tier, the most powerful closest to
|
|
159
|
+
you. Switch the drawing between **Galaxy**, **Pyramid** and **List** in the panel on
|
|
160
|
+
the left.
|
|
161
|
+
|
|
162
|
+
**Degrees** *(needs a scan or the sample)*. The people two steps away. **Separation**
|
|
163
|
+
ranks every one of them with every way in: which of your connections knows them, and
|
|
164
|
+
how many do. **Orbit**, **Bridge Chains**, **Pyramid** and **List** draw the same people
|
|
165
|
+
differently.
|
|
166
|
+
|
|
167
|
+
<img src="docs/img/degrees.png" alt="Degrees → Separation: people two steps away, ranked, each with the connection who can introduce you" width="100%">
|
|
168
|
+
|
|
169
|
+
**Paths.** Your network by company and industry, in the spirit of LinkedIn's old
|
|
170
|
+
InMaps:
|
|
171
|
+
- **Map**: your companies as bubbles (the 140 largest), grouped by industry and linked
|
|
172
|
+
where your connections at one know people at another.
|
|
173
|
+
- **Industries**: a card per industry.
|
|
174
|
+
- **Companies**: pick one to see its people level by level, up to the top.
|
|
175
|
+
|
|
176
|
+
Click a company for its analyzer: who you know there, who you can reach, the warmest way
|
|
177
|
+
in, and its most powerful people.
|
|
178
|
+
|
|
179
|
+
<img src="docs/img/paths.png" alt="Paths → Map with the company analyzer open: companies grouped by industry, and one company's people by level and ways in" width="100%">
|
|
180
|
+
|
|
181
|
+
**Scores.** How a power score is worked out, and the three things you can change about
|
|
182
|
+
it, in one place: your field (the sectors you work in), how tiers are graded (on your
|
|
183
|
+
network's curve or the fixed scale), and every scanned company's score.
|
|
184
|
+
|
|
185
|
+
**Outlink** *(needs a scan)*. Getting introduced, as a game. Each mapped connection's
|
|
186
|
+
circle offers its best five people at a time; mark invites as sent to fill the ring and
|
|
187
|
+
unlock the next five. It also shows the next best moves, levels and points (from
|
|
188
|
+
invites sent and people who accepted, never from browsing).
|
|
189
|
+
|
|
190
|
+
**Scan.** The guided scanner. It shows progress, the budget that's left, the cooldown
|
|
191
|
+
lock, and a **Paused** list you can carry on from.
|
|
192
|
+
|
|
193
|
+
**Also in the app:**
|
|
194
|
+
- **Who only one connection reaches.** For every connection whose circle you've scanned,
|
|
195
|
+
the people none of your other connections reach: in Bridge Chains, and in the
|
|
196
|
+
*Insights* on their card, with *Show them* to list exactly who.
|
|
197
|
+
- **Network health** (Scores). How much of your 2nd degree you reach two or more ways,
|
|
198
|
+
your effective reach, and the five connections who reach the most people no one else
|
|
199
|
+
does. Only your own numbers, never a percentile against other people.
|
|
200
|
+
- **A physics lab for the Galaxy** (experimental, in Filters). Sliders for gravity, rings,
|
|
201
|
+
push, pull and distance; colour by tier, degree, company or warmth; find anyone; and a
|
|
202
|
+
**replay** of your network growing by the date you connected, saved as a picture or a
|
|
203
|
+
video.
|
|
204
|
+
- **Social** (experimental). From your own LinkedIn data: who's warm, dormant or waiting
|
|
205
|
+
on a reply, and your career chapters. Message text is never kept.
|
|
206
|
+
|
|
207
|
+
## Latest news
|
|
208
|
+
|
|
209
|
+
<p>
|
|
210
|
+
<a href="https://github.com/blakeb056/six-degrees/releases/latest"><img src="https://img.shields.io/github/v/release/blakeb056/six-degrees?label=latest&color=ffd700" alt="Latest release"></a>
|
|
211
|
+
<a href="https://github.com/blakeb056/six-degrees/releases/latest"><img src="https://img.shields.io/github/release-date/blakeb056/six-degrees?label=released&color=3ee08f" alt="Release date"></a>
|
|
116
212
|
</p>
|
|
117
213
|
|
|
118
|
-
|
|
214
|
+
Six Degrees ships often, and every release says what it added, changed and fixed:
|
|
119
215
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
216
|
+
- **[Releases](https://github.com/blakeb056/six-degrees/releases)**: each version's
|
|
217
|
+
downloads and notes ([Atom feed](https://github.com/blakeb056/six-degrees/releases.atom)).
|
|
218
|
+
- **[CHANGELOG.md](CHANGELOG.md)**: everything, newest first, including what's
|
|
219
|
+
coming in the next release.
|
|
220
|
+
- **[Release notes on the website](https://sixdegreesapp.com/releases/)**: every version,
|
|
221
|
+
built from the changelog each time the site is published, with a
|
|
222
|
+
[feed](https://sixdegreesapp.com/releases/feed.xml).
|
|
223
|
+
- **[The blog](https://sixdegreesapp.com/blog/)**: how it works, and why.
|
|
124
224
|
|
|
125
|
-
|
|
126
|
-
|
|
225
|
+
## Why Six Degrees
|
|
226
|
+
|
|
227
|
+
A connections list tells you who you know. Six Degrees shows what that network can do:
|
|
228
|
+
|
|
229
|
+
- **Who can introduce you, ranked.** Everyone two steps away, each with every way in.
|
|
230
|
+
- **Who only one connection reaches.** Where a single person is your only door.
|
|
231
|
+
- **Your network's health.** How much of it you reach more than one way.
|
|
232
|
+
- **A physics lab and a replay.** Tune the Galaxy's layout, and watch your network grow.
|
|
233
|
+
- **All on your own computer.** No account, no server, no telemetry.
|
|
234
|
+
|
|
235
|
+
In the sample network that ships with the app (150 invented connections), 598 people are
|
|
236
|
+
two steps away, and 503 of them, 84%, are reached through only one connection.
|
|
237
|
+
|
|
238
|
+
**How it compares**, as fairly as we can put it
|
|
239
|
+
([the full table](https://sixdegreesapp.com/#compare)):
|
|
240
|
+
|
|
241
|
+
| | Six Degrees | LinkedIn's own search | Spreadsheets and CRMs | Network tools (SocNetV, Gephi) |
|
|
242
|
+
|---|---|---|---|---|
|
|
243
|
+
| Your connections, from LinkedIn's export | ✓ | ✓ | ✓ | as nodes, with no ties |
|
|
244
|
+
| Everyone two steps away, with who can introduce you | ✓ (needs a scan, or the sample) | 2nd-degree search, mutual connections on each | — (the export is 1st degree only) | if you bring the ties |
|
|
245
|
+
| Who only one connection reaches | ✓ | not shown | — | brokerage measures, on your data |
|
|
246
|
+
| A score per person, with the working shown | ✓ | not shown | your own formulas | general centrality |
|
|
247
|
+
| A layout you can tune, and a replay by date | ✓ (experimental) | — | — | ✓ many layouts; Gephi's timeline |
|
|
248
|
+
| Any network, any data, research-grade statistics | — (LinkedIn networks only) | — | any data | ✓ what they're built for |
|
|
249
|
+
| Runs on your computer, with no account | ✓ | — (online, with your account) | varies | ✓ |
|
|
250
|
+
| Price | free, MIT | free, with paid plans | free to paid | free, open source |
|
|
251
|
+
| Windows | coming soon | ✓ | ✓ | ✓ |
|
|
252
|
+
|
|
253
|
+
Something out of date, or unfair to another tool? [Open an issue](https://github.com/blakeb056/six-degrees/issues).
|
|
127
254
|
|
|
128
255
|
## Before you scan LinkedIn
|
|
129
256
|
|
|
@@ -179,43 +306,6 @@ While the sample or a CSV is loaded, the top bar shows *Sample network ×* or *Y
|
|
|
179
306
|
×* (click × to go back), and Outlink and Scan are hidden. Neither is ever added to your
|
|
180
307
|
network: they last as long as the app's window is open.
|
|
181
308
|
|
|
182
|
-
## What it does
|
|
183
|
-
|
|
184
|
-
**Network Circle.** Everyone you know, on rings by tier, the most powerful closest to
|
|
185
|
-
you. Switch the drawing between **Galaxy**, **Orbit**, **Pyramid** and **List** in the
|
|
186
|
-
panel on the left.
|
|
187
|
-
|
|
188
|
-
**Degrees** *(needs a scan or the sample)*. The people two steps away. **Separation**
|
|
189
|
-
ranks every one of them with every way in: which of your connections knows them, and
|
|
190
|
-
how many do. **Orbit**, **Bridge Chains**, **Pyramid** and **List** draw the same people
|
|
191
|
-
differently.
|
|
192
|
-
|
|
193
|
-
<img src="docs/img/degrees.png" alt="Degrees → Separation: people two steps away, ranked, each with the connection who can introduce you" width="100%">
|
|
194
|
-
|
|
195
|
-
**Paths.** Your network by company and industry, in the spirit of LinkedIn's old
|
|
196
|
-
InMaps:
|
|
197
|
-
- **Map**: your companies as bubbles (the 140 largest), grouped by industry and linked
|
|
198
|
-
where your connections at one know people at another.
|
|
199
|
-
- **Industries**: a card per industry.
|
|
200
|
-
- **Companies**: pick one to see its people level by level, up to the top.
|
|
201
|
-
|
|
202
|
-
Click a company for its analyzer: who you know there, who you can reach, the warmest way
|
|
203
|
-
in, and its most powerful people.
|
|
204
|
-
|
|
205
|
-
<img src="docs/img/paths.png" alt="Paths → Map with the company analyzer open: companies grouped by industry, and one company's people by level and ways in" width="100%">
|
|
206
|
-
|
|
207
|
-
**Scores.** How a power score is worked out, and the three things you can change about
|
|
208
|
-
it, in one place: your field (the sectors you work in), how tiers are graded (on your
|
|
209
|
-
network's curve or the fixed scale), and every scanned company's score.
|
|
210
|
-
|
|
211
|
-
**Outlink** *(needs a scan)*. Getting introduced, as a game. Each mapped connection's
|
|
212
|
-
circle offers its best five people at a time; mark invites as sent to fill the ring and
|
|
213
|
-
unlock the next five. It also shows the next best moves, levels and points (from
|
|
214
|
-
invites sent and people who accepted, never from browsing).
|
|
215
|
-
|
|
216
|
-
**Scan.** The guided scanner. It shows progress, the budget that's left, the cooldown
|
|
217
|
-
lock, and a **Paused** list you can carry on from.
|
|
218
|
-
|
|
219
309
|
## How scoring works
|
|
220
310
|
|
|
221
311
|
```
|
|
@@ -403,11 +493,33 @@ Nothing to configure. Two optional environment variables exist:
|
|
|
403
493
|
| `SIX_DEGREES_HOME` | Where your data lives. Defaults to `~/.six-degrees`. It's read at launch, so it applies to `npx six-degrees` and source runs (npx also takes `--data-dir`; see `npx six-degrees --help`), not when the Mac app is opened from the Dock or Finder. A relative folder is taken from the folder you run the command in. |
|
|
404
494
|
| `ADMIN_TOKEN` | Not needed on your own computer. The app listens only on 127.0.0.1 and isn't built to be exposed: don't put it behind a tunnel or bind it to another address ([SECURITY.md](SECURITY.md)). If it's ever bound elsewhere, the ten routes that delete, replace or hand over your data, run the scanner, open the data folder or update a copy run from source refuse every caller without this token, including the app's own buttons. Everything else, including reading your whole network, stays open. |
|
|
405
495
|
|
|
496
|
+
## Docs
|
|
497
|
+
|
|
498
|
+
| You want | Read |
|
|
499
|
+
|---|---|
|
|
500
|
+
| To install it, step by step | [Install it](#install-it), or [the website's guide](https://sixdegreesapp.com/download/#install) with videos |
|
|
501
|
+
| How scanning works, and what it risks | [docs/SCRAPING.md](docs/SCRAPING.md) |
|
|
502
|
+
| How a power score is worked out | [How scoring works](#how-scoring-works), and [docs/brain/SCORING.md](docs/brain/SCORING.md) |
|
|
503
|
+
| Where your data lives, moving computers | [How your data is stored](#how-your-data-is-stored) |
|
|
504
|
+
| The security model, and reporting a problem | [SECURITY.md](SECURITY.md) |
|
|
505
|
+
| What changed in each version | [CHANGELOG.md](CHANGELOG.md), or the [release notes](https://sixdegreesapp.com/releases/) on the website |
|
|
506
|
+
| How it works, and why | [The blog](https://sixdegreesapp.com/blog/): who can introduce you, local-first, the physics lab, scanning slowly |
|
|
507
|
+
| What's next | [The roadmap](https://sixdegreesapp.com/roadmap/) |
|
|
508
|
+
| The website itself | [site/README.md](site/README.md): previewing it, adding a post or a page |
|
|
509
|
+
| To change the code | [CONTRIBUTING.md](CONTRIBUTING.md), then [docs/brain/](docs/brain/00-START-HERE.md) |
|
|
510
|
+
|
|
406
511
|
## Contributing
|
|
407
512
|
|
|
408
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
409
|
-
|
|
410
|
-
|
|
513
|
+
Bug reports, ideas and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
514
|
+
to run it from source, and the [code of conduct](CODE_OF_CONDUCT.md). Report a security
|
|
515
|
+
problem privately, through
|
|
516
|
+
[GitHub's security advisories](https://github.com/blakeb056/six-degrees/security/advisories/new)
|
|
517
|
+
([SECURITY.md](SECURITY.md)), not in an issue.
|
|
518
|
+
|
|
519
|
+
The rule that matters most: **never commit real network data**. That means no exported
|
|
520
|
+
CSVs, no avatars, and no screenshots of real people. When you file an
|
|
521
|
+
[issue](https://github.com/blakeb056/six-degrees/issues), redact names before attaching
|
|
522
|
+
anything.
|
|
411
523
|
|
|
412
524
|
## License
|
|
413
525
|
|
package/lib/scoring.js
CHANGED
|
@@ -58,6 +58,31 @@
|
|
|
58
58
|
// themselves are tables, fingerprinted below.)
|
|
59
59
|
export const SCORING_VERSION = 6;
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* A function of one piece of text, remembered: the same text gets the answer
|
|
63
|
+
* it got the first time, without the work again. Reading a headline or a
|
|
64
|
+
* company name runs hundreds of patterns (every name on the curated list and
|
|
65
|
+
* the public dataset is tried against it), and a network hands over the same
|
|
66
|
+
* text again and again: everyone at a company writes its name, and a page
|
|
67
|
+
* groups, links and filters the same people several times over. At 30,000
|
|
68
|
+
* people each of those passes took seconds, and Paths 10 to 20 to open.
|
|
69
|
+
*
|
|
70
|
+
* Only for a function whose answer depends on nothing but the text, and whose
|
|
71
|
+
* answer nobody changes afterwards (readRoles hands out copies for that). It
|
|
72
|
+
* keeps up to `max` answers and drops the oldest first, so it can't grow
|
|
73
|
+
* without end on a server that stays up for weeks.
|
|
74
|
+
*/
|
|
75
|
+
export function rememberByText(read, max = 100000) {
|
|
76
|
+
const answers = new Map();
|
|
77
|
+
return (text) => {
|
|
78
|
+
if (answers.has(text)) return answers.get(text);
|
|
79
|
+
const answer = read(text);
|
|
80
|
+
if (answers.size >= max) answers.delete(answers.keys().next().value);
|
|
81
|
+
answers.set(text, answer);
|
|
82
|
+
return answer;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
// ── title ────────────────────────────────────────────────────────────────────
|
|
62
87
|
|
|
63
88
|
export const LEVELS = {
|
|
@@ -1008,8 +1033,16 @@ function heldAfterComma(part) {
|
|
|
1008
1033
|
* An audience of their own is a role of its own (`audience`), with no company.
|
|
1009
1034
|
*/
|
|
1010
1035
|
export function readRoles(headline = '', role = '') {
|
|
1036
|
+
// Copies, since rolesWithCompanies fills in a role's company on what it gets.
|
|
1037
|
+
return rolesIn(String(headline || role || '')).map((r) => ({ ...r, title: { ...r.title } }));
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Only the text is read (the headline, else the role), so each is read once.
|
|
1041
|
+
const rolesIn = rememberByText(readRolesOf);
|
|
1042
|
+
|
|
1043
|
+
function readRolesOf(text) {
|
|
1011
1044
|
const out = [];
|
|
1012
|
-
const parts = headlineParts(
|
|
1045
|
+
const parts = headlineParts(text);
|
|
1013
1046
|
let last = null; // the last role a title gave: { role, i, rank }
|
|
1014
1047
|
let retiredNext = false; // a lone "Retired" before any role: the next one's
|
|
1015
1048
|
for (const [i, raw] of parts.entries()) {
|
|
@@ -2033,6 +2066,14 @@ const PLATFORMS = new Set(['tech', 'media', 'entertainment']);
|
|
|
2033
2066
|
|
|
2034
2067
|
/** A company name as people write it → one clean, canonical name (or null). */
|
|
2035
2068
|
export function cleanCompany(raw) {
|
|
2069
|
+
return cleanNameOf(String(raw || ''));
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
// Read once per name: everyone at a company writes it, and each read tries the
|
|
2073
|
+
// whole curated list and public dataset against it.
|
|
2074
|
+
const cleanNameOf = rememberByText(readCompanyName);
|
|
2075
|
+
|
|
2076
|
+
function readCompanyName(raw) {
|
|
2036
2077
|
// NFKC first: styled letters ("𝗠𝗶𝗰𝗿𝗼𝘀𝗼𝗳𝘁") and full-width punctuation ("|")
|
|
2037
2078
|
// become plain ones. Private-use glyphs go with the emoji: the Apple logo
|
|
2038
2079
|
// () after "Apple" used to leave it unread.
|
package/lib/scraper-client.js
CHANGED
|
@@ -211,7 +211,11 @@ export async function beginScrape(action, { name, id, onLog, ...extra } = {}) {
|
|
|
211
211
|
});
|
|
212
212
|
let started;
|
|
213
213
|
try {
|
|
214
|
-
|
|
214
|
+
// Scan page → "Hide the Chrome window while scanning", remembered in this
|
|
215
|
+
// browser, applies to every scan however it was started.
|
|
216
|
+
let hidden = false;
|
|
217
|
+
try { hidden = JSON.parse(localStorage.getItem('six-degrees-hide-chrome') || 'false') === true; } catch {}
|
|
218
|
+
started = await startScrape(action, name, { ...(id ? { id } : {}), ...(hidden ? { headless: true } : {}), ...extra });
|
|
215
219
|
} catch (err) {
|
|
216
220
|
// Refused, often because something else runs: ask what, straight away.
|
|
217
221
|
// Until the answer comes, only our own claim is taken back.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "six-degrees",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
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",
|