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.
Files changed (180) 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 +11 -11
  4. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  5. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.rsc +11 -11
  13. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
  14. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +9 -9
  15. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  37. package/.next/standalone/.next/server/app/import.html +1 -1
  38. package/.next/standalone/.next/server/app/import.rsc +13 -13
  39. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
  40. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
  41. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +11 -11
  42. package/.next/standalone/.next/server/app/index.html +1 -1
  43. package/.next/standalone/.next/server/app/index.rsc +13 -13
  44. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +11 -11
  45. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
  46. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  47. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/launch.html +2 -2
  50. package/.next/standalone/.next/server/app/launch.rsc +13 -13
  51. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
  52. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
  53. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +11 -11
  54. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/paths.html +1 -1
  59. package/.next/standalone/.next/server/app/paths.rsc +13 -13
  60. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
  61. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
  62. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +11 -11
  63. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  65. package/.next/standalone/.next/server/app/profile.html +1 -1
  66. package/.next/standalone/.next/server/app/profile.rsc +13 -13
  67. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
  68. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
  69. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +11 -11
  70. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/queue.html +1 -1
  73. package/.next/standalone/.next/server/app/queue.rsc +13 -13
  74. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
  75. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
  76. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +11 -11
  77. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  78. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  79. package/.next/standalone/.next/server/app/setup.html +1 -1
  80. package/.next/standalone/.next/server/app/setup.rsc +13 -13
  81. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
  82. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  83. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +11 -11
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jrmmj-._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qx548g._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1an3leo._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1m8zedk._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/_1_fynia._.js +2 -2
  89. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h1_f_2._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/ssr/_0bf3x4q._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/_1djpk8t._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +2 -2
  99. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
  100. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  101. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  102. package/.next/standalone/.next/server/pages/404.html +1 -1
  103. package/.next/standalone/.next/server/pages/500.html +1 -1
  104. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  105. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  106. package/.next/standalone/.next/static/chunks/0jk64vo17avsb.js +1 -0
  107. package/.next/standalone/.next/static/chunks/0s65dsrthylo6.js +2 -0
  108. package/.next/standalone/.next/static/chunks/0uyfdhi8dek85.js +16 -0
  109. package/.next/standalone/.next/static/chunks/{1zmpcmngg5c7-.js → 229haorf0f19b.js} +1 -1
  110. package/.next/{static/chunks/3ts-_4i6bfkh2.js → standalone/.next/static/chunks/2g54uafnwuakc.js} +1 -1
  111. package/.next/standalone/.next/static/chunks/2ot61qsv6_0eq.js +1 -0
  112. package/.next/{static/chunks/3pfyuezyqd9ob.js → standalone/.next/static/chunks/2spwqs6b6dx13.js} +1 -1
  113. package/.next/{static/chunks/1odow92u6wxz0.js → standalone/.next/static/chunks/38dlepuubiox0.js} +1 -1
  114. package/.next/standalone/AGENTS.md +10 -0
  115. package/.next/standalone/CHANGELOG.md +41 -0
  116. package/.next/standalone/CONTRIBUTING.md +9 -0
  117. package/.next/standalone/README.md +49 -29
  118. package/.next/standalone/SECURITY.md +12 -9
  119. package/.next/standalone/app/api/scraper/route.js +4 -3
  120. package/.next/standalone/app/api/update/route.js +10 -2
  121. package/.next/standalone/app/components/ForceGraph.js +1 -1
  122. package/.next/standalone/app/components/Sidebar.js +8 -8
  123. package/.next/standalone/app/components/UpdatePanel.js +39 -8
  124. package/.next/standalone/app/import/page.js +3 -3
  125. package/.next/standalone/app/layout.js +2 -2
  126. package/.next/standalone/app/page.js +2 -2
  127. package/.next/standalone/app/paths/page.js +1 -1
  128. package/.next/standalone/app/profile/page.js +0 -178
  129. package/.next/standalone/app/setup/page.js +4 -4
  130. package/.next/standalone/bin/six-degrees.mjs +7 -2
  131. package/.next/standalone/install.sh +1 -1
  132. package/.next/standalone/lib/csv.js +53 -27
  133. package/.next/standalone/lib/release.js +5 -2
  134. package/.next/standalone/lib/scraper-client.js +3 -3
  135. package/.next/standalone/package-lock.json +103 -9
  136. package/.next/standalone/package.json +12 -10
  137. package/.next/standalone/public/demo-data.json +1 -1
  138. package/.next/standalone/scripts/gen-synthetic.mjs +16 -1
  139. package/.next/standalone/scripts/readme_buttons.py +89 -0
  140. package/.next/standalone/scripts/screenshots.py +2 -0
  141. package/.next/standalone/site/app.js +84 -0
  142. package/.next/standalone/site/index.html +221 -0
  143. package/.next/standalone/site/style.css +155 -0
  144. package/.next/standalone/site/video/install-mac-poster.jpg +0 -0
  145. package/.next/standalone/site/video/install-mac.mp4 +0 -0
  146. package/.next/standalone/site/video/linux-poster.jpg +0 -0
  147. package/.next/standalone/site/video/linux.mp4 +0 -0
  148. package/.next/standalone/site/video/scan-poster.jpg +0 -0
  149. package/.next/standalone/site/video/scan.mp4 +0 -0
  150. package/.next/static/chunks/0jk64vo17avsb.js +1 -0
  151. package/.next/static/chunks/0s65dsrthylo6.js +2 -0
  152. package/.next/static/chunks/0uyfdhi8dek85.js +16 -0
  153. package/.next/static/chunks/{1zmpcmngg5c7-.js → 229haorf0f19b.js} +1 -1
  154. package/.next/{standalone/.next/static/chunks/3ts-_4i6bfkh2.js → static/chunks/2g54uafnwuakc.js} +1 -1
  155. package/.next/static/chunks/2ot61qsv6_0eq.js +1 -0
  156. package/.next/{standalone/.next/static/chunks/3pfyuezyqd9ob.js → static/chunks/2spwqs6b6dx13.js} +1 -1
  157. package/.next/{standalone/.next/static/chunks/1odow92u6wxz0.js → static/chunks/38dlepuubiox0.js} +1 -1
  158. package/CHANGELOG.md +41 -0
  159. package/README.md +49 -29
  160. package/SECURITY.md +12 -9
  161. package/bin/six-degrees.mjs +7 -2
  162. package/lib/csv.js +53 -27
  163. package/lib/release.js +5 -2
  164. package/lib/scraper-client.js +3 -3
  165. package/package.json +12 -10
  166. package/public/demo-data.json +1 -1
  167. package/.next/standalone/.next/static/chunks/1wb_is4wsdm-u.js +0 -1
  168. package/.next/standalone/.next/static/chunks/2rrf5kzx2fhin.js +0 -2
  169. package/.next/standalone/.next/static/chunks/2s_k9x4d0gi9h.js +0 -16
  170. package/.next/standalone/.next/static/chunks/3hsi90cejq2dg.js +0 -1
  171. package/.next/static/chunks/1wb_is4wsdm-u.js +0 -1
  172. package/.next/static/chunks/2rrf5kzx2fhin.js +0 -2
  173. package/.next/static/chunks/2s_k9x4d0gi9h.js +0 -16
  174. package/.next/static/chunks/3hsi90cejq2dg.js +0 -1
  175. /package/.next/standalone/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_buildManifest.js +0 -0
  176. /package/.next/standalone/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_clientMiddlewareManifest.js +0 -0
  177. /package/.next/standalone/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_ssgManifest.js +0 -0
  178. /package/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_buildManifest.js +0 -0
  179. /package/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_clientMiddlewareManifest.js +0 -0
  180. /package/.next/static/{N9wokeIiC9rUIGmzBhj6T → ZGTiDrZ5NHGETgGdlhssu}/_ssgManifest.js +0 -0
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect, useState } from 'react';
4
- import { runScrape, scraperStatus, notReadyMessage } from '../../lib/scraper-client';
5
4
  import { loadNetwork } from '../../lib/network';
6
5
  import MappingProgress from '../components/MappingProgress';
7
6
  import { IS_DEMO } from '../../lib/demo';
@@ -33,10 +32,6 @@ function ProfileInner() {
33
32
  const [notifications, setNotifications] = useState([]);
34
33
  const [queueStats, setQueueStats] = useState({ total: 0, sent: 0, accepted: 0 });
35
34
  const [mapping, setMapping] = useState({ degree1: [], degree2: [], skips: [] });
36
- const [setupStatus, setSetupStatus] = useState('idle');
37
- const [setupLog, setSetupLog] = useState([]);
38
- const [bridgeStatus, setBridgeStatus] = useState('idle');
39
- const [bridgeLog, setBridgeLog] = useState([]);
40
35
 
41
36
  useEffect(() => {
42
37
  if (IS_DEMO) return;
@@ -207,104 +202,6 @@ function ProfileInner() {
207
202
  </div>
208
203
  </div>
209
204
 
210
- {/* === SET UP ACCOUNT === */}
211
- <SetupScrapeCard status={setupStatus} log={setupLog} onStart={async () => {
212
- setSetupStatus('running');
213
- setSetupLog(['Checking the scraper...']);
214
- const blocked = notReadyMessage(await scraperStatus().catch(() => null));
215
- if (blocked) {
216
- setSetupStatus('offline');
217
- setSetupLog([blocked]);
218
- return;
219
- }
220
- try {
221
- const final = await runScrape('full', { onLog: setSetupLog });
222
- setSetupStatus(final.exitCode === 0 ? 'done' : 'error');
223
- } catch { setSetupStatus('error'); }
224
- }} onRetry={() => { setSetupStatus('idle'); setSetupLog([]); }} />
225
-
226
- {/* === AUTO-BRIDGE ALL === */}
227
- <div style={{
228
- background: 'rgba(155,89,182,0.06)', borderRadius: 12, padding: 20, marginBottom: 24,
229
- border: '1px solid rgba(155,89,182,0.2)',
230
- }}>
231
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
232
- <h3 style={{ fontSize: 14, fontWeight: 700, margin: 0, color: '#9B59B6' }}>Auto-Bridge All</h3>
233
- {bridgeStatus === 'done' && <span style={{ fontSize: 10, color: '#00ff88', fontWeight: 600 }}>Complete</span>}
234
- </div>
235
- <p style={{ fontSize: 11, color: '#888', margin: '0 0 12px' }}>
236
- Scan every connection&apos;s network. S-tier first → D-tier last. 2 min cooldown between each.
237
- </p>
238
-
239
- {bridgeStatus === 'idle' && (
240
- <button onClick={async () => {
241
- setBridgeStatus('running');
242
- setBridgeLog(['Checking the scraper...']);
243
- const blocked = notReadyMessage(await scraperStatus().catch(() => null));
244
- if (blocked) {
245
- setBridgeStatus('offline');
246
- setBridgeLog([blocked]);
247
- return;
248
- }
249
- try {
250
- const final = await runScrape('auto-bridge', { onLog: setBridgeLog });
251
- setBridgeStatus(final.exitCode === 0 ? 'done' : 'error');
252
- } catch { setBridgeStatus('error'); }
253
- }} style={{
254
- width: '100%', padding: '12px', borderRadius: 8, border: 'none', cursor: 'pointer',
255
- background: 'linear-gradient(135deg, #9B59B6, #FF6B35)',
256
- color: '#fff', fontWeight: 700, fontSize: 14,
257
- }}>
258
- Auto-Bridge All Connections
259
- </button>
260
- )}
261
-
262
- {bridgeStatus === 'offline' && (
263
- <div>
264
- <div style={{ padding: '10px', borderRadius: 8, background: 'rgba(255,80,80,0.1)', border: '1px solid rgba(255,80,80,0.3)', color: '#ff5050', fontSize: 12, marginBottom: 8, textAlign: 'center' }}>
265
- Scraper offline — start it first
266
- </div>
267
- <button onClick={() => setBridgeStatus('idle')} style={{
268
- width: '100%', padding: '10px', borderRadius: 8, border: 'none', cursor: 'pointer',
269
- background: 'rgba(255,255,255,0.1)', color: '#aaa', fontWeight: 600, fontSize: 12,
270
- }}>Retry</button>
271
- </div>
272
- )}
273
-
274
- {bridgeStatus === 'running' && (
275
- <div style={{
276
- padding: '10px', borderRadius: 8, background: 'rgba(155,89,182,0.1)',
277
- border: '1px solid rgba(155,89,182,0.3)',
278
- }}>
279
- <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
280
- <div style={{ width: 8, height: 8, borderRadius: '50%', background: '#9B59B6', animation: 'pulse 1s infinite' }} />
281
- <span style={{ fontSize: 11, fontWeight: 600, color: '#9B59B6' }}>Bridging...</span>
282
- </div>
283
- <div style={{ fontFamily: 'monospace', fontSize: 9, color: '#777', maxHeight: 150, overflow: 'auto' }}>
284
- {bridgeLog.map((l, i) => <div key={i}>{l}</div>)}
285
- </div>
286
- </div>
287
- )}
288
-
289
- {bridgeStatus === 'done' && (
290
- <div style={{ padding: '10px', borderRadius: 8, background: 'rgba(0,255,136,0.1)', border: '1px solid rgba(0,255,136,0.3)', color: '#00ff88', fontSize: 12, fontWeight: 600, textAlign: 'center' }}>
291
- All bridges mapped! Refresh the app.
292
- </div>
293
- )}
294
-
295
- {bridgeStatus === 'error' && (
296
- <div>
297
- <div style={{ padding: '10px', borderRadius: 8, background: 'rgba(255,80,80,0.1)', border: '1px solid rgba(255,80,80,0.3)', color: '#ff5050', fontSize: 12, textAlign: 'center', marginBottom: 8 }}>
298
- Something went wrong
299
- </div>
300
- <button onClick={() => { setBridgeStatus('idle'); setBridgeLog([]); }} style={{
301
- width: '100%', padding: '10px', borderRadius: 8, border: 'none', cursor: 'pointer',
302
- background: 'rgba(255,255,255,0.1)', color: '#aaa', fontWeight: 600, fontSize: 12,
303
- }}>Try Again</button>
304
- </div>
305
- )}
306
- </div>
307
-
308
205
  {/* === 2ND-DEGREE MAPPING PROGRESS === */}
309
206
  <MappingProgress
310
207
  degree1={mapping.degree1}
@@ -495,78 +392,3 @@ function StatCard({ label, value, color }) {
495
392
  </div>
496
393
  );
497
394
  }
498
-
499
- function SetupScrapeCard({ status, log, onStart, onRetry }) {
500
- return (
501
- <div style={{
502
- background: 'rgba(52,152,219,0.06)', borderRadius: 12, padding: 20, marginBottom: 24,
503
- border: '1px solid rgba(52,152,219,0.2)',
504
- }}>
505
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
506
- <h3 style={{ fontSize: 14, fontWeight: 700, margin: 0, color: '#3498DB' }}>Account Setup</h3>
507
- {status === 'done' && <span style={{ fontSize: 10, color: '#00ff88', fontWeight: 600 }}>Complete</span>}
508
- </div>
509
- <p style={{ fontSize: 11, color: '#888', margin: '0 0 12px' }}>
510
- Full scrape of all your LinkedIn connections with profile photos. Takes 3-5 minutes.
511
- </p>
512
-
513
- {status === 'idle' && (
514
- <button onClick={onStart} style={{
515
- width: '100%', padding: '12px', borderRadius: 8, border: 'none', cursor: 'pointer',
516
- background: 'linear-gradient(135deg, #3498DB, #9B59B6)',
517
- color: '#fff', fontWeight: 700, fontSize: 14,
518
- }}>
519
- Set Up Account — Full Scrape
520
- </button>
521
- )}
522
-
523
- {status === 'offline' && (
524
- <div>
525
- <div style={{ padding: '10px', borderRadius: 8, background: 'rgba(255,80,80,0.1)', border: '1px solid rgba(255,80,80,0.3)', color: '#ff5050', fontSize: 12, marginBottom: 8, textAlign: 'center' }}>
526
- Scraper server offline
527
- </div>
528
- <p style={{ fontSize: 10, color: '#888', textAlign: 'center', marginBottom: 8 }}>
529
- Open the <strong>Scan</strong> page to finish setting the scraper up
530
- </p>
531
- <button onClick={onRetry} style={{
532
- width: '100%', padding: '10px', borderRadius: 8, border: 'none', cursor: 'pointer',
533
- background: 'rgba(255,255,255,0.1)', color: '#aaa', fontWeight: 600, fontSize: 12,
534
- }}>Retry</button>
535
- </div>
536
- )}
537
-
538
- {status === 'running' && (
539
- <div style={{
540
- padding: '10px', borderRadius: 8, background: 'rgba(52,152,219,0.1)',
541
- border: '1px solid rgba(52,152,219,0.3)',
542
- }}>
543
- <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
544
- <div style={{ width: 8, height: 8, borderRadius: '50%', background: '#3498DB', animation: 'pulse 1s infinite' }} />
545
- <span style={{ fontSize: 11, fontWeight: 600, color: '#3498DB' }}>Scanning all connections...</span>
546
- </div>
547
- <div style={{ fontFamily: 'monospace', fontSize: 10, color: '#888', maxHeight: 120, overflow: 'auto' }}>
548
- {log.map((l, i) => <div key={i}>{l}</div>)}
549
- </div>
550
- </div>
551
- )}
552
-
553
- {status === 'done' && (
554
- <div style={{ padding: '10px', borderRadius: 8, background: 'rgba(0,255,136,0.1)', border: '1px solid rgba(0,255,136,0.3)', color: '#00ff88', fontSize: 12, fontWeight: 600, textAlign: 'center' }}>
555
- All connections scraped with images! Refresh the app to see your network.
556
- </div>
557
- )}
558
-
559
- {status === 'error' && (
560
- <div>
561
- <div style={{ padding: '10px', borderRadius: 8, background: 'rgba(255,80,80,0.1)', border: '1px solid rgba(255,80,80,0.3)', color: '#ff5050', fontSize: 12, textAlign: 'center', marginBottom: 8 }}>
562
- Something went wrong
563
- </div>
564
- <button onClick={onRetry} style={{
565
- width: '100%', padding: '10px', borderRadius: 8, border: 'none', cursor: 'pointer',
566
- background: 'rgba(255,255,255,0.1)', color: '#aaa', fontWeight: 600, fontSize: 12,
567
- }}>Try Again</button>
568
- </div>
569
- )}
570
- </div>
571
- );
572
- }
@@ -15,7 +15,7 @@ const BG = '#0a0a1a';
15
15
  const LINE = '1px solid rgba(255,255,255,0.1)';
16
16
 
17
17
  const ACTION_LABELS = {
18
- install: 'Installing the scraper',
18
+ install: 'Installing the scanner',
19
19
  login: 'Waiting for you to sign in',
20
20
  full: 'Scanning your whole network',
21
21
  refresh: 'Checking for new connections',
@@ -183,7 +183,7 @@ function SetupInner() {
183
183
 
184
184
  {notFound && (
185
185
  <Box tone="bad">
186
- <b>Can’t find the scraper files.</b><br />
186
+ <b>Can’t find the scanner files.</b><br />
187
187
  <span style={{ color: '#9aa' }}>
188
188
  Expected <code style={code}>scripts/scrape.py</code> next to the app. If you
189
189
  downloaded a zip, run the app from inside the project folder.
@@ -195,7 +195,7 @@ function SetupInner() {
195
195
  <Box tone="bad">
196
196
  <b>Google Chrome isn’t installed.</b><br />
197
197
  <span style={{ color: '#9aa' }}>
198
- The scraper drives your real Chrome. Install it from{' '}
198
+ The scanner drives your real Chrome. Install it from{' '}
199
199
  <a href="https://www.google.com/chrome/" target="_blank" rel="noreferrer"
200
200
  style={{ color: '#3498DB' }}>google.com/chrome</a>, then reload this page.
201
201
  </span>
@@ -206,7 +206,7 @@ function SetupInner() {
206
206
  <Step
207
207
  n={1}
208
208
  done={!!c.dependencies}
209
- title="Install what the scraper needs"
209
+ title="Install what the scanner needs"
210
210
  body={
211
211
  c.dependencies
212
212
  ? 'Installed.'
@@ -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' }).unref();
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
- /* a browser that will not open is not worth failing over */
95
+ instead();
91
96
  }
92
97
  }
93
98
 
@@ -110,7 +110,7 @@ stop_running_copy() {
110
110
  main() {
111
111
  printf '\n 6 Degrees — installer\n\n'
112
112
 
113
- [ "$(uname -s)" = "Darwin" ] || fail "This installer is for macOS. On Linux, run it from source (see the README). Windows isn't supported yet."
113
+ [ "$(uname -s)" = "Darwin" ] || fail "This installer is for macOS. On Linux, run: npx six-degrees (needs Node 22.13 or later). Windows isn't supported yet."
114
114
 
115
115
  local arch
116
116
  # A Terminal running under Rosetta reports x86_64 even on Apple Silicon, and
@@ -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
- return saveSessionNetwork({ degree1: connections, degree2: [], source: 'csv' });
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
- return { degree1: parsed.degree1 || [], degree2: parsed.degree2 || [], source: parsed.source || 'csv' };
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
 
@@ -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
- return 'npx six-degrees@latest';
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
  }
@@ -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 scraper.');
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 scraper files are missing from this install.';
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 scraper is not installed yet — open Scan to set it up.';
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
  }