six-degrees 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +2 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js +2 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js +2 -2
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js +3 -2
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +2 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +2 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/messages/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/social/messages/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/social/messages/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js +9 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/social/messages/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/social/route.js +4 -2
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +15 -15
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +15 -15
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +15 -15
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +15 -15
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +15 -15
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +15 -15
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +15 -15
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06mi9k2._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08f5awg._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bat8nx._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0ea9qwy._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +6 -6
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1xh07bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_social_messages_route_actions_0skh09r.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_18cr1gi._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0btpv1t.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_1zc5q0a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1feicqq._.js → [root-of-the-server]__0qdeuyo._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0ezj4e0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1mj739k._.js → _0l8cxmo._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1errbuf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0wxbq78._.js → app_components_19c2d9c._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_1wax83z._.js → node_modules_0h91jdk._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_1nkgb45._.js → node_modules_1f8t_rp._.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/07dyt47mp4s9-.js +1 -0
- package/.next/standalone/.next/static/chunks/{3q3pmt5vv-4pi.js → 160wjb73ddwqd.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1z-zp93cyoxvz.js → 1c_n0973t07l0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0bo1dxn5gwzpk.css → 1xptlvtcz6654.css} +1 -1
- package/.next/standalone/.next/static/chunks/{2_wcobfqry2uj.js → 206rm6j42gl_a.js} +1 -1
- package/.next/standalone/.next/static/chunks/2coxzjm_k6ilr.js +1 -0
- package/.next/{static/chunks/0g_p4p7oci18u.js → standalone/.next/static/chunks/2kfhc58df2xyo.js} +1 -1
- package/.next/standalone/.next/static/chunks/2pw6_osz1n_te.js +1 -0
- package/.next/standalone/.next/static/chunks/{38wwtni5e1ec1.js → 39hr8d-b2ibtw.js} +1 -1
- package/.next/standalone/CHANGELOG.md +48 -0
- package/.next/standalone/app/api/social/messages/route.js +130 -0
- package/.next/standalone/app/api/social/route.js +89 -47
- package/.next/standalone/app/components/GalaxyLab.js +4 -1
- package/.next/standalone/app/components/ScanStatusBar.js +112 -50
- package/.next/standalone/app/components/settings/DataSection.js +2 -1
- package/.next/standalone/app/globals.css +4 -0
- package/.next/standalone/app/setup/page.js +4 -3
- package/.next/standalone/app/social/Conversations.js +246 -0
- package/.next/standalone/app/social/page.js +88 -9
- package/.next/standalone/lib/galaxy-lab.js +6 -1
- package/.next/standalone/lib/island.js +36 -0
- package/.next/standalone/lib/linkedin-export.js +175 -5
- package/.next/standalone/lib/social-store.js +73 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +156 -23
- package/.next/static/chunks/07dyt47mp4s9-.js +1 -0
- package/.next/static/chunks/{3q3pmt5vv-4pi.js → 160wjb73ddwqd.js} +1 -1
- package/.next/static/chunks/{1z-zp93cyoxvz.js → 1c_n0973t07l0.js} +1 -1
- package/.next/static/chunks/{0bo1dxn5gwzpk.css → 1xptlvtcz6654.css} +1 -1
- package/.next/static/chunks/{2_wcobfqry2uj.js → 206rm6j42gl_a.js} +1 -1
- package/.next/static/chunks/2coxzjm_k6ilr.js +1 -0
- package/.next/{standalone/.next/static/chunks/0g_p4p7oci18u.js → static/chunks/2kfhc58df2xyo.js} +1 -1
- package/.next/static/chunks/2pw6_osz1n_te.js +1 -0
- package/.next/static/chunks/{38wwtni5e1ec1.js → 39hr8d-b2ibtw.js} +1 -1
- package/CHANGELOG.md +48 -0
- package/lib/galaxy-lab.js +6 -1
- package/lib/island.js +36 -0
- package/lib/linkedin-export.js +175 -5
- package/lib/social-store.js +73 -0
- package/package.json +1 -1
- package/scripts/scrape.py +156 -23
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17kn1mk._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1dmc7-t._.js +0 -8
- package/.next/standalone/.next/server/chunks/ssr/_005q9n1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_217nddo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_0i3fw_m._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +0 -3
- package/.next/standalone/.next/static/chunks/0y1yms5kez2f3.js +0 -1
- package/.next/standalone/.next/static/chunks/14m1zu-8warte.js +0 -1
- package/.next/standalone/.next/static/chunks/15qbli9udo0vh.js +0 -1
- package/.next/static/chunks/0y1yms5kez2f3.js +0 -1
- package/.next/static/chunks/14m1zu-8warte.js +0 -1
- package/.next/static/chunks/15qbli9udo0vh.js +0 -1
- /package/.next/standalone/.next/static/{T0eWMsKjgSiPnmx1E0q3G → 7zWJnSkfSoRhRsqY6ZKtD}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{T0eWMsKjgSiPnmx1E0q3G → 7zWJnSkfSoRhRsqY6ZKtD}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{T0eWMsKjgSiPnmx1E0q3G → 7zWJnSkfSoRhRsqY6ZKtD}/_ssgManifest.js +0 -0
- /package/.next/static/{T0eWMsKjgSiPnmx1E0q3G → 7zWJnSkfSoRhRsqY6ZKtD}/_buildManifest.js +0 -0
- /package/.next/static/{T0eWMsKjgSiPnmx1E0q3G → 7zWJnSkfSoRhRsqY6ZKtD}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{T0eWMsKjgSiPnmx1E0q3G → 7zWJnSkfSoRhRsqY6ZKtD}/_ssgManifest.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.10] - 2026-09-29
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **The scan status bar is now a notch.** A small pill hangs from the bottom of the header,
|
|
13
|
+
centred, on every page. It shows nothing while nothing runs, only a tiny dimmed *Auto scan*
|
|
14
|
+
while the all-day mode waits, and while a scan runs, what it's doing with a thin progress
|
|
15
|
+
line. Hover or click to open it: who, today's searches, the latest line, *Details* and
|
|
16
|
+
*Stop*. Other long jobs show there too, starting with recording the Galaxy's replay.
|
|
17
|
+
|
|
18
|
+
## [0.4.9] - 2026-09-29
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **Conversations, in the Social tab.** Every conversation with one of your connections, from your
|
|
22
|
+
export and the live messages sync: who it's with (name and tier), when it was last active, who
|
|
23
|
+
wrote last, unread, how many messages, and *Open on LinkedIn* (the thread when the sync found
|
|
24
|
+
its link, else their profile). Waiting on you comes first, then S and A tier, then the most
|
|
25
|
+
recent. Group conversations are shown and marked, and aren't counted in warmth. A search box
|
|
26
|
+
finds people by name.
|
|
27
|
+
- **Keep my messages on this computer**, a switch that's off unless you turn it on. When it's on,
|
|
28
|
+
the messages themselves, what other people wrote to you included, are kept in the app's data
|
|
29
|
+
folder (a file of their own beside the Social tab's), and a conversation opens as a thread,
|
|
30
|
+
newest at the bottom, a page at a time so a thread of thousands opens as fast as a short one.
|
|
31
|
+
The search box then finds what was said, too. They're never sent anywhere, and "Save a copy of
|
|
32
|
+
my network" doesn't carry them (Settings says so). Switching it off asks first, then deletes
|
|
33
|
+
them; *Forget it* deletes them with everything else.
|
|
34
|
+
- **The live messages sync reads a little more of the list.** Each conversation's link, whether
|
|
35
|
+
it's a group, and its newest message: who wrote last, which now feeds warmth and Waiting on you
|
|
36
|
+
where your export is missing or older, and its words, which go to the app only while Keep my
|
|
37
|
+
messages is on. It still opens no conversation, and its saved samples still keep no words.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- **Auto scan is slower and has its own ceilings.** Sittings of 8 pages, then an hour's rest;
|
|
41
|
+
searches only from 9:00 to 18:00; never more than 40 searches in 24 hours or 200 in 7 days,
|
|
42
|
+
however high the daily budget is set (the account restricted on 28 September did 373 in a
|
|
43
|
+
day with the budget at 500); and two days with no searches after any check from LinkedIn.
|
|
44
|
+
- **The scan status bar sits under the buttons.** On each page it now sits in its own row
|
|
45
|
+
under the header, pushing the page down instead of covering it. On Network Circle it floats
|
|
46
|
+
just under the header, measured, so a header that wraps to two rows no longer hides it.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
- **The live messages sync keeps what it finds.** It read your conversations but matched none of
|
|
50
|
+
them: the pattern for a profile link never matched, and LinkedIn's messaging gives a member
|
|
51
|
+
link your connections list doesn't have. It now matches each person to your connections by
|
|
52
|
+
link, or by a name only one connection has (the name isn't kept), and scrolls the list's own
|
|
53
|
+
panel so more than the first 20 conversations load.
|
|
54
|
+
- **Messaging samples keep no words.** The sync's saved samples of LinkedIn's data (for tuning
|
|
55
|
+
the reader) now drop every piece of writing and every name before they're saved.
|
|
56
|
+
|
|
9
57
|
## [0.4.8] - 2026-09-29
|
|
10
58
|
|
|
11
59
|
### Changed
|
package/lib/galaxy-lab.js
CHANGED
|
@@ -215,7 +215,12 @@ export function mergeSocial(social) {
|
|
|
215
215
|
for (const [url, v] of Object.entries(social?.live || {})) {
|
|
216
216
|
const k = keyFor({ profile_url: url });
|
|
217
217
|
const cur = merged.get(k) || { total: 0, recent: 0 };
|
|
218
|
-
|
|
218
|
+
// The live sync's word on who wrote last counts when it's the newer news:
|
|
219
|
+
// no export yet, or the conversation moved on since the export was made.
|
|
220
|
+
if (v.last && (!cur.last || v.last > cur.last)) {
|
|
221
|
+
cur.last = v.last;
|
|
222
|
+
if (typeof v.lastFromThem === 'boolean') cur.lastFromThem = v.lastFromThem;
|
|
223
|
+
}
|
|
219
224
|
cur.unread = v.unread;
|
|
220
225
|
cur.total = Math.max(cur.total || 0, 1);
|
|
221
226
|
merged.set(k, cur);
|
package/lib/island.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The notch (app/components/ScanStatusBar.js): one small pill hanging under the
|
|
2
|
+
// header that shows what the app is doing. The scanner shows there on its own;
|
|
3
|
+
// anything else that runs for a while (a replay being recorded, an import) can
|
|
4
|
+
// show there too, through this list.
|
|
5
|
+
//
|
|
6
|
+
// const done = showActivity({ id: 'replay', label: 'Recording the replay', detail: '12 s' });
|
|
7
|
+
// … later: done();
|
|
8
|
+
//
|
|
9
|
+
// An activity is { id, label, detail?, progress? (0–1), tone? ('run' | 'idle' | 'warn') }.
|
|
10
|
+
// Showing the same id again replaces it. The list only changes when something
|
|
11
|
+
// in it does, as useSyncExternalStore needs.
|
|
12
|
+
|
|
13
|
+
const listeners = new Set();
|
|
14
|
+
let activities = [];
|
|
15
|
+
|
|
16
|
+
export const activitiesNow = () => activities;
|
|
17
|
+
const EMPTY = [];
|
|
18
|
+
export const noActivities = () => EMPTY;
|
|
19
|
+
|
|
20
|
+
export function watchActivities(f) {
|
|
21
|
+
listeners.add(f);
|
|
22
|
+
return () => listeners.delete(f);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function showActivity(a) {
|
|
26
|
+
if (!a?.id) return () => {};
|
|
27
|
+
activities = [...activities.filter((x) => x.id !== a.id), a];
|
|
28
|
+
listeners.forEach((f) => f());
|
|
29
|
+
return () => clearActivity(a.id);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function clearActivity(id) {
|
|
33
|
+
if (!activities.some((x) => x.id === id)) return;
|
|
34
|
+
activities = activities.filter((x) => x.id !== id);
|
|
35
|
+
listeners.forEach((f) => f());
|
|
36
|
+
}
|
package/lib/linkedin-export.js
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
//
|
|
7
7
|
// people per profile: when you last messaged, who wrote last, how many
|
|
8
8
|
// messages (1:1 conversations only). Never a message's text.
|
|
9
|
+
// conversations per conversation: who's in it, when it was last active, who
|
|
10
|
+
// wrote last, how many messages. The text itself only when you turn
|
|
11
|
+
// on Keep my messages, and then in a file of its own
|
|
12
|
+
// (app/api/social/messages).
|
|
9
13
|
// chapters your jobs (Positions.csv) and the connections made in each
|
|
10
14
|
// posts your posts (Shares.csv) and the connections that came the week after
|
|
11
15
|
// invites requests to and from you (Invitations.csv), when the export has them
|
|
@@ -49,14 +53,29 @@ export function exportDate(s) {
|
|
|
49
53
|
|
|
50
54
|
const urlKey = (u) => keyFor({ profile_url: u });
|
|
51
55
|
|
|
56
|
+
/** You, in messages.csv: the sender in the most conversations. */
|
|
57
|
+
function selfOf(rows = []) {
|
|
58
|
+
const senders = new Map();
|
|
59
|
+
for (const r of rows) {
|
|
60
|
+
const from = urlKey(r['sender profile url']);
|
|
61
|
+
const id = r['conversation id'];
|
|
62
|
+
if (!id || !from) continue;
|
|
63
|
+
if (!senders.has(from)) senders.set(from, new Set());
|
|
64
|
+
senders.get(from).add(id);
|
|
65
|
+
}
|
|
66
|
+
let self = null;
|
|
67
|
+
for (const [who, ids] of senders) if (!self || ids.size > senders.get(self).size) self = who;
|
|
68
|
+
return self;
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
/**
|
|
53
72
|
* From messages.csv, per person (by profile): { last, lastFromThem, total, recent }.
|
|
54
73
|
* `self` is you: the sender in the most conversations. Only 1:1 conversations
|
|
55
74
|
* count; group threads say little about one relationship. No text is read.
|
|
56
75
|
*/
|
|
57
76
|
export function messageStats(rows = []) {
|
|
77
|
+
const self = selfOf(rows);
|
|
58
78
|
const convos = new Map();
|
|
59
|
-
const senders = new Map();
|
|
60
79
|
for (const r of rows) {
|
|
61
80
|
const from = urlKey(r['sender profile url']);
|
|
62
81
|
const id = r['conversation id'];
|
|
@@ -69,11 +88,7 @@ export function messageStats(rows = []) {
|
|
|
69
88
|
c.people.add(from);
|
|
70
89
|
to.forEach((p) => c.people.add(p));
|
|
71
90
|
c.msgs.push({ from, t });
|
|
72
|
-
if (!senders.has(from)) senders.set(from, new Set());
|
|
73
|
-
senders.get(from).add(id);
|
|
74
91
|
}
|
|
75
|
-
let self = null;
|
|
76
|
-
for (const [who, ids] of senders) if (!self || ids.size > senders.get(self).size) self = who;
|
|
77
92
|
let asOf = 0;
|
|
78
93
|
const people = new Map();
|
|
79
94
|
for (const c of convos.values()) {
|
|
@@ -96,6 +111,104 @@ export function messageStats(rows = []) {
|
|
|
96
111
|
return { self, asOf: asOf || null, people };
|
|
97
112
|
}
|
|
98
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Every conversation in messages.csv you're part of, one record each:
|
|
116
|
+
* { id, title, people, group, messages: [{ t, fromMe, text }] }, messages oldest
|
|
117
|
+
* first. `people` is everyone but you, by profile key; more than one makes it a
|
|
118
|
+
* group. You are found the way messageStats finds you. The words (the subject,
|
|
119
|
+
* when there is one, then the message) are read only with keepText: without it
|
|
120
|
+
* a message is a date and who sent it, and the title (a group's name) is left
|
|
121
|
+
* out too.
|
|
122
|
+
*/
|
|
123
|
+
export function buildConversations(rows = [], { keepText = false } = {}) {
|
|
124
|
+
const self = selfOf(rows);
|
|
125
|
+
const convos = new Map();
|
|
126
|
+
for (const r of rows) {
|
|
127
|
+
const from = urlKey(r['sender profile url']);
|
|
128
|
+
const id = r['conversation id'];
|
|
129
|
+
if (!id || !from) continue;
|
|
130
|
+
const t = exportDate(r.date);
|
|
131
|
+
if (t == null) continue;
|
|
132
|
+
if (!convos.has(id)) convos.set(id, { id, title: '', everyone: new Set(), messages: [] });
|
|
133
|
+
const c = convos.get(id);
|
|
134
|
+
c.everyone.add(from);
|
|
135
|
+
String(r['recipient profile urls'] || '').split(/[,;\s]+/).filter(Boolean).forEach((u) => c.everyone.add(urlKey(u)));
|
|
136
|
+
const m = { t, fromMe: from === self };
|
|
137
|
+
if (keepText) {
|
|
138
|
+
m.text = [r.subject, r.content].map((x) => String(x || '').trim()).filter(Boolean).join('\n\n');
|
|
139
|
+
if (!c.title && r['conversation title']) c.title = String(r['conversation title']).trim();
|
|
140
|
+
}
|
|
141
|
+
c.messages.push(m);
|
|
142
|
+
}
|
|
143
|
+
const conversations = [];
|
|
144
|
+
for (const c of convos.values()) {
|
|
145
|
+
if (!c.everyone.has(self)) continue;
|
|
146
|
+
const people = [...c.everyone].filter((p) => p && p !== self).sort();
|
|
147
|
+
if (!people.length) continue;
|
|
148
|
+
c.messages.sort((a, b) => a.t - b.t);
|
|
149
|
+
const out = { id: c.id, people, group: people.length > 1, messages: c.messages };
|
|
150
|
+
if (keepText) out.title = c.title;
|
|
151
|
+
conversations.push(out);
|
|
152
|
+
}
|
|
153
|
+
return { self, conversations };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** What the Conversations list keeps of one conversation, never any words: { id, people, group, last, lastFromThem, count }. */
|
|
157
|
+
export function conversationSummary(c) {
|
|
158
|
+
const last = c.messages[c.messages.length - 1];
|
|
159
|
+
return {
|
|
160
|
+
id: c.id,
|
|
161
|
+
people: c.people,
|
|
162
|
+
group: c.group,
|
|
163
|
+
last: last ? last.t : null,
|
|
164
|
+
lastFromThem: last ? !last.fromMe : null,
|
|
165
|
+
count: c.messages.length,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The Conversations list: the export's conversations with what the live sync
|
|
171
|
+
* adds (unread, the thread's link, and a newer last message), plus a
|
|
172
|
+
* conversation for each person the sync found and the export doesn't have yet
|
|
173
|
+
* (id `live:<key>`, count unknown).
|
|
174
|
+
*/
|
|
175
|
+
export function conversationList(list = [], live = {}) {
|
|
176
|
+
const out = (Array.isArray(list) ? list : []).map((c) => ({ ...c, unread: null, threadUrl: null }));
|
|
177
|
+
const oneToOne = new Map();
|
|
178
|
+
for (const c of out) {
|
|
179
|
+
if (c.group || c.people?.length !== 1) continue;
|
|
180
|
+
const had = oneToOne.get(c.people[0]);
|
|
181
|
+
if (!had || (c.last || 0) > (had.last || 0)) oneToOne.set(c.people[0], c);
|
|
182
|
+
}
|
|
183
|
+
for (const [key, v] of Object.entries(live || {})) {
|
|
184
|
+
if (!v || typeof v !== 'object') continue;
|
|
185
|
+
let c = oneToOne.get(key);
|
|
186
|
+
if (!c) {
|
|
187
|
+
c = { id: `live:${key}`, people: [key], group: false, last: null, lastFromThem: null, count: null, unread: null, threadUrl: null };
|
|
188
|
+
out.push(c);
|
|
189
|
+
oneToOne.set(key, c);
|
|
190
|
+
}
|
|
191
|
+
if (v.last && (!c.last || v.last > c.last)) {
|
|
192
|
+
c.last = v.last;
|
|
193
|
+
if (typeof v.lastFromThem === 'boolean') c.lastFromThem = v.lastFromThem;
|
|
194
|
+
}
|
|
195
|
+
c.unread = Number.isFinite(v.unread) ? v.unread : c.unread;
|
|
196
|
+
c.threadUrl = v.threadUrl || c.threadUrl;
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The order the Conversations list shows: waiting on you (they wrote last, one
|
|
203
|
+
* to one) first, then your S and A tier people, then the most recent.
|
|
204
|
+
* `tierOf(conversation)` gives its best tier.
|
|
205
|
+
*/
|
|
206
|
+
export function sortConversations(list = [], tierOf = () => null) {
|
|
207
|
+
const waiting = (c) => (!c.group && c.lastFromThem === true ? 0 : 1);
|
|
208
|
+
const tier = (c) => ({ S: 0, A: 1 }[tierOf(c)] ?? 2);
|
|
209
|
+
return [...list].sort((a, b) => waiting(a) - waiting(b) || tier(a) - tier(b) || (b.last || 0) - (a.last || 0));
|
|
210
|
+
}
|
|
211
|
+
|
|
99
212
|
/** Warm (in the last 30 days, or 3+ in 90), cool (within a year), dormant (longer ago), or never. */
|
|
100
213
|
export function warmthOf(stat, asOf) {
|
|
101
214
|
if (!stat || !stat.total) return 'never';
|
|
@@ -154,3 +267,60 @@ export function invitationSplit(rows = []) {
|
|
|
154
267
|
}
|
|
155
268
|
return { incoming, outgoing, total: incoming + outgoing };
|
|
156
269
|
}
|
|
270
|
+
|
|
271
|
+
const nameKey = (s) => String(s || '').split(',')[0].normalize('NFD').replace(/[̀-ͯ]/g, '')
|
|
272
|
+
.toLowerCase().replace(/[^a-z0-9 ]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
273
|
+
|
|
274
|
+
const THREAD_LINK = /^https:\/\/www\.linkedin\.com\/messaging\/[\w\-/%=.]*$/;
|
|
275
|
+
|
|
276
|
+
/** A link to a LinkedIn messaging thread, or null: nothing else is ever put behind "Open on LinkedIn". */
|
|
277
|
+
export function threadLink(u) {
|
|
278
|
+
const v = String(u || '').trim();
|
|
279
|
+
return THREAD_LINK.test(v) ? v : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** A message the live sync read, as it's kept: { t, fromMe, text }, or null. */
|
|
283
|
+
function previewOf(p) {
|
|
284
|
+
if (!p || typeof p !== 'object') return null;
|
|
285
|
+
const t = Number(p.t) || null;
|
|
286
|
+
const text = typeof p.text === 'string' ? p.text.slice(0, 20000) : null;
|
|
287
|
+
if (!t && !text) return null;
|
|
288
|
+
return { t, fromMe: p.fromMe === true, text };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The live messages sync's people, matched to your connections. LinkedIn's
|
|
293
|
+
* messaging gives a member link (/in/ACoA…) your connections list doesn't have,
|
|
294
|
+
* so a person is matched by their link when it's the same, otherwise by their
|
|
295
|
+
* name when exactly one connection has it. Only the match is kept, never the
|
|
296
|
+
* name. Returns { live: { connectionKey: { last, unread, threadUrl,
|
|
297
|
+
* lastFromThem, preview } }, unmatched }; the caller decides whether the
|
|
298
|
+
* preview's words are kept.
|
|
299
|
+
*/
|
|
300
|
+
export function matchLive(incoming = {}, connections = []) {
|
|
301
|
+
const byUrl = new Map();
|
|
302
|
+
const byName = new Map();
|
|
303
|
+
for (const c of connections) {
|
|
304
|
+
const key = keyFor(c);
|
|
305
|
+
byUrl.set(key, key);
|
|
306
|
+
const n = nameKey(c.name);
|
|
307
|
+
if (n) byName.set(n, byName.has(n) ? null : key); // null: two people share it
|
|
308
|
+
}
|
|
309
|
+
const live = {};
|
|
310
|
+
let unmatched = 0;
|
|
311
|
+
for (const [url, v] of Object.entries(incoming || {})) {
|
|
312
|
+
if (!v || typeof v !== 'object') continue;
|
|
313
|
+
const key = byUrl.get(keyFor({ profile_url: url })) || byName.get(nameKey(v.name)) || null;
|
|
314
|
+
if (!key) { unmatched += 1; continue; }
|
|
315
|
+
const last = Number(v.last) || null;
|
|
316
|
+
if (live[key] && (live[key].last || 0) >= (last || 0)) continue;
|
|
317
|
+
live[key] = {
|
|
318
|
+
last,
|
|
319
|
+
unread: Number.isFinite(Number(v.unread)) && v.unread !== null ? Number(v.unread) : null,
|
|
320
|
+
threadUrl: threadLink(v.threadUrl),
|
|
321
|
+
lastFromThem: typeof v.lastFromThem === 'boolean' ? v.lastFromThem : null,
|
|
322
|
+
preview: previewOf(v.preview),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return { live, unmatched };
|
|
326
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Where the Social tab keeps what it knows, per profile, in the data folder:
|
|
2
|
+
//
|
|
3
|
+
// social-<profile>.json numbers and dates (app/api/social): who you
|
|
4
|
+
// messaged, when, who wrote last, the list of
|
|
5
|
+
// conversations. Never any words.
|
|
6
|
+
// social-messages-<profile>.json the messages themselves, only while Keep my
|
|
7
|
+
// messages is on (app/api/social/messages).
|
|
8
|
+
//
|
|
9
|
+
// Two files so that turning Keep my messages off, or Forget it, deletes the
|
|
10
|
+
// words by deleting one file, and so the everyday file the Galaxy and the
|
|
11
|
+
// Social tab read stays small however many messages there are. Neither is in
|
|
12
|
+
// "Save a copy of my network": that carries an allow-list of files
|
|
13
|
+
// (lib/data-folder.js TRAVELLING_FILES) and these aren't on it.
|
|
14
|
+
|
|
15
|
+
import { readFileSync, writeFileSync, renameSync, rmSync, statSync } from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { dataDir } from './db-client';
|
|
18
|
+
import { resolveProfile } from './profile';
|
|
19
|
+
|
|
20
|
+
// The last messages file read, while it's unchanged (readMessages).
|
|
21
|
+
const cache = { file: null, mtimeMs: 0, size: 0, data: null };
|
|
22
|
+
|
|
23
|
+
/** The Social tab's files for the profile in use, or null before there is one. */
|
|
24
|
+
export function socialFiles() {
|
|
25
|
+
const me = resolveProfile({ create: false });
|
|
26
|
+
if (!me?.id || !/^[\w-]+$/.test(String(me.id))) return null;
|
|
27
|
+
const dir = dataDir();
|
|
28
|
+
const messages = path.join(dir, `social-messages-${me.id}.json`);
|
|
29
|
+
return {
|
|
30
|
+
profileId: me.id,
|
|
31
|
+
social: path.join(dir, `social-${me.id}.json`),
|
|
32
|
+
messages,
|
|
33
|
+
// An import's messages arrive in parts (each request stays under the 10 MB
|
|
34
|
+
// middleware reads); they gather here and replace `messages` at the end.
|
|
35
|
+
incoming: `${messages}.incoming`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function readJson(file, fallback = null) {
|
|
40
|
+
try { return JSON.parse(readFileSync(file, 'utf8')) ?? fallback; } catch { return fallback; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Written whole or not at all: a crash mid-write never leaves half a file. */
|
|
44
|
+
export function writeJsonAtomic(file, value) {
|
|
45
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
46
|
+
writeFileSync(tmp, JSON.stringify(value));
|
|
47
|
+
renameSync(tmp, file);
|
|
48
|
+
if (cache.file === file) cache.file = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The kept messages and their live-sync previews, or empty ones. */
|
|
52
|
+
export function readMessages(file) {
|
|
53
|
+
// Kept in memory while the file is unchanged: a long history is read once,
|
|
54
|
+
// not on every page of a thread or every letter typed in the search box.
|
|
55
|
+
let st;
|
|
56
|
+
try { st = statSync(file); } catch { return { threads: {}, previews: {} }; }
|
|
57
|
+
if (cache.file === file && cache.mtimeMs === st.mtimeMs && cache.size === st.size) return cache.data;
|
|
58
|
+
const raw = readJson(file, {});
|
|
59
|
+
const data = {
|
|
60
|
+
threads: raw?.threads && typeof raw.threads === 'object' ? raw.threads : {},
|
|
61
|
+
previews: raw?.previews && typeof raw.previews === 'object' ? raw.previews : {},
|
|
62
|
+
};
|
|
63
|
+
Object.assign(cache, { file, mtimeMs: st.mtimeMs, size: st.size, data });
|
|
64
|
+
return data;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The messages gone: the kept file and any import still arriving. */
|
|
68
|
+
export function deleteMessages(files) {
|
|
69
|
+
if (!files) return;
|
|
70
|
+
rmSync(files.messages, { force: true });
|
|
71
|
+
rmSync(files.incoming, { force: true });
|
|
72
|
+
cache.file = null;
|
|
73
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "six-degrees",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
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",
|