@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 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(`Not connected yet. The link above stays valid — open it, finish authenticating, then run:\n npx @floomhq/signaldash connect ${provider}`);
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
- log("SignalDash setup\n");
165
- await cmdLogin(code, undefined, dependencies);
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
- await cmdSkill(dependencies);
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("Registered the MCP with Claude Code.");
193
+ log(" " + chalk.green("+") + " MCP registered with Claude Code");
171
194
  } catch {
172
- log("To finish in Cursor, add to .cursor/mcp.json:");
173
- log(' {"mcpServers":{"signaldash":{"command":"npx","args":["-y","@floomhq/signaldash","mcp"]}}}');
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
- for (const provider of ["linkedin", "whatsapp"]) {
176
- log(`\n--- connect ${provider} ---`);
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; // a skipped channel should not fail the whole setup
220
+ process.exitCode = 0;
179
221
  }
180
- log("\nDone. Ask your agent: \"list my recent LinkedIn chats and draft replies.\"");
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
- if (
197
- process.argv[1] &&
198
- realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)
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.4.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