@floomhq/signaldash 0.8.0 → 0.10.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.
- package/bin/sd.mjs +28 -34
- package/package.json +1 -1
package/bin/sd.mjs
CHANGED
|
@@ -164,10 +164,14 @@ const TOOLS = [
|
|
|
164
164
|
{ name: "wa_list_chats", ch: "wa", action: "list_chats", description: "List your WhatsApp chats." },
|
|
165
165
|
{ name: "wa_read_messages", ch: "wa", action: "read", description: "Read messages in a WhatsApp chat. args: chat_id" },
|
|
166
166
|
{ name: "wa_send_message", ch: "wa", action: "send", description: "Send a WhatsApp message (rate-safe). args: chat_id, text" },
|
|
167
|
+
{ name: "li_my_posts", path: "/li/posts", description: "List the user's own LinkedIn posts with engagement counts (reactions, comments, impressions). args: limit" },
|
|
168
|
+
{ name: "li_post_reactions", path: "/li/post_reactions", description: "Who reacted to a post — name + headline. These are warm inbound signals. args: post_id, limit" },
|
|
169
|
+
{ name: "li_post_comments", path: "/li/post_comments", description: "Comments on a post, with author. args: post_id, limit" },
|
|
170
|
+
{ name: "li_draft_post", path: "/li/create_post", description: "Draft a LinkedIn post. Returns the draft WITHOUT publishing. Publishing requires the human to approve and re-send with publish:true. args: text" },
|
|
167
171
|
];
|
|
168
172
|
function mcpTool(name) {
|
|
169
173
|
return { name, description: TOOLS.find(t => t.name === name).description,
|
|
170
|
-
inputSchema: { type: "object", properties: { chat_id: { type: "string" }, text: { type: "string" }, limit: { type: "number" } } } };
|
|
174
|
+
inputSchema: { type: "object", properties: { chat_id: { type: "string" }, text: { type: "string" }, limit: { type: "number" }, post_id: { type: "string" }, member_id: { type: "string" }, publish: { type: "boolean" } } } };
|
|
171
175
|
}
|
|
172
176
|
export async function runMcp(dependencies = {}) {
|
|
173
177
|
const input = dependencies.input || process.stdin;
|
|
@@ -278,46 +282,36 @@ export async function cmdConnections(outPath, dependencies = {}) {
|
|
|
278
282
|
const log = dependencies.log || console.log;
|
|
279
283
|
const request = dependencies.request || api;
|
|
280
284
|
const { chalk, ora } = await ui();
|
|
281
|
-
const { writeFileSync: wf
|
|
285
|
+
const { writeFileSync: wf } = await import("node:fs");
|
|
282
286
|
const file = outPath || "linkedin-connections.csv";
|
|
283
287
|
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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 {}
|
|
288
|
+
// Background drip-sync: one page (~100 connections) per ~90s, server-side.
|
|
289
|
+
// A burst export of a large network looks robotic to LinkedIn; this paces it
|
|
290
|
+
// like a human and survives restarts, so the user just waits for the file.
|
|
291
|
+
const start = await request("/li/connections/sync", {});
|
|
292
|
+
if (start.status >= 300) {
|
|
293
|
+
(dependencies.error || console.error)(start.json.error || "could not start sync");
|
|
294
|
+
process.exitCode = 1; return;
|
|
295
295
|
}
|
|
296
|
-
|
|
297
|
-
|
|
296
|
+
const spin = ora({ text: "Syncing connections (paced to keep your account safe)...", indent: 2 }).start();
|
|
297
|
+
let done = false;
|
|
298
298
|
for (;;) {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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 }));
|
|
299
|
+
const st = await request("/li/connections/sync", undefined, { method: "GET" });
|
|
300
|
+
const s2 = st.json || {};
|
|
301
|
+
spin.text = `Synced ${s2.fetched ?? 0} connections (${s2.pages ?? 0} pages)...`;
|
|
302
|
+
if (s2.status === "complete") { done = true; break; }
|
|
303
|
+
if (s2.status === "failed" || s2.status === "paused") { spin.warn(`sync ${s2.status}: ${s2.error || ""}`); break; }
|
|
304
|
+
await new Promise(r => setTimeout(r, 15000));
|
|
310
305
|
}
|
|
311
|
-
|
|
312
|
-
|
|
306
|
+
const dl = await request("/li/connections/download", undefined, { method: "GET" });
|
|
307
|
+
const rows = (dl.json && dl.json.connections) || [];
|
|
308
|
+
if (!rows.length) { spin.fail("nothing fetched yet, run again later to resume"); process.exitCode = 1; return; }
|
|
309
|
+
if (done) spin.succeed(`${rows.length} connections`);
|
|
313
310
|
const esc = v => `"${String(v ?? "").replace(/"/g, '""')}"`;
|
|
314
|
-
|
|
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 {}
|
|
311
|
+
wf(file, ["name,headline,public_id,profile_url,connected_at",
|
|
312
|
+
...rows.map(c => [c.name, c.headline, c.public_id, c.profile_url, c.connected_at].map(esc).join(","))].join("\n"));
|
|
318
313
|
log(" " + chalk.green("+") + " saved " + chalk.cyan(file));
|
|
319
|
-
log(" " + chalk.dim("
|
|
320
|
-
log(" " + chalk.dim("and includes emails. Both work; that one never touches the API."));
|
|
314
|
+
if (!done) log(" " + chalk.dim("sync still running; run this again later to get the rest"));
|
|
321
315
|
}
|
|
322
316
|
|
|
323
317
|
export async function main(argv = process.argv.slice(2), dependencies = {}) {
|