six-degrees 0.2.0 → 0.2.1
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 +11 -11
- 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 +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +9 -9
- 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/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/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/setup-profile/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/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 +13 -13
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
- 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 +11 -11
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
- 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 +13 -13
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
- 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 +11 -11
- 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 +13 -13
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
- 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 +11 -11
- 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 +13 -13
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
- 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 +11 -11
- 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 +13 -13
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
- 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 +11 -11
- 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 +13 -13
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
- 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 +11 -11
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jrmmj-._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qx548g._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1an3leo._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1m8zedk._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +2 -2
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h1_f_2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bf3x4q._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1djpk8t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.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_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
- 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/0jk64vo17avsb.js +1 -0
- package/.next/standalone/.next/static/chunks/0s65dsrthylo6.js +2 -0
- package/.next/standalone/.next/static/chunks/0uyfdhi8dek85.js +16 -0
- package/.next/standalone/.next/static/chunks/{1zmpcmngg5c7-.js → 229haorf0f19b.js} +1 -1
- package/.next/{static/chunks/3ts-_4i6bfkh2.js → standalone/.next/static/chunks/2g54uafnwuakc.js} +1 -1
- package/.next/standalone/.next/static/chunks/2ot61qsv6_0eq.js +1 -0
- package/.next/{static/chunks/3pfyuezyqd9ob.js → standalone/.next/static/chunks/2spwqs6b6dx13.js} +1 -1
- package/.next/{static/chunks/1odow92u6wxz0.js → standalone/.next/static/chunks/38dlepuubiox0.js} +1 -1
- package/.next/standalone/AGENTS.md +10 -0
- package/.next/standalone/CHANGELOG.md +41 -0
- package/.next/standalone/CONTRIBUTING.md +9 -0
- package/.next/standalone/README.md +49 -29
- package/.next/standalone/SECURITY.md +12 -9
- package/.next/standalone/app/api/scraper/route.js +4 -3
- package/.next/standalone/app/api/update/route.js +10 -2
- package/.next/standalone/app/components/ForceGraph.js +1 -1
- package/.next/standalone/app/components/Sidebar.js +8 -8
- package/.next/standalone/app/components/UpdatePanel.js +39 -8
- package/.next/standalone/app/import/page.js +3 -3
- package/.next/standalone/app/layout.js +2 -2
- package/.next/standalone/app/page.js +2 -2
- package/.next/standalone/app/paths/page.js +1 -1
- package/.next/standalone/app/profile/page.js +0 -178
- package/.next/standalone/app/setup/page.js +4 -4
- package/.next/standalone/bin/six-degrees.mjs +7 -2
- package/.next/standalone/install.sh +1 -1
- package/.next/standalone/lib/csv.js +53 -27
- package/.next/standalone/lib/release.js +5 -2
- package/.next/standalone/lib/scraper-client.js +3 -3
- package/.next/standalone/package-lock.json +103 -9
- package/.next/standalone/package.json +12 -10
- package/.next/standalone/public/demo-data.json +1 -1
- package/.next/standalone/scripts/gen-synthetic.mjs +16 -1
- package/.next/standalone/scripts/readme_buttons.py +89 -0
- package/.next/standalone/scripts/screenshots.py +2 -0
- package/.next/standalone/site/app.js +84 -0
- package/.next/standalone/site/index.html +221 -0
- package/.next/standalone/site/style.css +155 -0
- package/.next/standalone/site/video/install-mac-poster.jpg +0 -0
- package/.next/standalone/site/video/install-mac.mp4 +0 -0
- package/.next/standalone/site/video/linux-poster.jpg +0 -0
- package/.next/standalone/site/video/linux.mp4 +0 -0
- package/.next/standalone/site/video/scan-poster.jpg +0 -0
- package/.next/standalone/site/video/scan.mp4 +0 -0
- package/.next/static/chunks/0jk64vo17avsb.js +1 -0
- package/.next/static/chunks/0s65dsrthylo6.js +2 -0
- package/.next/static/chunks/0uyfdhi8dek85.js +16 -0
- package/.next/static/chunks/{1zmpcmngg5c7-.js → 229haorf0f19b.js} +1 -1
- package/.next/{standalone/.next/static/chunks/3ts-_4i6bfkh2.js → static/chunks/2g54uafnwuakc.js} +1 -1
- package/.next/static/chunks/2ot61qsv6_0eq.js +1 -0
- package/.next/{standalone/.next/static/chunks/3pfyuezyqd9ob.js → static/chunks/2spwqs6b6dx13.js} +1 -1
- package/.next/{standalone/.next/static/chunks/1odow92u6wxz0.js → static/chunks/38dlepuubiox0.js} +1 -1
- package/CHANGELOG.md +41 -0
- package/README.md +49 -29
- package/SECURITY.md +12 -9
- package/bin/six-degrees.mjs +7 -2
- package/lib/csv.js +53 -27
- package/lib/release.js +5 -2
- package/lib/scraper-client.js +3 -3
- package/package.json +12 -10
- package/public/demo-data.json +1 -1
- package/.next/standalone/.next/static/chunks/1wb_is4wsdm-u.js +0 -1
- package/.next/standalone/.next/static/chunks/2rrf5kzx2fhin.js +0 -2
- package/.next/standalone/.next/static/chunks/2s_k9x4d0gi9h.js +0 -16
- package/.next/standalone/.next/static/chunks/3hsi90cejq2dg.js +0 -1
- package/.next/static/chunks/1wb_is4wsdm-u.js +0 -1
- package/.next/static/chunks/2rrf5kzx2fhin.js +0 -2
- package/.next/static/chunks/2s_k9x4d0gi9h.js +0 -16
- package/.next/static/chunks/3hsi90cejq2dg.js +0 -1
- /package/.next/standalone/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_ssgManifest.js +0 -0
- /package/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_buildManifest.js +0 -0
- /package/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_ssgManifest.js +0 -0
package/.next/{standalone/.next/static/chunks/1odow92u6wxz0.js → static/chunks/38dlepuubiox0.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,2776,e=>{"use strict";var t=e.i(43476),r=e.i(15812);e.s(["default",0,function({children:e}){let{userId:n,ready:o}=(0,r.useUser)();return o&&n?e:(0,t.jsx)("div",{style:{minHeight:"100vh",display:"flex",alignItems:"center",justifyContent:"center",background:"#0a0a1a",color:"#fff",padding:24,textAlign:"center",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'},children:o?(0,t.jsxs)("div",{style:{maxWidth:420},children:[(0,t.jsx)("div",{style:{fontSize:18,fontWeight:700,marginBottom:8},children:"Can’t reach 6 Degrees’ local server"}),(0,t.jsx)("div",{style:{color:"#888",fontSize:14,lineHeight:1.6},children:"The page loaded but the app behind it is not answering. If you started it from a terminal, check that window for an error, then reload this page."})]}):(0,t.jsx)("div",{style:{fontSize:18,fontWeight:600},children:"Loading…"})})}])},19821,e=>{"use strict";async function t(e){let t=e?`/api/network?userId=${encodeURIComponent(e)}`:"/api/network",r=await fetch(t);if(!r.ok)throw Error(`Failed to load network (${r.status})`);let n=await r.json();return{degree1:n.degree1||[],degree2:n.degree2||[],degree3:n.degree3||[]}}e.s(["loadNetwork",0,t])},40996,e=>{"use strict";async function t(){let e=await fetch("/api/scraper");if(!e.ok)throw Error("Could not reach the app.");return e.json()}async function r(e,t,n={}){let o=await fetch("/api/scraper",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:e,...t?{name:t}:{},...n})}),a=await o.json().catch(()=>({}));if(!o.ok)throw Error(a.error||"Could not start the scraper.");return a}async function n(){await fetch("/api/scraper",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"cancel"})})}e.s(["notReadyMessage",0,function(e){return e?e.checks?.scriptsFound?e.checks?.python?e.checks?.dependencies?e.checks?.chrome?null:"Google Chrome is not installed.":"The scraper is not installed yet — open Scan to set it up.":"Python 3 is not installed on this machine.":"The scraper files are missing from this install.":"Could not reach the app."},"runScrape",0,function(e,{name:n,onLog:o,...a}={}){return new Promise((i,u)=>{r(e,n,a).then(()=>{let e=setInterval(async()=>{try{let r=await t();o?.(r.log||[]),r.running||(clearInterval(e),i(r))}catch{}},1500)}).catch(u)})},"scraperStatus",0,t,"startScrape",0,r,"stopScrape",0,n])},5258,e=>{"use strict";e.s(["TIER_COLORS",0,{S:"#f6c344",A:"#a78bfa",B:"#60a5fa",C:"#94a3b8",D:"#6b7280"},"TIER_COLORS_CLASSIC",0,{S:"#FFD700",A:"#9B59B6",B:"#3498DB",C:"#95A5A6",D:"#BDC3C7"},"TIER_ORDER",0,["S","A","B","C","D"],"TIER_RING_RADIUS",0,{S:150,A:230,B:310,C:390,D:470},"initialsFor",0,function(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===t.length?"?":(t[0].charAt(0)+(t.length>1?t[t.length-1].charAt(0):"")).toUpperCase()}])},22016,(e,t,r)=>{"use strict";e.i(47167),Object.defineProperty(r,"__esModule",{value:!0});var n={default:function(){return m},useLinkStatus:function(){return S}};for(var o in n)Object.defineProperty(r,o,{enumerable:!0,get:n[o]});let a=e.r(90809),i=e.r(43476),u=a._(e.r(71645)),s=e.r(95057),l=e.r(8372),c=e.r(18581),f=e.r(18967),p=e.r(5550),d=e.r(88540),h=e.r(91949),y=e.r(73668),g=e.r(9396);function m(t){var r;let n,o,a,[m,S]=(0,u.useOptimistic)(h.IDLE_LINK_STATUS),v=(0,u.useRef)(null),{href:C,as:P,children:E,prefetch:O=null,passHref:_,replace:j,shallow:T,scroll:R,onClick:w,onMouseEnter:A,onTouchStart:I,legacyBehavior:k=!1,onNavigate:x,transitionTypes:N,ref:L,unstable_dynamicOnHover:U,...B}=t;n=E,k&&("string"==typeof n||"number"==typeof n)&&(n=(0,i.jsx)("a",{children:n}));let D=u.default.useContext(l.AppRouterContext),M=!1!==O,$=!1===O?"none":!0===O?"full":"auto",F="none"!==$?"auto"===$?g.FetchStrategy.PPR:g.FetchStrategy.Full:g.FetchStrategy.PPR,z="string"==typeof(r=P||C)?r:(0,s.formatUrl)(r);if(k){if(n?.$$typeof===Symbol.for("react.lazy"))throw Object.defineProperty(Error("`<Link legacyBehavior>` received a direct child that is either a Server Component, or JSX that was loaded with React.lazy(). This is not supported. Either remove legacyBehavior, or make the direct child a Client Component that renders the Link's `<a>` tag."),"__NEXT_ERROR_CODE",{value:"E863",enumerable:!1,configurable:!0});o=u.default.Children.only(n)}let K=k?o&&"object"==typeof o&&o.ref:L,W,J=u.default.useCallback(e=>(null!==D&&(v.current=(0,h.mountLinkInstance)(e,z,D,F,M,S,W)),()=>{v.current&&((0,h.unmountLinkForCurrentNavigation)(v.current),v.current=null),(0,h.unmountPrefetchableInstance)(e)}),[M,z,D,F,S,W]),G={ref:(0,c.useMergedRef)(J,K),onClick(t){k||"function"!=typeof w||w(t),k&&o.props&&"function"==typeof o.props.onClick&&o.props.onClick(t),!D||t.defaultPrevented||function(t,r,n,o,a,i,s,l="none"){if("u">typeof window){let c,{nodeName:f}=t.currentTarget;if("A"===f.toUpperCase()&&((c=t.currentTarget.getAttribute("target"))&&"_self"!==c||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||t.nativeEvent&&2===t.nativeEvent.which)||t.currentTarget.hasAttribute("download"))return;if(!(0,y.isLocalURL)(r)){o&&(t.preventDefault(),location.replace(r));return}if(t.preventDefault(),i){let e=!1;if(i({preventDefault:()=>{e=!0}}),e)return}let{dispatchNavigateAction:p}=e.r(99781);u.default.startTransition(()=>{p(r,o?"replace":"push",!1===a?d.ScrollBehavior.NoScroll:d.ScrollBehavior.Default,n.current,s,l)})}}(t,z,v,j,R,x,N,$)},onMouseEnter(e){k||"function"!=typeof A||A(e),k&&o.props&&"function"==typeof o.props.onMouseEnter&&o.props.onMouseEnter(e),D&&M&&(0,h.onNavigationIntent)(e.currentTarget,!0===U)},onTouchStart:function(e){k||"function"!=typeof I||I(e),k&&o.props&&"function"==typeof o.props.onTouchStart&&o.props.onTouchStart(e),D&&M&&(0,h.onNavigationIntent)(e.currentTarget,!0===U)}};return(0,f.isAbsoluteUrl)(z)?G.href=z:k&&!_&&("a"!==o.type||"href"in o.props)||(G.href=(0,p.addBasePath)(z)),a=k?u.default.cloneElement(o,G):(0,i.jsx)("a",{...B,...G,children:n}),(0,i.jsx)(b.Provider,{value:m,children:a})}let b=(0,u.createContext)(h.IDLE_LINK_STATUS),S=()=>(0,u.useContext)(b);("function"==typeof r.default||"object"==typeof r.default&&null!==r.default)&&void 0===r.default.__esModule&&(Object.defineProperty(r.default,"__esModule",{value:!0}),Object.assign(r.default,r),t.exports=r.default)},18581,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"useMergedRef",{enumerable:!0,get:function(){return o}});let n=e.r(71645);function o(e,t){let r=(0,n.useRef)(null),o=(0,n.useRef)(null);return(0,n.useCallback)(n=>{if(null===n){let e=r.current;e&&(r.current=null,e());let t=o.current;t&&(o.current=null,t())}else e&&(r.current=a(e,n)),t&&(o.current=a(t,n))},[e,t])}function a(e,t){if("function"!=typeof e)return e.current=t,()=>{e.current=null};{let r=e(t);return"function"==typeof r?r:()=>e(null)}}("function"==typeof r.default||"object"==typeof r.default&&null!==r.default)&&void 0===r.default.__esModule&&(Object.defineProperty(r.default,"__esModule",{value:!0}),Object.assign(r.default,r),t.exports=r.default)},18967,(e,t,r)=>{"use strict";e.i(47167),Object.defineProperty(r,"__esModule",{value:!0});var n={DecodeError:function(){return m},MiddlewareNotFoundError:function(){return C},MissingStaticPage:function(){return v},NormalizeError:function(){return b},PageNotFoundError:function(){return S},SP:function(){return y},ST:function(){return g},WEB_VITALS:function(){return a},execOnce:function(){return i},getDisplayName:function(){return f},getLocationOrigin:function(){return l},getURL:function(){return c},isAbsoluteUrl:function(){return s},isResSent:function(){return p},loadGetInitialProps:function(){return h},normalizeRepeatedSlashes:function(){return d},stringifyError:function(){return P}};for(var o in n)Object.defineProperty(r,o,{enumerable:!0,get:n[o]});let a=["CLS","FCP","FID","INP","LCP","TTFB"];function i(e){let t,r=!1;return(...n)=>(r||(r=!0,t=e(...n)),t)}let u=/^[a-zA-Z][a-zA-Z\d+\-.]*?:/,s=e=>{let t=e.charCodeAt(0);return!!(t>=65&&t<=90||t>=97&&t<=122)&&u.test(e)};function l(){let{protocol:e,hostname:t,port:r}=window.location;return`${e}//${t}${r?":"+r:""}`}function c(){let{href:e}=window.location,t=l();return e.substring(t.length)}function f(e){return"string"==typeof e?e:e.displayName||e.name||"Unknown"}function p(e){return e.finished||e.headersSent}function d(e){let t=e.split("?");return t[0].replace(/\\/g,"/").replace(/\/\/+/g,"/")+(t[1]?`?${t.slice(1).join("?")}`:"")}async function h(e,t){let r=t.res||t.ctx&&t.ctx.res;if(!e.getInitialProps)return t.ctx&&t.Component?{pageProps:await h(t.Component,t.ctx)}:{};let n=await e.getInitialProps(t);if(r&&p(r))return n;if(!n)throw Object.defineProperty(Error(`"${f(e)}.getInitialProps()" should resolve to an object. But found "${n}" instead.`),"__NEXT_ERROR_CODE",{value:"E1025",enumerable:!1,configurable:!0});return n}let y="u">typeof performance,g=y&&["mark","measure","getEntriesByName"].every(e=>"function"==typeof performance[e]);class m extends Error{}class b extends Error{}class S extends Error{constructor(e){super(),this.code="ENOENT",this.name="PageNotFoundError",this.message=`Cannot find module for page: ${e}`}}class v extends Error{constructor(e,t){super(),this.message=`Failed to load static file for page: ${e} ${t}`}}class C extends Error{constructor(){super(),this.code="ENOENT",this.message="Cannot find the middleware module"}}function P(e){return JSON.stringify({message:e.message,stack:e.stack})}},73668,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"isLocalURL",{enumerable:!0,get:function(){return a}});let n=e.r(18967),o=e.r(52817);function a(e){if(!(0,n.isAbsoluteUrl)(e))return!0;try{let t=(0,n.getLocationOrigin)(),r=new URL(e,t);return r.origin===t&&(0,o.hasBasePath)(r.pathname)}catch(e){return!1}}},98183,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n={assign:function(){return s},searchParamsToUrlQuery:function(){return a},urlQueryToSearchParams:function(){return u}};for(var o in n)Object.defineProperty(r,o,{enumerable:!0,get:n[o]});function a(e){let t={};for(let[r,n]of e.entries()){let e=t[r];void 0===e?t[r]=n:Array.isArray(e)?e.push(n):t[r]=[e,n]}return t}function i(e){return"string"==typeof e?e:("number"!=typeof e||isNaN(e))&&"boolean"!=typeof e?"":String(e)}function u(e){let t=new URLSearchParams;for(let[r,n]of Object.entries(e))if(Array.isArray(n))for(let e of n)t.append(r,i(e));else t.set(r,i(n));return t}function s(e,...t){for(let r of t){for(let t of r.keys())e.delete(t);for(let[t,n]of r.entries())e.append(t,n)}return e}},95057,(e,t,r)=>{"use strict";e.i(47167),Object.defineProperty(r,"__esModule",{value:!0});var n={formatUrl:function(){return u},formatWithValidation:function(){return l},urlObjectKeys:function(){return s}};for(var o in n)Object.defineProperty(r,o,{enumerable:!0,get:n[o]});let a=e.r(90809)._(e.r(98183)),i=/https?|ftp|gopher|file/;function u(e){let{auth:t,hostname:r}=e,n=e.protocol||"",o=e.pathname||"",u=e.hash||"",s=e.query||"",l=!1;t=t?encodeURIComponent(t).replace(/%3A/i,":")+"@":"",e.host?l=t+e.host:r&&(l=t+(~r.indexOf(":")?`[${r}]`:r),e.port&&(l+=":"+e.port)),s&&"object"==typeof s&&(s=String(a.urlQueryToSearchParams(s)));let c=e.search||s&&`?${s}`||"";return n&&!n.endsWith(":")&&(n+=":"),e.slashes||(!n||i.test(n))&&!1!==l?(l="//"+(l||""),o&&"/"!==o[0]&&(o="/"+o)):l||(l=""),u&&"#"!==u[0]&&(u="#"+u),c&&"?"!==c[0]&&(c="?"+c),o=o.replace(/[?#]/g,encodeURIComponent),c=c.replace("#","%23"),`${n}${l}${o}${c}${u}`}let s=["auth","hash","host","hostname","href","path","pathname","port","protocol","query","search","slashes"];function l(e){return u(e)}}]);
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,2776,e=>{"use strict";var t=e.i(43476),r=e.i(15812);e.s(["default",0,function({children:e}){let{userId:n,ready:o}=(0,r.useUser)();return o&&n?e:(0,t.jsx)("div",{style:{minHeight:"100vh",display:"flex",alignItems:"center",justifyContent:"center",background:"#0a0a1a",color:"#fff",padding:24,textAlign:"center",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'},children:o?(0,t.jsxs)("div",{style:{maxWidth:420},children:[(0,t.jsx)("div",{style:{fontSize:18,fontWeight:700,marginBottom:8},children:"Can’t reach 6 Degrees’ local server"}),(0,t.jsx)("div",{style:{color:"#888",fontSize:14,lineHeight:1.6},children:"The page loaded but the app behind it is not answering. If you started it from a terminal, check that window for an error, then reload this page."})]}):(0,t.jsx)("div",{style:{fontSize:18,fontWeight:600},children:"Loading…"})})}])},19821,e=>{"use strict";async function t(e){let t=e?`/api/network?userId=${encodeURIComponent(e)}`:"/api/network",r=await fetch(t);if(!r.ok)throw Error(`Failed to load network (${r.status})`);let n=await r.json();return{degree1:n.degree1||[],degree2:n.degree2||[],degree3:n.degree3||[]}}e.s(["loadNetwork",0,t])},40996,e=>{"use strict";async function t(){let e=await fetch("/api/scraper");if(!e.ok)throw Error("Could not reach the app.");return e.json()}async function r(e,t,n={}){let o=await fetch("/api/scraper",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:e,...t?{name:t}:{},...n})}),a=await o.json().catch(()=>({}));if(!o.ok)throw Error(a.error||"Could not start the scanner.");return a}async function n(){await fetch("/api/scraper",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"cancel"})})}e.s(["notReadyMessage",0,function(e){return e?e.checks?.scriptsFound?e.checks?.python?e.checks?.dependencies?e.checks?.chrome?null:"Google Chrome is not installed.":"The scanner is not installed yet — open Scan to set it up.":"Python 3 is not installed on this machine.":"The scanner files are missing from this install.":"Could not reach the app."},"runScrape",0,function(e,{name:n,onLog:o,...a}={}){return new Promise((i,u)=>{r(e,n,a).then(()=>{let e=setInterval(async()=>{try{let r=await t();o?.(r.log||[]),r.running||(clearInterval(e),i(r))}catch{}},1500)}).catch(u)})},"scraperStatus",0,t,"startScrape",0,r,"stopScrape",0,n])},5258,e=>{"use strict";e.s(["TIER_COLORS",0,{S:"#f6c344",A:"#a78bfa",B:"#60a5fa",C:"#94a3b8",D:"#6b7280"},"TIER_COLORS_CLASSIC",0,{S:"#FFD700",A:"#9B59B6",B:"#3498DB",C:"#95A5A6",D:"#BDC3C7"},"TIER_ORDER",0,["S","A","B","C","D"],"TIER_RING_RADIUS",0,{S:150,A:230,B:310,C:390,D:470},"initialsFor",0,function(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===t.length?"?":(t[0].charAt(0)+(t.length>1?t[t.length-1].charAt(0):"")).toUpperCase()}])},22016,(e,t,r)=>{"use strict";e.i(47167),Object.defineProperty(r,"__esModule",{value:!0});var n={default:function(){return m},useLinkStatus:function(){return S}};for(var o in n)Object.defineProperty(r,o,{enumerable:!0,get:n[o]});let a=e.r(90809),i=e.r(43476),u=a._(e.r(71645)),s=e.r(95057),l=e.r(8372),c=e.r(18581),f=e.r(18967),d=e.r(5550),p=e.r(88540),h=e.r(91949),y=e.r(73668),g=e.r(9396);function m(t){var r;let n,o,a,[m,S]=(0,u.useOptimistic)(h.IDLE_LINK_STATUS),v=(0,u.useRef)(null),{href:C,as:P,children:E,prefetch:O=null,passHref:_,replace:j,shallow:T,scroll:R,onClick:w,onMouseEnter:A,onTouchStart:I,legacyBehavior:k=!1,onNavigate:x,transitionTypes:N,ref:L,unstable_dynamicOnHover:U,...B}=t;n=E,k&&("string"==typeof n||"number"==typeof n)&&(n=(0,i.jsx)("a",{children:n}));let D=u.default.useContext(l.AppRouterContext),M=!1!==O,$=!1===O?"none":!0===O?"full":"auto",F="none"!==$?"auto"===$?g.FetchStrategy.PPR:g.FetchStrategy.Full:g.FetchStrategy.PPR,z="string"==typeof(r=P||C)?r:(0,s.formatUrl)(r);if(k){if(n?.$$typeof===Symbol.for("react.lazy"))throw Object.defineProperty(Error("`<Link legacyBehavior>` received a direct child that is either a Server Component, or JSX that was loaded with React.lazy(). This is not supported. Either remove legacyBehavior, or make the direct child a Client Component that renders the Link's `<a>` tag."),"__NEXT_ERROR_CODE",{value:"E863",enumerable:!1,configurable:!0});o=u.default.Children.only(n)}let K=k?o&&"object"==typeof o&&o.ref:L,W,J=u.default.useCallback(e=>(null!==D&&(v.current=(0,h.mountLinkInstance)(e,z,D,F,M,S,W)),()=>{v.current&&((0,h.unmountLinkForCurrentNavigation)(v.current),v.current=null),(0,h.unmountPrefetchableInstance)(e)}),[M,z,D,F,S,W]),G={ref:(0,c.useMergedRef)(J,K),onClick(t){k||"function"!=typeof w||w(t),k&&o.props&&"function"==typeof o.props.onClick&&o.props.onClick(t),!D||t.defaultPrevented||function(t,r,n,o,a,i,s,l="none"){if("u">typeof window){let c,{nodeName:f}=t.currentTarget;if("A"===f.toUpperCase()&&((c=t.currentTarget.getAttribute("target"))&&"_self"!==c||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||t.nativeEvent&&2===t.nativeEvent.which)||t.currentTarget.hasAttribute("download"))return;if(!(0,y.isLocalURL)(r)){o&&(t.preventDefault(),location.replace(r));return}if(t.preventDefault(),i){let e=!1;if(i({preventDefault:()=>{e=!0}}),e)return}let{dispatchNavigateAction:d}=e.r(99781);u.default.startTransition(()=>{d(r,o?"replace":"push",!1===a?p.ScrollBehavior.NoScroll:p.ScrollBehavior.Default,n.current,s,l)})}}(t,z,v,j,R,x,N,$)},onMouseEnter(e){k||"function"!=typeof A||A(e),k&&o.props&&"function"==typeof o.props.onMouseEnter&&o.props.onMouseEnter(e),D&&M&&(0,h.onNavigationIntent)(e.currentTarget,!0===U)},onTouchStart:function(e){k||"function"!=typeof I||I(e),k&&o.props&&"function"==typeof o.props.onTouchStart&&o.props.onTouchStart(e),D&&M&&(0,h.onNavigationIntent)(e.currentTarget,!0===U)}};return(0,f.isAbsoluteUrl)(z)?G.href=z:k&&!_&&("a"!==o.type||"href"in o.props)||(G.href=(0,d.addBasePath)(z)),a=k?u.default.cloneElement(o,G):(0,i.jsx)("a",{...B,...G,children:n}),(0,i.jsx)(b.Provider,{value:m,children:a})}let b=(0,u.createContext)(h.IDLE_LINK_STATUS),S=()=>(0,u.useContext)(b);("function"==typeof r.default||"object"==typeof r.default&&null!==r.default)&&void 0===r.default.__esModule&&(Object.defineProperty(r.default,"__esModule",{value:!0}),Object.assign(r.default,r),t.exports=r.default)},18581,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"useMergedRef",{enumerable:!0,get:function(){return o}});let n=e.r(71645);function o(e,t){let r=(0,n.useRef)(null),o=(0,n.useRef)(null);return(0,n.useCallback)(n=>{if(null===n){let e=r.current;e&&(r.current=null,e());let t=o.current;t&&(o.current=null,t())}else e&&(r.current=a(e,n)),t&&(o.current=a(t,n))},[e,t])}function a(e,t){if("function"!=typeof e)return e.current=t,()=>{e.current=null};{let r=e(t);return"function"==typeof r?r:()=>e(null)}}("function"==typeof r.default||"object"==typeof r.default&&null!==r.default)&&void 0===r.default.__esModule&&(Object.defineProperty(r.default,"__esModule",{value:!0}),Object.assign(r.default,r),t.exports=r.default)},18967,(e,t,r)=>{"use strict";e.i(47167),Object.defineProperty(r,"__esModule",{value:!0});var n={DecodeError:function(){return m},MiddlewareNotFoundError:function(){return C},MissingStaticPage:function(){return v},NormalizeError:function(){return b},PageNotFoundError:function(){return S},SP:function(){return y},ST:function(){return g},WEB_VITALS:function(){return a},execOnce:function(){return i},getDisplayName:function(){return f},getLocationOrigin:function(){return l},getURL:function(){return c},isAbsoluteUrl:function(){return s},isResSent:function(){return d},loadGetInitialProps:function(){return h},normalizeRepeatedSlashes:function(){return p},stringifyError:function(){return P}};for(var o in n)Object.defineProperty(r,o,{enumerable:!0,get:n[o]});let a=["CLS","FCP","FID","INP","LCP","TTFB"];function i(e){let t,r=!1;return(...n)=>(r||(r=!0,t=e(...n)),t)}let u=/^[a-zA-Z][a-zA-Z\d+\-.]*?:/,s=e=>{let t=e.charCodeAt(0);return!!(t>=65&&t<=90||t>=97&&t<=122)&&u.test(e)};function l(){let{protocol:e,hostname:t,port:r}=window.location;return`${e}//${t}${r?":"+r:""}`}function c(){let{href:e}=window.location,t=l();return e.substring(t.length)}function f(e){return"string"==typeof e?e:e.displayName||e.name||"Unknown"}function d(e){return e.finished||e.headersSent}function p(e){let t=e.split("?");return t[0].replace(/\\/g,"/").replace(/\/\/+/g,"/")+(t[1]?`?${t.slice(1).join("?")}`:"")}async function h(e,t){let r=t.res||t.ctx&&t.ctx.res;if(!e.getInitialProps)return t.ctx&&t.Component?{pageProps:await h(t.Component,t.ctx)}:{};let n=await e.getInitialProps(t);if(r&&d(r))return n;if(!n)throw Object.defineProperty(Error(`"${f(e)}.getInitialProps()" should resolve to an object. But found "${n}" instead.`),"__NEXT_ERROR_CODE",{value:"E1025",enumerable:!1,configurable:!0});return n}let y="u">typeof performance,g=y&&["mark","measure","getEntriesByName"].every(e=>"function"==typeof performance[e]);class m extends Error{}class b extends Error{}class S extends Error{constructor(e){super(),this.code="ENOENT",this.name="PageNotFoundError",this.message=`Cannot find module for page: ${e}`}}class v extends Error{constructor(e,t){super(),this.message=`Failed to load static file for page: ${e} ${t}`}}class C extends Error{constructor(){super(),this.code="ENOENT",this.message="Cannot find the middleware module"}}function P(e){return JSON.stringify({message:e.message,stack:e.stack})}},73668,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"isLocalURL",{enumerable:!0,get:function(){return a}});let n=e.r(18967),o=e.r(52817);function a(e){if(!(0,n.isAbsoluteUrl)(e))return!0;try{let t=(0,n.getLocationOrigin)(),r=new URL(e,t);return r.origin===t&&(0,o.hasBasePath)(r.pathname)}catch(e){return!1}}},98183,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n={assign:function(){return s},searchParamsToUrlQuery:function(){return a},urlQueryToSearchParams:function(){return u}};for(var o in n)Object.defineProperty(r,o,{enumerable:!0,get:n[o]});function a(e){let t={};for(let[r,n]of e.entries()){let e=t[r];void 0===e?t[r]=n:Array.isArray(e)?e.push(n):t[r]=[e,n]}return t}function i(e){return"string"==typeof e?e:("number"!=typeof e||isNaN(e))&&"boolean"!=typeof e?"":String(e)}function u(e){let t=new URLSearchParams;for(let[r,n]of Object.entries(e))if(Array.isArray(n))for(let e of n)t.append(r,i(e));else t.set(r,i(n));return t}function s(e,...t){for(let r of t){for(let t of r.keys())e.delete(t);for(let[t,n]of r.entries())e.append(t,n)}return e}},95057,(e,t,r)=>{"use strict";e.i(47167),Object.defineProperty(r,"__esModule",{value:!0});var n={formatUrl:function(){return u},formatWithValidation:function(){return l},urlObjectKeys:function(){return s}};for(var o in n)Object.defineProperty(r,o,{enumerable:!0,get:n[o]});let a=e.r(90809)._(e.r(98183)),i=/https?|ftp|gopher|file/;function u(e){let{auth:t,hostname:r}=e,n=e.protocol||"",o=e.pathname||"",u=e.hash||"",s=e.query||"",l=!1;t=t?encodeURIComponent(t).replace(/%3A/i,":")+"@":"",e.host?l=t+e.host:r&&(l=t+(~r.indexOf(":")?`[${r}]`:r),e.port&&(l+=":"+e.port)),s&&"object"==typeof s&&(s=String(a.urlQueryToSearchParams(s)));let c=e.search||s&&`?${s}`||"";return n&&!n.endsWith(":")&&(n+=":"),e.slashes||(!n||i.test(n))&&!1!==l?(l="//"+(l||""),o&&"/"!==o[0]&&(o="/"+o)):l||(l=""),u&&"#"!==u[0]&&(u="#"+u),c&&"?"!==c[0]&&(c="?"+c),o=o.replace(/[?#]/g,encodeURIComponent),c=c.replace("#","%23"),`${n}${l}${o}${c}${u}`}let s=["auth","hash","host","hostname","href","path","pathname","port","protocol","query","search","slashes"];function l(e){return u(e)}}]);
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.1] - 2026-09-25
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Tutorial videos on the download website:** installing on a Mac, running it on Linux, and
|
|
13
|
+
how scanning works. They're short and silent, with the steps as captions.
|
|
14
|
+
- **`npx six-degrees` on Linux.** The npm package is published (0.2.0 was the first). The
|
|
15
|
+
README, the website and the installer's message now point Linux users to it, with how to
|
|
16
|
+
update it and where it keeps your data.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Check for updates says what it found.** An up-to-date copy showed only a faint line, so
|
|
20
|
+
the button seemed to do nothing. The answer is now a clear status ("You're up to date: 0.2.1
|
|
21
|
+
is the newest version", with the time). The Mac app's **Check for Updates…** menu item opens
|
|
22
|
+
the Updates panel and runs the check, instead of just opening the top of the Scan page.
|
|
23
|
+
- **The profile page no longer starts scans.** Its "Set Up Account — Full Scan" and "Auto-Bridge
|
|
24
|
+
All Connections" cards are gone. The second mapped every connection in one go, with none of
|
|
25
|
+
the batch size, budget or warnings of the Scan page, where scanning lives.
|
|
26
|
+
- The window title is **Six Degrees** (it was "6 Degrees of Separation — LinkedIn Network
|
|
27
|
+
Research"), and the page description no longer claims to be a research project.
|
|
28
|
+
- The import page's "close the tab" note is true in the Mac app too.
|
|
29
|
+
- **Big LinkedIn exports import.** An export over about 9,000 connections was refused ("too large to hold in this browser tab"). The tab now keeps only what the export says about each person and scores it again on load, so exports up to LinkedIn's 30,000 maximum fit. Checked in a browser: 10,000 connections import and draw in about 4 seconds, 30,000 in about 25. It still never touches the database.
|
|
30
|
+
- **`npx six-degrees` downloads about 22 MB instead of about 235 MB.** The package already
|
|
31
|
+
carries its built server; Next, React and d3 are now build-time only.
|
|
32
|
+
- On Windows, npm now refuses the package with a clear "not supported" message instead of
|
|
33
|
+
installing it and crashing at start.
|
|
34
|
+
- On Linux without a desktop, `npx six-degrees` prints the address to open instead of a
|
|
35
|
+
crash when there's no browser to launch.
|
|
36
|
+
- The Scan page checks for Google Chrome on Linux too (where Playwright looks for it).
|
|
37
|
+
- Updating an npx copy says to stop it first, and keeps `--data-dir` if it was started
|
|
38
|
+
with one, so the new version finds your network.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- The app says **scanner** and **scan** everywhere you read it (the Scan page, the side
|
|
42
|
+
panel, the profile page, the import page); the code names are unchanged.
|
|
43
|
+
- Releases publish to npm through npm's trusted publishing: GitHub vouches for the release
|
|
44
|
+
workflow, so no npm token is stored anywhere. Each release builds the package on Linux,
|
|
45
|
+
installs it, checks it serves the app, and publishes that exact file.
|
|
46
|
+
- The README: macOS 15's second button is **Open Anyway**; the LinkedIn warning's link no
|
|
47
|
+
longer shows a file name; the release notes link the changelog.
|
|
48
|
+
- SECURITY.md lists all six gated routes (it said four).
|
|
49
|
+
|
|
9
50
|
## [0.2.0] - 2026-09-24
|
|
10
51
|
|
|
11
52
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://blakeb056.github.io/six-degrees/"><img src="desktop/icon/icon.svg" width="128" height="128" alt="Six Degrees website"></a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
<h1 align="center">Six Degrees</h1>
|
|
2
6
|
|
|
3
7
|
<p align="center">
|
|
@@ -5,32 +9,16 @@
|
|
|
5
9
|
and find the shortest path to someone you haven't met.</em>
|
|
6
10
|
</p>
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
<a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Apple-Silicon.dmg"><img src="https://img.shields.io/badge/Download-Apple%20Silicon%20(M1%20or%20newer)-2ea44f?style=for-the-badge&logo=apple&logoColor=white" alt="Download for a Mac with Apple Silicon (M1 or newer)"></a>
|
|
10
|
-
|
|
11
|
-
<a href="https://github.com/blakeb056/six-degrees/releases/latest/download/Six-Degrees-Mac-Intel.dmg"><img src="https://img.shields.io/badge/Download-Intel%20Mac-555555?style=for-the-badge&logo=apple&logoColor=white" alt="Download for a Mac with an Intel processor"></a>
|
|
12
|
-
</p>
|
|
12
|
+
## Install it
|
|
13
13
|
|
|
14
|
-
<p align="center"><
|
|
14
|
+
<p align="center"><strong>Click the button for your Mac to download the app.</strong></p>
|
|
15
15
|
|
|
16
16
|
<p align="center">
|
|
17
|
-
<img src="docs/img/
|
|
17
|
+
<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
|
+
<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>
|
|
18
19
|
</p>
|
|
19
20
|
|
|
20
|
-
<p align="center"><sub>
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
Your network has a shape, and you can't see it. Six Degrees draws it: everyone you
|
|
25
|
-
know placed on rings by how much they can open up for you, the handful of people
|
|
26
|
-
whose own circles reach the furthest, and the shortest chain from you to a stranger
|
|
27
|
-
worth meeting.
|
|
28
|
-
|
|
29
|
-
It's a Mac app. It runs on your computer, against your own data, with no account and
|
|
30
|
-
no server. (Inside the app the logo reads *6 Degrees*; in Applications it's **Six
|
|
31
|
-
Degrees**.)
|
|
32
|
-
|
|
33
|
-
## Install it
|
|
21
|
+
<p align="center"><sub>Free Mac app · macOS 13.5 or later · <a href="https://blakeb056.github.io/six-degrees/">website</a> · <a href="https://github.com/blakeb056/six-degrees/releases/latest">what's new</a></sub></p>
|
|
34
22
|
|
|
35
23
|
**Which download?** Apple menu → **About This Mac**. If it says "Chip: Apple M…", take
|
|
36
24
|
**Apple Silicon**. If it says "Processor: …Intel…", take **Intel**. It needs **macOS 13.5
|
|
@@ -45,8 +33,8 @@ paid Apple certificate yet:
|
|
|
45
33
|
malware. Close that message with **Done** (on macOS 13 or 14 it may say **OK** or
|
|
46
34
|
**Cancel**). Anything except *Move to Trash*.
|
|
47
35
|
3. Open **System Settings → Privacy & Security**, scroll down to *"Six Degrees" was
|
|
48
|
-
blocked*, and click **Open Anyway**. Confirm with **Open** (
|
|
49
|
-
asked
|
|
36
|
+
blocked*, and click **Open Anyway**. Confirm with **Open Anyway** again (on macOS
|
|
37
|
+
13 or 14 the button says **Open**), and enter your password if asked.
|
|
50
38
|
|
|
51
39
|
That's once per download; after that it opens like any app.
|
|
52
40
|
|
|
@@ -72,11 +60,26 @@ Your network isn't stored in the app. It's in a hidden folder in your home folde
|
|
|
72
60
|
touch it. Each new version also copies your data into its `backups` folder before it
|
|
73
61
|
first opens it.
|
|
74
62
|
|
|
75
|
-
**Linux:**
|
|
76
|
-
|
|
63
|
+
**Linux:** install **Node 22.13 or later** (from nodejs.org or nvm; Ubuntu's own
|
|
64
|
+
`nodejs` package is too old), then run:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx six-degrees
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
It opens http://127.0.0.1:6363 in your browser (`--no-open` on a machine without a
|
|
71
|
+
desktop), keeps your data in `~/.six-degrees` (it prints the folder when it starts;
|
|
72
|
+
`--data-dir` puts it elsewhere), and stops with Ctrl-C. To update, stop it and run
|
|
73
|
+
`npx six-degrees@latest`; the Updates panel at the bottom of the Scan page gives the same
|
|
74
|
+
line. Scanning LinkedIn also needs Google Chrome (not Chromium) and Python 3.9+ with venv
|
|
75
|
+
(on Ubuntu: `sudo apt install python3-venv`). It's tested on Ubuntu, and works on a Mac
|
|
76
|
+
too if you'd rather not install the app.
|
|
77
|
+
|
|
78
|
+
**Windows:** not yet. There's no Windows app, and `npx six-degrees` doesn't run on
|
|
79
|
+
Windows yet; npm says so if you try.
|
|
77
80
|
|
|
78
81
|
<details>
|
|
79
|
-
<summary>Run it from source (
|
|
82
|
+
<summary>Run it from source (contributors)</summary>
|
|
80
83
|
|
|
81
84
|
Needs **Node 22.13 or later** and git.
|
|
82
85
|
|
|
@@ -93,6 +96,23 @@ To update later: stop it (Ctrl-C), then
|
|
|
93
96
|
Scanning LinkedIn also needs Python 3.9+ and Google Chrome (see below).
|
|
94
97
|
</details>
|
|
95
98
|
|
|
99
|
+
## What it is
|
|
100
|
+
|
|
101
|
+
<p align="center">
|
|
102
|
+
<img src="docs/img/app-window.png" alt="Six Degrees on a Mac: your network drawn as circles around you, the most powerful people closest" width="100%">
|
|
103
|
+
</p>
|
|
104
|
+
|
|
105
|
+
<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>
|
|
106
|
+
|
|
107
|
+
Your network has a shape, and you can't see it. Six Degrees draws it: everyone you
|
|
108
|
+
know placed on rings by how much they can open up for you, the handful of people
|
|
109
|
+
whose own circles reach the furthest, and the shortest chain from you to a stranger
|
|
110
|
+
worth meeting.
|
|
111
|
+
|
|
112
|
+
It's a Mac app (or `npx six-degrees` on Linux). It runs on your computer, against your own data, with no account and
|
|
113
|
+
no server. (Inside the app the logo reads *6 Degrees*; in Applications it's **Six
|
|
114
|
+
Degrees**.)
|
|
115
|
+
|
|
96
116
|
## Before you scan LinkedIn
|
|
97
117
|
|
|
98
118
|
> [!WARNING]
|
|
@@ -108,7 +128,7 @@ Scanning LinkedIn also needs Python 3.9+ and Google Chrome (see below).
|
|
|
108
128
|
> stops at the first sign of push-back. The risk is still yours.
|
|
109
129
|
>
|
|
110
130
|
> **The CSV import and the sample network carry no LinkedIn risk at all.** Details:
|
|
111
|
-
> [
|
|
131
|
+
> [how scanning works and what it risks](docs/SCRAPING.md).
|
|
112
132
|
|
|
113
133
|
## First run
|
|
114
134
|
|
|
@@ -116,7 +136,7 @@ The app opens on a welcome screen with three ways in, and asks nothing about you
|
|
|
116
136
|
|
|
117
137
|
- **Scan my LinkedIn**: a guided page that ticks each step off as it goes. It sets up
|
|
118
138
|
the scanner in one click, you sign into LinkedIn yourself in a Chrome window, and then
|
|
119
|
-
it scans. Needs Google Chrome
|
|
139
|
+
it scans. Needs Google Chrome and Python 3.9+; the page checks that
|
|
120
140
|
both are installed and says what's missing. The app marks this *Recommended* because
|
|
121
141
|
it's the only way to Degrees and Outlink. Read the warning above first.
|
|
122
142
|
- **Import my LinkedIn CSV**: LinkedIn's official export, read on your machine. On
|
|
@@ -251,7 +271,7 @@ Nothing to configure. Two optional environment variables exist:
|
|
|
251
271
|
|
|
252
272
|
| Variable | Purpose |
|
|
253
273
|
|---|---|
|
|
254
|
-
| `SIX_DEGREES_HOME` | Where your data lives. Defaults to `~/.six-degrees`. It's read at launch, so it applies
|
|
274
|
+
| `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. |
|
|
255
275
|
| `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 six routes that delete data, run the scanner 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. |
|
|
256
276
|
|
|
257
277
|
## Contributing
|
package/SECURITY.md
CHANGED
|
@@ -12,17 +12,20 @@ This app is designed to run **on your own machine, against your own network**.
|
|
|
12
12
|
|
|
13
13
|
- The web UI binds to localhost. It is not hardened for exposure to the
|
|
14
14
|
internet, and it has no multi-user authentication or per-user authorization.
|
|
15
|
-
-
|
|
16
|
-
`setup-profile`)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
- Six API routes are gated (`admin-delete`, `admin-update`, `delete-cluster`,
|
|
16
|
+
`setup-profile`, `scraper`, `update`). The first four can irreversibly
|
|
17
|
+
destroy or rewrite data, `scraper` starts processes, and `update` changes the
|
|
18
|
+
code that runs next. While the server is bound to 127.0.0.1 they need no
|
|
19
|
+
token — anyone who can reach it can already open the database file directly,
|
|
20
|
+
so a token there adds friction rather than safety. If it is bound to any
|
|
21
|
+
other address, every caller must send `ADMIN_TOKEN` as a bearer, including
|
|
22
|
+
this machine and the app's own buttons, and the routes refuse to run when it
|
|
23
|
+
is unset.
|
|
21
24
|
- A localhost-bound service is still reachable by any web page you visit — the
|
|
22
25
|
binding keeps other machines out, not your own browser. Every mutating API
|
|
23
26
|
request is therefore refused when the browser reports it came from another
|
|
24
27
|
site (`Sec-Fetch-Site`, with `Origin` as a fallback). Command-line callers
|
|
25
|
-
such as the
|
|
28
|
+
such as the scanner send neither header and are unaffected.
|
|
26
29
|
- Values stored in the database are treated as untrusted text and escaped
|
|
27
30
|
before rendering. Do not reintroduce `innerHTML` (including d3's `.html()`)
|
|
28
31
|
for anything data-bearing.
|
|
@@ -34,10 +37,10 @@ Everything stays local:
|
|
|
34
37
|
|
|
35
38
|
- A CSV import is parsed in the browser and held for that tab only. It is never
|
|
36
39
|
uploaded and never written to a database.
|
|
37
|
-
-
|
|
40
|
+
- Scanned data and cached avatars are written to your machine and are gitignored.
|
|
38
41
|
- There is no telemetry, no analytics, and no crash reporting.
|
|
39
42
|
|
|
40
|
-
If you run the
|
|
43
|
+
If you run the scanner, `chrome-profile/` in the data directory holds a live
|
|
41
44
|
logged-in LinkedIn session. Never copy, sync, or commit it.
|
|
42
45
|
|
|
43
46
|
Never commit `public/avatars/` or any exported CSV — they contain real people.
|
package/bin/six-degrees.mjs
CHANGED
|
@@ -84,10 +84,15 @@ function freePort(start, attempts = 20) {
|
|
|
84
84
|
function openBrowser(url) {
|
|
85
85
|
const cmd = platform() === 'darwin' ? 'open' : platform() === 'win32' ? 'start' : 'xdg-open';
|
|
86
86
|
const args = platform() === 'win32' ? ['', url] : [url];
|
|
87
|
+
// A machine with no desktop (no xdg-open) can't open one; say where to go.
|
|
88
|
+
// spawn reports a missing command as an 'error' event, not a throw.
|
|
89
|
+
const instead = () => console.log(` Open ${url} in your browser.`);
|
|
87
90
|
try {
|
|
88
|
-
spawn(cmd, args, { stdio: 'ignore', detached: true, shell: platform() === 'win32' })
|
|
91
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true, shell: platform() === 'win32' });
|
|
92
|
+
child.on('error', instead);
|
|
93
|
+
child.unref();
|
|
89
94
|
} catch {
|
|
90
|
-
|
|
95
|
+
instead();
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
package/lib/csv.js
CHANGED
|
@@ -56,6 +56,34 @@ function hashId(input) {
|
|
|
56
56
|
return `csv-${Math.abs(h).toString(36)}`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/** One connection, built from what the export itself says about the person. */
|
|
60
|
+
function connectionFrom({ id, name, role, company, profileUrl, connected_date }) {
|
|
61
|
+
// /paths and the sidebar read seniority off `headline`, not `role`, so
|
|
62
|
+
// rebuild the "Role at Company" shape the scanner would have produced.
|
|
63
|
+
const headline = role && company ? `${role} at ${company}` : role || company || name;
|
|
64
|
+
return {
|
|
65
|
+
id,
|
|
66
|
+
degree: 1,
|
|
67
|
+
name,
|
|
68
|
+
headline,
|
|
69
|
+
role,
|
|
70
|
+
company,
|
|
71
|
+
profile_url: profileUrl || null,
|
|
72
|
+
profile_image_url: null, // the official export carries no photos
|
|
73
|
+
connected_date,
|
|
74
|
+
source_connection_id: null,
|
|
75
|
+
is_catalyst: false,
|
|
76
|
+
catalyst_score: null,
|
|
77
|
+
circle_power: null,
|
|
78
|
+
circle_s_count: null,
|
|
79
|
+
circle_a_count: null,
|
|
80
|
+
circle_elite_pct: null,
|
|
81
|
+
outreach_status: null,
|
|
82
|
+
unlock_status: null,
|
|
83
|
+
...scoreRecord(headline, company),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
59
87
|
// ── The importer ───────────────────────────────────────────────────────────
|
|
60
88
|
export function parseConnectionsCsv(text) {
|
|
61
89
|
const rows = parseCsv(text);
|
|
@@ -90,10 +118,6 @@ export function parseConnectionsCsv(text) {
|
|
|
90
118
|
const role = cell(iPosition);
|
|
91
119
|
const profileUrl = cell(iUrl);
|
|
92
120
|
|
|
93
|
-
// /paths and the sidebar read seniority off `headline`, not `role`, so
|
|
94
|
-
// rebuild the "Role at Company" shape the scraper would have produced.
|
|
95
|
-
const headline = role && company ? `${role} at ${company}` : role || company || name;
|
|
96
|
-
|
|
97
121
|
const id = profileUrl ? hashId(profileUrl) : `csv-row-${connections.length}`;
|
|
98
122
|
if (seen.has(id)) { skipped++; continue; }
|
|
99
123
|
seen.add(id);
|
|
@@ -105,27 +129,7 @@ export function parseConnectionsCsv(text) {
|
|
|
105
129
|
if (!isNaN(d.getTime())) connected_date = d.toISOString().split('T')[0];
|
|
106
130
|
}
|
|
107
131
|
|
|
108
|
-
connections.push({
|
|
109
|
-
id,
|
|
110
|
-
degree: 1,
|
|
111
|
-
name,
|
|
112
|
-
headline,
|
|
113
|
-
role,
|
|
114
|
-
company,
|
|
115
|
-
profile_url: profileUrl || null,
|
|
116
|
-
profile_image_url: null, // the official export carries no photos
|
|
117
|
-
connected_date,
|
|
118
|
-
source_connection_id: null,
|
|
119
|
-
is_catalyst: false,
|
|
120
|
-
catalyst_score: null,
|
|
121
|
-
circle_power: null,
|
|
122
|
-
circle_s_count: null,
|
|
123
|
-
circle_a_count: null,
|
|
124
|
-
circle_elite_pct: null,
|
|
125
|
-
outreach_status: null,
|
|
126
|
-
unlock_status: null,
|
|
127
|
-
...scoreRecord(headline, company),
|
|
128
|
-
});
|
|
132
|
+
connections.push(connectionFrom({ id, name, role, company, profileUrl, connected_date }));
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
if (!connections.length) {
|
|
@@ -148,8 +152,29 @@ export function saveSessionNetwork({ degree1 = [], degree2 = [], source = 'csv'
|
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
154
|
|
|
155
|
+
// A scored row is ~540 characters and the tab holds ~5 million, which capped an
|
|
156
|
+
// import near 9,000 connections. So the tab keeps only what the export says about
|
|
157
|
+
// each person (~130 characters) and scores it again on load: an export at
|
|
158
|
+
// LinkedIn's own maximum of 30,000 connections fits.
|
|
159
|
+
export function packConnections(connections) {
|
|
160
|
+
return connections.map((c) => [c.id, c.name, c.role, c.company, c.profile_url, c.connected_date]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function unpackConnections(rows = []) {
|
|
164
|
+
return rows
|
|
165
|
+
.map(([id, name, role, company, profileUrl, connected_date]) => connectionFrom({ id, name, role, company, profileUrl, connected_date }))
|
|
166
|
+
.sort((a, b) => b.power_score - a.power_score);
|
|
167
|
+
}
|
|
168
|
+
|
|
151
169
|
export function saveCsvNetwork(connections) {
|
|
152
|
-
|
|
170
|
+
try {
|
|
171
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({
|
|
172
|
+
packed: packConnections(connections), degree2: [], source: 'csv', importedAt: Date.now(),
|
|
173
|
+
}));
|
|
174
|
+
return true;
|
|
175
|
+
} catch {
|
|
176
|
+
return false; // quota
|
|
177
|
+
}
|
|
153
178
|
}
|
|
154
179
|
|
|
155
180
|
export function hasCsvNetwork() {
|
|
@@ -161,7 +186,8 @@ export function loadCsvNetwork() {
|
|
|
161
186
|
const raw = sessionStorage.getItem(STORAGE_KEY);
|
|
162
187
|
if (!raw) return null;
|
|
163
188
|
const parsed = JSON.parse(raw);
|
|
164
|
-
|
|
189
|
+
const degree1 = parsed.packed ? unpackConnections(parsed.packed) : parsed.degree1 || [];
|
|
190
|
+
return { degree1, degree2: parsed.degree2 || [], source: parsed.source || 'csv' };
|
|
165
191
|
} catch { return null; }
|
|
166
192
|
}
|
|
167
193
|
|
package/lib/release.js
CHANGED
|
@@ -39,12 +39,15 @@ export function installKind(env = process.env, root = '') {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** What to run to update, for each way of installing. */
|
|
42
|
-
export function updateCommand(kind, slug) {
|
|
42
|
+
export function updateCommand(kind, slug, { dataDir } = {}) {
|
|
43
43
|
if (kind === 'mac-app') {
|
|
44
44
|
return `curl -fsSL https://raw.githubusercontent.com/${slug}/main/install.sh | bash`;
|
|
45
45
|
}
|
|
46
46
|
if (kind === 'source') {
|
|
47
47
|
return 'git pull && npm ci && npm run build && npm run start:packaged';
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
// A copy started with --data-dir must be restarted with it, or the new
|
|
50
|
+
// version opens an empty network in the default folder.
|
|
51
|
+
const quoted = dataDir && `'${String(dataDir).replace(/'/g, `'\\''`)}'`;
|
|
52
|
+
return quoted ? `npx six-degrees@latest --data-dir ${quoted}` : 'npx six-degrees@latest';
|
|
50
53
|
}
|
package/lib/scraper-client.js
CHANGED
|
@@ -20,7 +20,7 @@ export async function startScrape(action, name, extra = {}) {
|
|
|
20
20
|
body: JSON.stringify({ action, ...(name ? { name } : {}), ...extra }),
|
|
21
21
|
});
|
|
22
22
|
const d = await r.json().catch(() => ({}));
|
|
23
|
-
if (!r.ok) throw new Error(d.error || 'Could not start the
|
|
23
|
+
if (!r.ok) throw new Error(d.error || 'Could not start the scanner.');
|
|
24
24
|
return d;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -48,9 +48,9 @@ export function runScrape(action, { name, onLog, ...extra } = {}) {
|
|
|
48
48
|
/** What to tell someone when the scraper is not usable yet. */
|
|
49
49
|
export function notReadyMessage(status) {
|
|
50
50
|
if (!status) return 'Could not reach the app.';
|
|
51
|
-
if (!status.checks?.scriptsFound) return 'The
|
|
51
|
+
if (!status.checks?.scriptsFound) return 'The scanner files are missing from this install.';
|
|
52
52
|
if (!status.checks?.python) return 'Python 3 is not installed on this machine.';
|
|
53
|
-
if (!status.checks?.dependencies) return 'The
|
|
53
|
+
if (!status.checks?.dependencies) return 'The scanner is not installed yet — open Scan to set it up.';
|
|
54
54
|
if (!status.checks?.chrome) return 'Google Chrome is not installed.';
|
|
55
55
|
return null;
|
|
56
56
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "six-degrees",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Map your professional network as a galaxy
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
7
7
|
"repository": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/blakeb056/six-degrees/issues"
|
|
13
13
|
},
|
|
14
|
-
"homepage": "https://github.
|
|
14
|
+
"homepage": "https://blakeb056.github.io/six-degrees/",
|
|
15
15
|
"keywords": [
|
|
16
16
|
"linkedin",
|
|
17
17
|
"network",
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": ">=22.13"
|
|
27
27
|
},
|
|
28
|
+
"os": [
|
|
29
|
+
"darwin",
|
|
30
|
+
"linux"
|
|
31
|
+
],
|
|
28
32
|
"private": false,
|
|
29
33
|
"bin": {
|
|
30
34
|
"six-degrees": "bin/six-degrees.mjs"
|
|
@@ -73,16 +77,14 @@
|
|
|
73
77
|
"desktop": "electron desktop",
|
|
74
78
|
"update": "node scripts/update.mjs"
|
|
75
79
|
},
|
|
76
|
-
"dependencies": {
|
|
77
|
-
"d3": "^7.9.0",
|
|
78
|
-
"next": "16.3.6",
|
|
79
|
-
"react": "19.2.4",
|
|
80
|
-
"react-dom": "19.2.4"
|
|
81
|
-
},
|
|
82
80
|
"devDependencies": {
|
|
83
81
|
"@electron/packager": "20.3.0",
|
|
82
|
+
"d3": "^7.9.0",
|
|
84
83
|
"electron": "44.4.5",
|
|
85
84
|
"eslint": "^9",
|
|
86
|
-
"eslint-config-next": "16.3.6"
|
|
85
|
+
"eslint-config-next": "16.3.6",
|
|
86
|
+
"next": "16.3.6",
|
|
87
|
+
"react": "19.2.4",
|
|
88
|
+
"react-dom": "19.2.4"
|
|
87
89
|
}
|
|
88
90
|
}
|