@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.
- package/README.md +4 -0
- package/bin/sd.mjs +81 -9
- 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
|
-
|
|
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 = {}) {
|
|
@@ -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 === "
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
"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
|
}
|