@floomhq/signaldash 0.3.0 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +4 -0
  2. package/bin/sd.mjs +81 -9
  3. package/package.json +8 -2
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
@@ -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
- log(`Open this to connect your ${provider}:\n\n ${r.json.url}\n`);
81
- log("Waiting for authentication to complete...");
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
- log(`Connected ${provider}${status.json.name ? `: ${status.json.name}` : ""}`);
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
- error(`Connection timed out. Manual fallback:\n signaldash connect ${provider} claim <account_id>`);
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 = {}) {
@@ -156,10 +170,64 @@ export async function cmdSkill(dependencies = {}) {
156
170
  log("Your agent now knows how to use LinkedIn + WhatsApp safely through SignalDash.");
157
171
  }
158
172
 
173
+
174
+ // One-command onboarding: login + skill + MCP registration + connect both channels.
175
+ export async function cmdSetup(code, dependencies = {}) {
176
+ const log = dependencies.log || console.log;
177
+ const { execSync } = await import("node:child_process");
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: () => {} });
185
+ if (process.exitCode === 1) return;
186
+ log(" " + chalk.green("+") + " logged in");
187
+
188
+ await cmdSkill({ ...dependencies, log: () => {} });
189
+ log(" " + chalk.green("+") + " agent skill installed");
190
+
191
+ try {
192
+ execSync("claude mcp add signaldash -- npx -y @floomhq/signaldash mcp", { stdio: "ignore" });
193
+ log(" " + chalk.green("+") + " MCP registered with Claude Code");
194
+ } catch {
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"]}}}'));
197
+ }
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("");
219
+ await cmdConnect(provider, dependencies);
220
+ process.exitCode = 0;
221
+ }
222
+ log("\n " + chalk.dim("Try: ") + chalk.white('"list my recent whatsapp chats and draft replies"') + "\n");
223
+ }
224
+
159
225
  export async function main(argv = process.argv.slice(2), dependencies = {}) {
160
226
  const [cmd, a, b, c] = argv;
161
227
  const log = dependencies.log || console.log;
162
- if (cmd === "login") await cmdLogin(a, b === "--backend" ? c : undefined, dependencies);
228
+ 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);
230
+ else if (cmd === "login") await cmdLogin(a, b === "--backend" ? c : undefined, dependencies);
163
231
  else if (cmd === "connect" && b === "claim") await cmdClaim(a, c, dependencies);
164
232
  else if (cmd === "connect") await cmdConnect(a, dependencies);
165
233
  else if (cmd === "mcp") await runMcp(dependencies);
@@ -167,9 +235,13 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) {
167
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.`);
168
236
  }
169
237
 
170
- if (
171
- process.argv[1] &&
172
- realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)
173
- ) {
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()) {
174
246
  await main();
175
247
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/signaldash",
3
- "version": "0.3.0",
3
+ "version": "0.5.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
  }