@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 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(`Connection timed out. Manual fallback:\n signaldash connect ${provider} claim <account_id>`);
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 === "login") await cmdLogin(a, b === "--backend" ? c : undefined, dependencies);
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 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\nThe agent reaches channels only through SignalDash. No keys on your machine.`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/signaldash",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Secure LinkedIn and WhatsApp MCP access for AI agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.