@floomhq/signaldash 0.2.0 → 0.4.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 +4 -0
- package/bin/sd.mjs +45 -3
- package/package.json +1 -1
- package/skills/signaldash/SKILL.md +68 -0
package/README.md
CHANGED
|
@@ -102,6 +102,10 @@ receive the Unipile access key.
|
|
|
102
102
|
SignalDash enforces a daily send cap. The safe-usage skill adds the human
|
|
103
103
|
workflow around that runtime control:
|
|
104
104
|
|
|
105
|
+
- Every successful send includes `rate_limit.limit`, `used`, `remaining`, and
|
|
106
|
+
`resets_at`, plus matching `X-RateLimit-*` headers. When the per-user cap is
|
|
107
|
+
exhausted, SignalDash returns HTTP 429 with `code: "rate_limit_exceeded"` and
|
|
108
|
+
`Retry-After`. Usage is persisted server-side and resets at midnight UTC.
|
|
105
109
|
- Read the exact thread before every send.
|
|
106
110
|
- Never infer a recipient from a partial name.
|
|
107
111
|
- Never send a duplicate or retry an ambiguous timeout without re-reading.
|
package/bin/sd.mjs
CHANGED
|
@@ -97,7 +97,7 @@ export async function cmdConnect(provider, dependencies = {}) {
|
|
|
97
97
|
await sleep(Math.min(pollMs, Math.max(0, deadline - now())));
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
error(`
|
|
100
|
+
error(`Not connected yet. The link above stays valid — open it, finish authenticating, then run:\n npx @floomhq/signaldash connect ${provider}`);
|
|
101
101
|
process.exitCode = 1;
|
|
102
102
|
}
|
|
103
103
|
export async function cmdClaim(provider, accountId, dependencies = {}) {
|
|
@@ -141,14 +141,56 @@ export async function runMcp(dependencies = {}) {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
|
|
145
|
+
export async function cmdSkill(dependencies = {}) {
|
|
146
|
+
const log = dependencies.log || console.log;
|
|
147
|
+
const { mkdirSync: mk, writeFileSync: wf, readFileSync: rf, existsSync: ex } = await import("node:fs");
|
|
148
|
+
const { dirname, join } = await import("node:path");
|
|
149
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
150
|
+
const src = join(here, "..", "skills", "signaldash", "SKILL.md");
|
|
151
|
+
if (!ex(src)) { log("skill file not found in package"); process.exitCode = 1; return; }
|
|
152
|
+
const dest = join(homedir(), ".claude", "skills", "signaldash");
|
|
153
|
+
mk(dest, { recursive: true });
|
|
154
|
+
wf(join(dest, "SKILL.md"), rf(src, "utf8"));
|
|
155
|
+
log(`Installed the SignalDash skill to ${dest}/SKILL.md`);
|
|
156
|
+
log("Your agent now knows how to use LinkedIn + WhatsApp safely through SignalDash.");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
// One-command onboarding: login + skill + MCP registration + connect both channels.
|
|
161
|
+
export async function cmdSetup(code, dependencies = {}) {
|
|
162
|
+
const log = dependencies.log || console.log;
|
|
163
|
+
const { execSync } = await import("node:child_process");
|
|
164
|
+
log("SignalDash setup\n");
|
|
165
|
+
await cmdLogin(code, undefined, dependencies);
|
|
166
|
+
if (process.exitCode === 1) return;
|
|
167
|
+
await cmdSkill(dependencies);
|
|
168
|
+
try {
|
|
169
|
+
execSync("claude mcp add signaldash -- npx -y @floomhq/signaldash mcp", { stdio: "ignore" });
|
|
170
|
+
log("Registered the MCP with Claude Code.");
|
|
171
|
+
} catch {
|
|
172
|
+
log("To finish in Cursor, add to .cursor/mcp.json:");
|
|
173
|
+
log(' {"mcpServers":{"signaldash":{"command":"npx","args":["-y","@floomhq/signaldash","mcp"]}}}');
|
|
174
|
+
}
|
|
175
|
+
for (const provider of ["linkedin", "whatsapp"]) {
|
|
176
|
+
log(`\n--- connect ${provider} ---`);
|
|
177
|
+
await cmdConnect(provider, dependencies);
|
|
178
|
+
process.exitCode = 0; // a skipped channel should not fail the whole setup
|
|
179
|
+
}
|
|
180
|
+
log("\nDone. Ask your agent: \"list my recent LinkedIn chats and draft replies.\"");
|
|
181
|
+
}
|
|
182
|
+
|
|
144
183
|
export async function main(argv = process.argv.slice(2), dependencies = {}) {
|
|
145
184
|
const [cmd, a, b, c] = argv;
|
|
146
185
|
const log = dependencies.log || console.log;
|
|
147
|
-
if (cmd === "
|
|
186
|
+
if (cmd === "setup") await cmdSetup(a, dependencies);
|
|
187
|
+
else if (cmd && /^[0-9a-f]{8,}$/i.test(cmd) && !["login","connect","mcp","skill"].includes(cmd)) await cmdSetup(cmd, dependencies);
|
|
188
|
+
else if (cmd === "login") await cmdLogin(a, b === "--backend" ? c : undefined, dependencies);
|
|
148
189
|
else if (cmd === "connect" && b === "claim") await cmdClaim(a, c, dependencies);
|
|
149
190
|
else if (cmd === "connect") await cmdConnect(a, dependencies);
|
|
150
191
|
else if (cmd === "mcp") await runMcp(dependencies);
|
|
151
|
-
else
|
|
192
|
+
else if (cmd === "skill") await cmdSkill(dependencies);
|
|
193
|
+
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.`);
|
|
152
194
|
}
|
|
153
195
|
|
|
154
196
|
if (
|
package/package.json
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: signaldash
|
|
3
|
+
description: Give this agent secure access to the user's LinkedIn and WhatsApp — list chats, read threads, and send messages — through SignalDash. Use whenever the user asks to check, read, triage, reply to, or send LinkedIn/WhatsApp messages. Also covers first-time setup.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SignalDash — LinkedIn + WhatsApp for this agent
|
|
7
|
+
|
|
8
|
+
SignalDash is the gate between you and the user's real messaging accounts. The
|
|
9
|
+
user's channel credentials live on the SignalDash server, never on their machine
|
|
10
|
+
and never in your context. You act through SignalDash tools only.
|
|
11
|
+
|
|
12
|
+
## Setup (only if the tools are not available yet)
|
|
13
|
+
|
|
14
|
+
The user needs an invite code from Federico, then:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @floomhq/signaldash login <invite-code>
|
|
18
|
+
npx @floomhq/signaldash connect linkedin # opens a link; user authenticates; auto-detects
|
|
19
|
+
npx @floomhq/signaldash connect whatsapp # same, QR scan from WhatsApp > Linked Devices
|
|
20
|
+
claude mcp add signaldash -- npx -y @floomhq/signaldash mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Cursor: add to `.cursor/mcp.json` →
|
|
24
|
+
`{"mcpServers":{"signaldash":{"command":"npx","args":["-y","@floomhq/signaldash","mcp"]}}}`
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
| Tool | What it does |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `li_list_chats` | list the user's LinkedIn chats |
|
|
31
|
+
| `li_read_messages` | read a LinkedIn thread (`chat_id`) |
|
|
32
|
+
| `li_send_message` | send a LinkedIn message (`chat_id`, `text`) |
|
|
33
|
+
| `wa_list_chats` | list the user's WhatsApp chats |
|
|
34
|
+
| `wa_read_messages` | read a WhatsApp thread (`chat_id`) |
|
|
35
|
+
| `wa_send_message` | send a WhatsApp message (`chat_id`, `text`) |
|
|
36
|
+
|
|
37
|
+
## The rules you MUST follow
|
|
38
|
+
|
|
39
|
+
These protect the user's accounts from being restricted or banned, and protect
|
|
40
|
+
them from you sending something wrong. They are not optional.
|
|
41
|
+
|
|
42
|
+
1. **Never send without the user's explicit approval of the exact recipient and
|
|
43
|
+
exact text.** Draft it, show it, wait. Discussion is not approval.
|
|
44
|
+
2. **Read the thread before you send.** Always `*_read_messages` on that exact
|
|
45
|
+
chat first: check who it is, what they last said, and whether this was
|
|
46
|
+
already sent. Never send blind. Never double-send.
|
|
47
|
+
3. **Never bulk.** No mass messaging, no "message everyone who…", no bulk
|
|
48
|
+
profile fetching. LinkedIn restricts accounts for exactly this. Hard ceiling
|
|
49
|
+
is roughly 15-20 LinkedIn sends per day; the server enforces a daily cap and
|
|
50
|
+
spacing, do not try to work around it or parallelize sends.
|
|
51
|
+
4. **One message at a time, human pace.** Let the server's pacing do its work.
|
|
52
|
+
5. **Stop on any warning.** A checkpoint, restriction, "unusual activity"
|
|
53
|
+
prompt, 403 or 429 means halt all activity on that account and tell the user.
|
|
54
|
+
Do not retry.
|
|
55
|
+
6. **Personalize.** Read the thread and write a specific reply. Templated
|
|
56
|
+
identical messages get reported as spam.
|
|
57
|
+
|
|
58
|
+
## Good vs bad requests
|
|
59
|
+
|
|
60
|
+
Good: "list my unread LinkedIn chats and draft replies for my approval" ·
|
|
61
|
+
"read the thread with Mircea and suggest an answer" · "what came in on WhatsApp
|
|
62
|
+
from people I've spoken to before?"
|
|
63
|
+
|
|
64
|
+
Bad (refuse or push back): "connection-request everyone who liked my post" ·
|
|
65
|
+
"message all my connections about X" · "pull every profile in my network".
|
|
66
|
+
|
|
67
|
+
Rule of thumb: if a human could not plausibly do it by hand in a day, do not do
|
|
68
|
+
it. Account health beats throughput.
|