@floomhq/signaldash 0.8.0 → 0.9.0

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 (2) hide show
  1. package/bin/sd.mjs +23 -33
  2. package/package.json +1 -1
package/bin/sd.mjs CHANGED
@@ -278,46 +278,36 @@ export async function cmdConnections(outPath, dependencies = {}) {
278
278
  const log = dependencies.log || console.log;
279
279
  const request = dependencies.request || api;
280
280
  const { chalk, ora } = await ui();
281
- const { writeFileSync: wf, readFileSync: rf, existsSync: ex } = await import("node:fs");
281
+ const { writeFileSync: wf } = await import("node:fs");
282
282
  const file = outPath || "linkedin-connections.csv";
283
283
 
284
- // Resume support: keep the cursor next to the CSV so a large network is
285
- // fetched over several paced passes instead of one burst (LinkedIn detection
286
- // is behavioural; bursts are what get accounts flagged).
287
- const stateFile = file + ".state.json";
288
- let cursor = null, rows = [];
289
- if (ex(stateFile)) {
290
- try {
291
- const prev = JSON.parse(rf(stateFile, "utf8"));
292
- cursor = prev.cursor || null; rows = prev.rows || [];
293
- if (cursor) log(" " + chalk.dim(`resuming from previous run (${rows.length} already fetched)`));
294
- } catch {}
284
+ // Background drip-sync: one page (~100 connections) per ~90s, server-side.
285
+ // A burst export of a large network looks robotic to LinkedIn; this paces it
286
+ // like a human and survives restarts, so the user just waits for the file.
287
+ const start = await request("/li/connections/sync", {});
288
+ if (start.status >= 300) {
289
+ (dependencies.error || console.error)(start.json.error || "could not start sync");
290
+ process.exitCode = 1; return;
295
291
  }
296
-
297
- const spin = ora({ text: "Fetching connections (paced, this is deliberate)...", indent: 2 }).start();
292
+ const spin = ora({ text: "Syncing connections (paced to keep your account safe)...", indent: 2 }).start();
293
+ let done = false;
298
294
  for (;;) {
299
- const r = await request("/li/connections", { cursor, max_pages: 10 });
300
- if (r.status >= 300) {
301
- spin.fail(r.json.error || "failed");
302
- if (rows.length) { wf(stateFile, JSON.stringify({ cursor, rows })); log(" " + chalk.dim("progress saved; run again to resume")); }
303
- process.exitCode = 1; return;
304
- }
305
- rows = rows.concat(r.json.connections || []);
306
- cursor = r.json.next_cursor;
307
- spin.text = `Fetched ${rows.length} connections...`;
308
- if (!cursor) break;
309
- wf(stateFile, JSON.stringify({ cursor, rows }));
295
+ const st = await request("/li/connections/sync", undefined, { method: "GET" });
296
+ const s2 = st.json || {};
297
+ spin.text = `Synced ${s2.fetched ?? 0} connections (${s2.pages ?? 0} pages)...`;
298
+ if (s2.status === "complete") { done = true; break; }
299
+ if (s2.status === "failed" || s2.status === "paused") { spin.warn(`sync ${s2.status}: ${s2.error || ""}`); break; }
300
+ await new Promise(r => setTimeout(r, 15000));
310
301
  }
311
- spin.succeed(`${rows.length} connections`);
312
-
302
+ const dl = await request("/li/connections/download", undefined, { method: "GET" });
303
+ const rows = (dl.json && dl.json.connections) || [];
304
+ if (!rows.length) { spin.fail("nothing fetched yet, run again later to resume"); process.exitCode = 1; return; }
305
+ if (done) spin.succeed(`${rows.length} connections`);
313
306
  const esc = v => `"${String(v ?? "").replace(/"/g, '""')}"`;
314
- const csv = ["name,headline,public_id,profile_url,connected_at",
315
- ...rows.map(c => [c.name, c.headline, c.public_id, c.profile_url, c.connected_at].map(esc).join(","))].join("\n");
316
- wf(file, csv);
317
- try { const { unlinkSync } = await import("node:fs"); if (ex(stateFile)) unlinkSync(stateFile); } catch {}
307
+ wf(file, ["name,headline,public_id,profile_url,connected_at",
308
+ ...rows.map(c => [c.name, c.headline, c.public_id, c.profile_url, c.connected_at].map(esc).join(","))].join("\n"));
318
309
  log(" " + chalk.green("+") + " saved " + chalk.cyan(file));
319
- log(" " + chalk.dim("Tip: LinkedIn's own export (Settings > Get a copy of your data) is zero-risk"));
320
- log(" " + chalk.dim("and includes emails. Both work; that one never touches the API."));
310
+ if (!done) log(" " + chalk.dim("sync still running; run this again later to get the rest"));
321
311
  }
322
312
 
323
313
  export async function main(argv = process.argv.slice(2), dependencies = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/signaldash",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Secure LinkedIn and WhatsApp MCP access for AI agents",
5
5
  "type": "module",
6
6
  "bin": {