@floomhq/signaldash 0.4.0 → 0.6.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 +64 -18
- package/package.json +8 -2
- package/skills/signaldash/SKILL.md +10 -0
package/bin/sd.mjs
CHANGED
|
@@ -8,6 +8,13 @@ import { homedir } from "node:os";
|
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
|
|
11
|
+
// Lazy-load presentation deps so `mcp` (stdio, machine-facing) stays clean/fast.
|
|
12
|
+
async function ui() {
|
|
13
|
+
const [{ default: chalk }, { default: prompts }, { default: ora }, { default: open }] =
|
|
14
|
+
await Promise.all([import("chalk"), import("prompts"), import("ora"), import("open")]);
|
|
15
|
+
return { chalk, prompts, ora, open };
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
const DEFAULT_BACKEND = process.env.SIGNALDASH_BACKEND || "https://signaldash-api.floom.dev";
|
|
12
19
|
|
|
13
20
|
function configPaths() {
|
|
@@ -77,8 +84,12 @@ export async function cmdConnect(provider, dependencies = {}) {
|
|
|
77
84
|
process.exitCode = 1;
|
|
78
85
|
return;
|
|
79
86
|
}
|
|
80
|
-
|
|
81
|
-
log("
|
|
87
|
+
const { chalk, ora, open } = await ui();
|
|
88
|
+
log("\n " + chalk.dim("Opening your browser. If it does not open, use this link:"));
|
|
89
|
+
log(" " + chalk.cyan(r.json.url) + "\n");
|
|
90
|
+
try { await open(r.json.url); } catch {}
|
|
91
|
+
const spin = ora({ text: `Waiting for ${provider} authentication...`, indent: 2 }).start();
|
|
92
|
+
dependencies._spin = spin;
|
|
82
93
|
|
|
83
94
|
const timeoutMs = positiveMilliseconds("SIGNALDASH_CONNECT_TIMEOUT_MS", 5 * 60 * 1000);
|
|
84
95
|
const pollMs = positiveMilliseconds("SIGNALDASH_CONNECT_POLL_MS", 2000);
|
|
@@ -86,10 +97,12 @@ export async function cmdConnect(provider, dependencies = {}) {
|
|
|
86
97
|
while (now() < deadline) {
|
|
87
98
|
const status = await request(`/connect/${provider}/status`, undefined, { method: "GET" });
|
|
88
99
|
if (status.status === 200 && status.json.connected) {
|
|
89
|
-
|
|
100
|
+
if (dependencies._spin) dependencies._spin.succeed(`Connected ${provider}${status.json.name ? `: ${status.json.name}` : ""}`);
|
|
101
|
+
else log(`Connected ${provider}`);
|
|
90
102
|
return;
|
|
91
103
|
}
|
|
92
104
|
if (status.status !== 202) {
|
|
105
|
+
if (dependencies._spin) dependencies._spin.fail("connect failed");
|
|
93
106
|
error("connect status failed:", status.json.error || status.status);
|
|
94
107
|
process.exitCode = 1;
|
|
95
108
|
return;
|
|
@@ -97,7 +110,8 @@ export async function cmdConnect(provider, dependencies = {}) {
|
|
|
97
110
|
await sleep(Math.min(pollMs, Math.max(0, deadline - now())));
|
|
98
111
|
}
|
|
99
112
|
|
|
100
|
-
|
|
113
|
+
if (dependencies._spin) dependencies._spin.stop();
|
|
114
|
+
error(`Not connected yet. The link above stays valid; open it, finish authenticating, then run:\n npx @floomhq/signaldash connect ${provider}\nManual fallback:\n signaldash connect ${provider} claim <account_id>`);
|
|
101
115
|
process.exitCode = 1;
|
|
102
116
|
}
|
|
103
117
|
export async function cmdClaim(provider, accountId, dependencies = {}) {
|
|
@@ -161,23 +175,51 @@ export async function cmdSkill(dependencies = {}) {
|
|
|
161
175
|
export async function cmdSetup(code, dependencies = {}) {
|
|
162
176
|
const log = dependencies.log || console.log;
|
|
163
177
|
const { execSync } = await import("node:child_process");
|
|
164
|
-
|
|
165
|
-
|
|
178
|
+
const { chalk, prompts } = await ui();
|
|
179
|
+
|
|
180
|
+
log("");
|
|
181
|
+
log(" " + chalk.bold.cyan("SignalDash") + chalk.dim(" secure LinkedIn + WhatsApp access for your agent"));
|
|
182
|
+
log("");
|
|
183
|
+
|
|
184
|
+
await cmdLogin(code, undefined, { ...dependencies, log: () => {} });
|
|
166
185
|
if (process.exitCode === 1) return;
|
|
167
|
-
|
|
186
|
+
log(" " + chalk.green("+") + " logged in");
|
|
187
|
+
|
|
188
|
+
await cmdSkill({ ...dependencies, log: () => {} });
|
|
189
|
+
log(" " + chalk.green("+") + " agent skill installed");
|
|
190
|
+
|
|
168
191
|
try {
|
|
169
192
|
execSync("claude mcp add signaldash -- npx -y @floomhq/signaldash mcp", { stdio: "ignore" });
|
|
170
|
-
log("
|
|
193
|
+
log(" " + chalk.green("+") + " MCP registered with Claude Code");
|
|
171
194
|
} catch {
|
|
172
|
-
log("
|
|
173
|
-
log('
|
|
195
|
+
log(" " + chalk.yellow("!") + " Claude Code not found. For Cursor, add to .cursor/mcp.json:");
|
|
196
|
+
log(" " + chalk.dim('{"mcpServers":{"signaldash":{"command":"npx","args":["-y","@floomhq/signaldash","mcp"]}}}'));
|
|
174
197
|
}
|
|
175
|
-
|
|
176
|
-
|
|
198
|
+
log("");
|
|
199
|
+
|
|
200
|
+
const choice = dependencies.choice ?? (await prompts({
|
|
201
|
+
type: "select",
|
|
202
|
+
name: "value",
|
|
203
|
+
message: "Which accounts do you want to connect?",
|
|
204
|
+
choices: [
|
|
205
|
+
{ title: "WhatsApp only", value: ["whatsapp"] },
|
|
206
|
+
{ title: "LinkedIn only", value: ["linkedin"] },
|
|
207
|
+
{ title: "Both", value: ["linkedin", "whatsapp"] },
|
|
208
|
+
{ title: "Skip for now", value: [] },
|
|
209
|
+
],
|
|
210
|
+
initial: 0,
|
|
211
|
+
})).value;
|
|
212
|
+
|
|
213
|
+
if (!choice || choice.length === 0) {
|
|
214
|
+
log("\n " + chalk.dim("Nothing connected. Run `signaldash connect whatsapp` or `connect linkedin` anytime."));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
for (const provider of choice) {
|
|
218
|
+
log("");
|
|
177
219
|
await cmdConnect(provider, dependencies);
|
|
178
|
-
process.exitCode = 0;
|
|
220
|
+
process.exitCode = 0;
|
|
179
221
|
}
|
|
180
|
-
log("\
|
|
222
|
+
log("\n " + chalk.dim("Try: ") + chalk.white('"list my recent whatsapp chats and draft replies"') + "\n");
|
|
181
223
|
}
|
|
182
224
|
|
|
183
225
|
export async function main(argv = process.argv.slice(2), dependencies = {}) {
|
|
@@ -193,9 +235,13 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
193
235
|
else log(`SignalDash — secure LinkedIn + WhatsApp access for your agent.\n\n signaldash login <invite-code> [--backend URL]\n signaldash connect linkedin|whatsapp\n signaldash connect linkedin|whatsapp claim <account_id>\n signaldash mcp\n signaldash skill install the agent skill\n\nThe agent reaches channels only through SignalDash. No keys on your machine.`);
|
|
194
236
|
}
|
|
195
237
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
238
|
+
function isDirectRun() {
|
|
239
|
+
try {
|
|
240
|
+
return Boolean(process.argv[1]) && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
241
|
+
} catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (isDirectRun()) {
|
|
200
246
|
await main();
|
|
201
247
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floomhq/signaldash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Secure LinkedIn and WhatsApp MCP access for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,5 +27,11 @@
|
|
|
27
27
|
"unipile",
|
|
28
28
|
"ai-agent"
|
|
29
29
|
],
|
|
30
|
-
"license": "MIT"
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"chalk": "5.3.0",
|
|
33
|
+
"prompts": "2.4.2",
|
|
34
|
+
"ora": "8.0.1",
|
|
35
|
+
"open": "10.1.0"
|
|
36
|
+
}
|
|
31
37
|
}
|
|
@@ -34,6 +34,16 @@ Cursor: add to `.cursor/mcp.json` →
|
|
|
34
34
|
| `wa_read_messages` | read a WhatsApp thread (`chat_id`) |
|
|
35
35
|
| `wa_send_message` | send a WhatsApp message (`chat_id`, `text`) |
|
|
36
36
|
|
|
37
|
+
## Enforced by the server (you cannot bypass these)
|
|
38
|
+
|
|
39
|
+
- **Read before send.** Sending to a chat you have not read recently returns
|
|
40
|
+
`428 read_before_send_required`. Call `*_read_messages` on that chat first.
|
|
41
|
+
- **No double-send.** An identical message to the same chat returns
|
|
42
|
+
`409 duplicate_send`.
|
|
43
|
+
- **Daily send cap + pacing.** Exceeding it returns `429 rate_limit_exceeded`.
|
|
44
|
+
|
|
45
|
+
These are server-side guards, not suggestions. Do not try to work around them.
|
|
46
|
+
|
|
37
47
|
## The rules you MUST follow
|
|
38
48
|
|
|
39
49
|
These protect the user's accounts from being restricted or banned, and protect
|