@floomhq/signaldash 0.6.0 → 0.7.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/README.md +2 -0
- package/bin/sd.mjs +96 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ URL and scan the live QR code. The CLI prints `Connected linkedin` or
|
|
|
25
25
|
The invite code is single-use. Login stores a SignalDash user token in
|
|
26
26
|
`~/.signaldash/config.json`; the Unipile access key remains on the SignalDash
|
|
27
27
|
server. Set `SIGNALDASH_HOME` to keep the local config in another directory.
|
|
28
|
+
Sessions expire after 90 days by default. Run `npx -y @floomhq/signaldash logout`
|
|
29
|
+
to revoke the current session and remove its local token.
|
|
28
30
|
|
|
29
31
|
If automatic account detection times out, the CLI prints the manual claim
|
|
30
32
|
command for the provider:
|
package/bin/sd.mjs
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
// bearer token. Never holds a Unipile key, never calls Unipile directly. The MCP
|
|
4
4
|
// tools an agent uses all proxy through the backend, so agents access channels
|
|
5
5
|
// THROUGH SignalDash, not around it.
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
chmodSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
mkdirSync,
|
|
12
|
+
} from "node:fs";
|
|
7
13
|
import { homedir } from "node:os";
|
|
8
14
|
import { createInterface } from "node:readline";
|
|
9
15
|
import { fileURLToPath } from "node:url";
|
|
@@ -30,8 +36,10 @@ function loadCfg() {
|
|
|
30
36
|
}
|
|
31
37
|
function saveCfg(c) {
|
|
32
38
|
const config = configPaths();
|
|
33
|
-
mkdirSync(config.directory, { recursive: true });
|
|
39
|
+
mkdirSync(config.directory, { recursive: true, mode: 0o700 });
|
|
40
|
+
chmodSync(config.directory, 0o700);
|
|
34
41
|
writeFileSync(config.file, JSON.stringify(c, null, 2), { mode: 0o600 });
|
|
42
|
+
chmodSync(config.file, 0o600);
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
async function api(path, body, { auth = true, method = "POST" } = {}) {
|
|
@@ -67,6 +75,30 @@ export async function cmdLogin(code, backend, dependencies = {}) {
|
|
|
67
75
|
log("Logged in to SignalDash. Token stored (no channel keys on your side).");
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
export async function cmdLogout(dependencies = {}) {
|
|
79
|
+
const request = dependencies.request || api;
|
|
80
|
+
const log = dependencies.log || console.log;
|
|
81
|
+
const error = dependencies.error || console.error;
|
|
82
|
+
const cfg = loadCfg();
|
|
83
|
+
if (!cfg.token) {
|
|
84
|
+
log("Already logged out.");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const response = await request("/logout", {});
|
|
88
|
+
if (response.status !== 200 && response.status !== 401) {
|
|
89
|
+
error("logout failed:", response.json.error || response.status);
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
delete cfg.token;
|
|
94
|
+
saveCfg(cfg);
|
|
95
|
+
log(
|
|
96
|
+
response.status === 200
|
|
97
|
+
? "Logged out of SignalDash. The session was revoked."
|
|
98
|
+
: "Local session cleared. The server session was already invalid.",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
70
102
|
export async function cmdConnect(provider, dependencies = {}) {
|
|
71
103
|
const request = dependencies.request || api;
|
|
72
104
|
const sleep = dependencies.wait || wait;
|
|
@@ -80,7 +112,9 @@ export async function cmdConnect(provider, dependencies = {}) {
|
|
|
80
112
|
}
|
|
81
113
|
const r = await request(`/connect/${provider}`, {});
|
|
82
114
|
if (r.status >= 300) {
|
|
83
|
-
error(
|
|
115
|
+
error(r.json.error === "login required"
|
|
116
|
+
? "Not logged in. Run: signaldash login <invite-code>"
|
|
117
|
+
: "connect failed: " + (r.json.error || r.status));
|
|
84
118
|
process.exitCode = 1;
|
|
85
119
|
return;
|
|
86
120
|
}
|
|
@@ -222,17 +256,74 @@ export async function cmdSetup(code, dependencies = {}) {
|
|
|
222
256
|
log("\n " + chalk.dim("Try: ") + chalk.white('"list my recent whatsapp chats and draft replies"') + "\n");
|
|
223
257
|
}
|
|
224
258
|
|
|
259
|
+
|
|
260
|
+
export async function cmdStatus(dependencies = {}) {
|
|
261
|
+
const log = dependencies.log || console.log;
|
|
262
|
+
const { chalk } = await ui();
|
|
263
|
+
const cfg = loadCfg();
|
|
264
|
+
if (!cfg.token) { log(" Not logged in. Run: " + chalk.cyan("signaldash login <invite-code>")); return; }
|
|
265
|
+
const request = dependencies.request || api;
|
|
266
|
+
log("");
|
|
267
|
+
log(" " + chalk.bold("SignalDash") + chalk.dim(" " + (cfg.backend || DEFAULT_BACKEND)));
|
|
268
|
+
for (const provider of ["linkedin", "whatsapp"]) {
|
|
269
|
+
const r = await request(`/connect/${provider}/status`, undefined, { method: "GET" });
|
|
270
|
+
const ok = r.status === 200 && r.json.connected;
|
|
271
|
+
log(" " + (ok ? chalk.green("+") : chalk.dim("-")) + " " + provider.padEnd(9) +
|
|
272
|
+
(ok ? chalk.dim(r.json.name || "connected") : chalk.dim("not connected")));
|
|
273
|
+
}
|
|
274
|
+
log("");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export async function cmdConnections(outPath, dependencies = {}) {
|
|
278
|
+
const log = dependencies.log || console.log;
|
|
279
|
+
const error = dependencies.error || console.error;
|
|
280
|
+
const request = dependencies.request || api;
|
|
281
|
+
const { chalk, ora } = await ui();
|
|
282
|
+
const spin = ora({ text: "Fetching your LinkedIn connections...", indent: 2 }).start();
|
|
283
|
+
const r = await request("/li/connections", {});
|
|
284
|
+
if (r.status >= 300) { spin.fail(r.json.error || "failed"); process.exitCode = 1; return; }
|
|
285
|
+
const rows = r.json.connections || [];
|
|
286
|
+
spin.succeed(`${rows.length} connections`);
|
|
287
|
+
const { writeFileSync: wf } = await import("node:fs");
|
|
288
|
+
const esc = v => `"${String(v ?? "").replace(/"/g, '""')}"`;
|
|
289
|
+
const csv = ["name,headline,public_id,profile_url,connected_at",
|
|
290
|
+
...rows.map(c => [c.name, c.headline, c.public_id, c.profile_url, c.connected_at].map(esc).join(","))].join("\n");
|
|
291
|
+
const file = outPath || "linkedin-connections.csv";
|
|
292
|
+
wf(file, csv);
|
|
293
|
+
log(" " + chalk.green("+") + " saved " + chalk.cyan(file));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
function printHelp(log = console.log) {
|
|
298
|
+
log(`SignalDash — secure LinkedIn + WhatsApp access for your agent.
|
|
299
|
+
|
|
300
|
+
signaldash <invite-code> set up everything in one go
|
|
301
|
+
signaldash status show what is connected
|
|
302
|
+
signaldash connect linkedin|whatsapp connect a channel
|
|
303
|
+
signaldash connections [file.csv] export your LinkedIn connections
|
|
304
|
+
signaldash skill install the agent skill
|
|
305
|
+
signaldash mcp run the MCP server (used by your agent)
|
|
306
|
+
signaldash logout revoke this device
|
|
307
|
+
|
|
308
|
+
The agent reaches your channels only through SignalDash. No keys on your machine.`);
|
|
309
|
+
}
|
|
310
|
+
|
|
225
311
|
export async function main(argv = process.argv.slice(2), dependencies = {}) {
|
|
226
312
|
const [cmd, a, b, c] = argv;
|
|
227
313
|
const log = dependencies.log || console.log;
|
|
228
314
|
if (cmd === "setup") await cmdSetup(a, dependencies);
|
|
229
|
-
else if (cmd && /^[0-9a-f]{8,}$/i.test(cmd) && !["login","connect","mcp","skill"].includes(cmd)) await cmdSetup(cmd, dependencies);
|
|
315
|
+
else if (cmd && /^[0-9a-f]{8,}$/i.test(cmd) && !["login","logout","connect","mcp","skill"].includes(cmd)) await cmdSetup(cmd, dependencies);
|
|
230
316
|
else if (cmd === "login") await cmdLogin(a, b === "--backend" ? c : undefined, dependencies);
|
|
317
|
+
else if (cmd === "logout") await cmdLogout(dependencies);
|
|
231
318
|
else if (cmd === "connect" && b === "claim") await cmdClaim(a, c, dependencies);
|
|
232
319
|
else if (cmd === "connect") await cmdConnect(a, dependencies);
|
|
233
320
|
else if (cmd === "mcp") await runMcp(dependencies);
|
|
321
|
+
else if (cmd === "status") await cmdStatus(dependencies);
|
|
322
|
+
else if (cmd === "connections" || (cmd === "export" && a === "connections")) await cmdConnections(cmd === "export" ? b : a, dependencies);
|
|
323
|
+
else if (cmd === "--version" || cmd === "-v") log("0.7.0");
|
|
324
|
+
else if (cmd && cmd !== "help" && !/^[0-9a-f]{8,}$/i.test(cmd)) { (dependencies.error || console.error)(`unknown command: ${cmd}`); printHelp(log); process.exitCode = 1; }
|
|
234
325
|
else if (cmd === "skill") await cmdSkill(dependencies);
|
|
235
|
-
else log
|
|
326
|
+
else printHelp(log);
|
|
236
327
|
}
|
|
237
328
|
|
|
238
329
|
function isDirectRun() {
|