six-degrees 0.4.7 → 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.
Files changed (172) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
  7. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  9. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.rsc +13 -13
  14. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
  15. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +11 -11
  16. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/favicon.ico.body +0 -0
  48. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/import.html +1 -1
  51. package/.next/standalone/.next/server/app/import.rsc +15 -15
  52. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +15 -15
  53. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
  54. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +13 -13
  55. package/.next/standalone/.next/server/app/index.html +1 -1
  56. package/.next/standalone/.next/server/app/index.rsc +15 -15
  57. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  58. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  59. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  60. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/launch.html +2 -2
  63. package/.next/standalone/.next/server/app/launch.rsc +15 -15
  64. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +15 -15
  65. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
  66. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +13 -13
  67. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/paths.html +1 -1
  72. package/.next/standalone/.next/server/app/paths.rsc +15 -15
  73. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +15 -15
  74. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
  75. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +13 -13
  76. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  77. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  78. package/.next/standalone/.next/server/app/profile.html +1 -1
  79. package/.next/standalone/.next/server/app/profile.rsc +15 -15
  80. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +15 -15
  81. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
  82. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +13 -13
  83. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  85. package/.next/standalone/.next/server/app/queue.html +1 -1
  86. package/.next/standalone/.next/server/app/queue.rsc +15 -15
  87. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +15 -15
  88. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
  89. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +13 -13
  90. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  91. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  92. package/.next/standalone/.next/server/app/scores.html +1 -1
  93. package/.next/standalone/.next/server/app/scores.rsc +15 -15
  94. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +15 -15
  95. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +1 -1
  96. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +13 -13
  97. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  98. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  99. package/.next/standalone/.next/server/app/settings.html +1 -1
  100. package/.next/standalone/.next/server/app/settings.rsc +15 -15
  101. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +15 -15
  102. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  103. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +13 -13
  104. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  105. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  106. package/.next/standalone/.next/server/app/setup.html +1 -1
  107. package/.next/standalone/.next/server/app/setup.rsc +15 -15
  108. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +15 -15
  109. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  110. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +13 -13
  111. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  113. package/.next/standalone/.next/server/app/social.html +1 -1
  114. package/.next/standalone/.next/server/app/social.rsc +15 -15
  115. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +15 -15
  116. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +1 -1
  117. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +13 -13
  118. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  119. package/.next/standalone/.next/server/chunks/_1mxn_lb._.js +1 -1
  120. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +1 -1
  121. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
  122. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00d8snt._.js +1 -1
  123. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00g61b8._.js +1 -1
  124. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08lf3gy._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hg5gib._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ho-u0w._.js +1 -1
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ze473p._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1cnxxc9._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jeztd-._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ll97ry._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m0255u._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1w9z0zv._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +1 -1
  135. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
  136. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  137. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  138. package/.next/standalone/.next/server/pages/404.html +1 -1
  139. package/.next/standalone/.next/server/pages/500.html +1 -1
  140. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  141. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  142. package/.next/{static/chunks/2_zu7rmb8flzl.js → standalone/.next/static/chunks/1z-zp93cyoxvz.js} +1 -1
  143. package/.next/standalone/.next/static/chunks/24tvdun1cb3g1.js +1 -0
  144. package/.next/standalone/.next/static/media/favicon.23vm2o6v3er2z.ico +0 -0
  145. package/.next/standalone/CHANGELOG.md +33 -0
  146. package/.next/standalone/CONTRIBUTING.md +5 -0
  147. package/.next/standalone/README.md +168 -56
  148. package/.next/standalone/app/favicon.ico +0 -0
  149. package/.next/standalone/app/paths/page.js +25 -18
  150. package/.next/standalone/lib/scoring.js +42 -1
  151. package/.next/standalone/package-lock.json +2 -2
  152. package/.next/standalone/package.json +1 -1
  153. package/.next/standalone/scripts/build-site.mjs +537 -0
  154. package/.next/standalone/scripts/site-markdown.mjs +188 -0
  155. package/.next/standalone/scripts/site-news.mjs +311 -0
  156. package/.next/{standalone/.next/static/chunks/2_zu7rmb8flzl.js → static/chunks/1z-zp93cyoxvz.js} +1 -1
  157. package/.next/static/chunks/24tvdun1cb3g1.js +1 -0
  158. package/.next/static/media/favicon.23vm2o6v3er2z.ico +0 -0
  159. package/CHANGELOG.md +33 -0
  160. package/README.md +168 -56
  161. package/lib/scoring.js +42 -1
  162. package/package.json +1 -1
  163. package/.next/standalone/.next/static/chunks/3p_pwu5gx2i9h.js +0 -1
  164. package/.next/standalone/.next/static/media/favicon.0zdzw5kurn8m-.ico +0 -0
  165. package/.next/static/chunks/3p_pwu5gx2i9h.js +0 -1
  166. package/.next/static/media/favicon.0zdzw5kurn8m-.ico +0 -0
  167. /package/.next/standalone/.next/static/{YNRcnda8lvMnnrOKL1L5l → T0eWMsKjgSiPnmx1E0q3G}/_buildManifest.js +0 -0
  168. /package/.next/standalone/.next/static/{YNRcnda8lvMnnrOKL1L5l → T0eWMsKjgSiPnmx1E0q3G}/_clientMiddlewareManifest.js +0 -0
  169. /package/.next/standalone/.next/static/{YNRcnda8lvMnnrOKL1L5l → T0eWMsKjgSiPnmx1E0q3G}/_ssgManifest.js +0 -0
  170. /package/.next/static/{YNRcnda8lvMnnrOKL1L5l → T0eWMsKjgSiPnmx1E0q3G}/_buildManifest.js +0 -0
  171. /package/.next/static/{YNRcnda8lvMnnrOKL1L5l → T0eWMsKjgSiPnmx1E0q3G}/_clientMiddlewareManifest.js +0 -0
  172. /package/.next/static/{YNRcnda8lvMnnrOKL1L5l → T0eWMsKjgSiPnmx1E0q3G}/_ssgManifest.js +0 -0
@@ -6,6 +6,39 @@ 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
+
9
42
  ## [0.4.7] - 2026-09-29
10
43
 
11
44
  ### Added
@@ -38,6 +38,11 @@ Read `docs/SCRAPING.md` and `docs/brain/TRAPS.md` first. The scanner's packages
38
38
  pinned by hash in `scripts/requirements.txt`: to change one, edit its version there and
39
39
  run `node scripts/pin-python-packages.mjs`.
40
40
 
41
+ ## The website
42
+
43
+ https://sixdegreesapp.com/ is built from `site/` by `scripts/build-site.mjs`. To add a
44
+ blog post or a page, or to preview the site, see [site/README.md](site/README.md).
45
+
41
46
  ## Releasing
42
47
 
43
48
  Bump the version, tag it, push the tag. `.github/workflows/release.yml` builds the
@@ -1,24 +1,68 @@
1
1
  <p align="center">
2
- <a href="https://sixdegreesapp.com/"><img src="desktop/icon/icon.svg" width="128" height="128" alt="Six Degrees website"></a>
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
- <em>Map your LinkedIn network as a galaxy — see who bridges you to everyone else,<br />
9
- and find the shortest path to someone you haven't met.</em>
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
- ## Install it
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"><strong>Click the button for your Mac to download the app.</strong></p>
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
- <p align="center"><sub>Free Mac app · macOS 13.5 or later · <a href="https://sixdegreesapp.com/">website</a> · <a href="https://github.com/blakeb056/six-degrees/releases/latest">what's new</a></sub></p>
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
- ## What it is
156
+ ## Features
113
157
 
114
- <p align="center">
115
- <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%">
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
- <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>
214
+ Six Degrees ships often, and every release says what it added, changed and fixed:
119
215
 
120
- Your network has a shape, and you can't see it. Six Degrees draws it: everyone you
121
- know placed on rings by how much they can open up for you, the handful of people
122
- whose own circles reach the furthest, and the shortest chain from you to a stranger
123
- worth meeting.
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
- It's a Mac app (or `npx six-degrees` on Linux). It runs on your computer, against your own data, with no account and
126
- no server.
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). The rule that matters most: **never commit real
409
- network data**. That means no exported CSVs, no avatars, and no screenshots of real
410
- people.
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
 
Binary file
@@ -106,12 +106,22 @@ function PathsInner() {
106
106
  function enrichPeople(people) {
107
107
  const d1Urls = new Set(d1Data.map(c => c.profile_url));
108
108
  const knownUrls = new Set([...d1Data, ...d2Data].map(c => c.profile_url));
109
+ // A person's mutuals are everyone here you're connected to but them. Counted
110
+ // once, then less their own, rather than a pass over everyone per person:
111
+ // that was 3.7 million checks at a company of 1,900.
112
+ const connectedById = new Map();
113
+ let connectedHere = 0;
114
+ for (const op of people) {
115
+ if (!d1Urls.has(op.profile_url || op.profileUrl)) continue;
116
+ connectedHere++;
117
+ connectedById.set(op.id, (connectedById.get(op.id) || 0) + 1);
118
+ }
109
119
  return people.map(p => ({
110
120
  ...p,
111
121
  connected: d1Urls.has(p.profile_url || p.profileUrl),
112
122
  known: knownUrls.has(p.profile_url || p.profileUrl),
113
123
  seniority: getSeniority(p.headline),
114
- mutualCount: people.filter(op => d1Urls.has(op.profile_url || op.profileUrl) && op.id !== p.id).length,
124
+ mutualCount: connectedHere - (connectedById.get(p.id) || 0),
115
125
  }));
116
126
  }
117
127
 
@@ -177,6 +187,9 @@ function PathsInner() {
177
187
  }
178
188
 
179
189
  // Build hierarchy for selected company
190
+ const d1ById = new Map();
191
+ for (const d of d1Data) if (!d1ById.has(d.id)) d1ById.set(d.id, d);
192
+ const d1Urls = new Set(d1Data.map(c => c.profile_url));
180
193
  const hierarchy = {};
181
194
  let connectedCount = 0;
182
195
  companyPeople.forEach(p => {
@@ -195,7 +208,7 @@ function PathsInner() {
195
208
  // Find the bridge path for degree-2 people
196
209
  let bridgeName = null;
197
210
  if (p.degree === 2 && p.source_connection_id) {
198
- const bridge = d1Data.find(d => d.id === p.source_connection_id);
211
+ const bridge = d1ById.get(p.source_connection_id);
199
212
  if (bridge) bridgeName = bridge.name;
200
213
  }
201
214
 
@@ -380,7 +393,7 @@ function PathsInner() {
380
393
  </div>
381
394
 
382
395
  {level.people.map((p, pi) => (
383
- <PathPersonRow key={p.id || pi} p={p} level={level} companyPeople={companyPeople} d1Data={d1Data} selectedCompany={selectedCompany} />
396
+ <PathPersonRow key={p.id || pi} p={p} level={level} companyPeople={companyPeople} d1Urls={d1Urls} selectedCompany={selectedCompany} />
384
397
  ))}
385
398
  </div>
386
399
  ))}
@@ -391,24 +404,18 @@ function PathsInner() {
391
404
  );
392
405
  }
393
406
 
394
- function PathPersonRow({ p, level, companyPeople, d1Data, selectedCompany }) {
407
+ function PathPersonRow({ p, level, companyPeople, d1Urls, selectedCompany }) {
395
408
  const [expanded, setExpanded] = useState(false);
396
409
 
397
- // Calculate strategic insights
398
- const d1Urls = new Set(d1Data.map(c => c.profile_url));
399
- const connectedAtCompany = companyPeople.filter(op => d1Urls.has(op.profile_url || op.profileUrl));
400
- const aboveMe = companyPeople.filter(op => {
401
- const s = getSeniority(op.headline);
402
- return s.level > p.seniority.level && !d1Urls.has(op.profile_url || op.profileUrl);
403
- });
404
- const sameLevel = companyPeople.filter(op => {
405
- const s = getSeniority(op.headline);
406
- return s.level === p.seniority.level && op.id !== p.id;
407
- });
408
-
409
- // What happens if you add this person
410
+ // What happens if you add this person. Only worked out for a row that's open,
411
+ // since only an open row shows it: every row comparing itself with everyone
412
+ // at the company, each time the page drew, took minutes at a company of 1,900.
413
+ // Everyone's level is read once already (enrichPeople's seniority).
410
414
  const unlockInsights = [];
411
- if (!p.connected) {
415
+ if (expanded && !p.connected) {
416
+ const aboveMe = companyPeople.filter(op => op.seniority.level > p.seniority.level && !d1Urls.has(op.profile_url || op.profileUrl));
417
+ const sameLevel = companyPeople.filter(op => op.seniority.level === p.seniority.level && op.id !== p.id);
418
+
412
419
  // How many people above would gain a mutual
413
420
  const aboveCount = aboveMe.length;
414
421
  if (aboveCount > 0) {
@@ -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(headline || role);
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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "six-degrees",
3
- "version": "0.4.7",
3
+ "version": "0.4.8",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "six-degrees",
9
- "version": "0.4.7",
9
+ "version": "0.4.8",
10
10
  "license": "MIT",
11
11
  "os": [
12
12
  "darwin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "six-degrees",
3
- "version": "0.4.7",
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",