@floomhq/signaldash 0.13.0 → 0.27.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 +249 -10
- package/bin/sd.mjs +1006 -38
- package/lib/cli.js +16 -22
- package/lib/config-file.js +114 -0
- package/lib/mcp.js +12 -2
- package/package.json +3 -3
- package/skills/signaldash/SKILL.md +1033 -62
- package/skills/signaldash-safe-usage/SKILL.md +74 -9
package/lib/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
|
-
import { chmod, mkdir, readFile
|
|
2
|
+
import { chmod, mkdir, readFile } from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { updateConfigFile } from "./config-file.js";
|
|
5
6
|
import { readSecrets, writeSecrets } from "./secrets.js";
|
|
6
7
|
import { runMcp } from "./mcp.js";
|
|
7
8
|
import { UnipileClient, accountIsReady } from "./unipile.js";
|
|
@@ -49,16 +50,6 @@ async function ensureWorkspace(workspace) {
|
|
|
49
50
|
await chmod(workspace, 0o700);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
async function writeJsonPrivate(target, value) {
|
|
53
|
-
const temporary = `${target}.${process.pid}.tmp`;
|
|
54
|
-
await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, {
|
|
55
|
-
mode: 0o600,
|
|
56
|
-
});
|
|
57
|
-
await chmod(temporary, 0o600);
|
|
58
|
-
await rename(temporary, target);
|
|
59
|
-
await chmod(target, 0o600);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
53
|
export async function readConfig(workspace, { optional = false } = {}) {
|
|
63
54
|
try {
|
|
64
55
|
return JSON.parse(
|
|
@@ -276,18 +267,21 @@ export async function connectCommand(channel, options, dependencies = {}) {
|
|
|
276
267
|
return { connected: false, hostedUrl };
|
|
277
268
|
}
|
|
278
269
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
...
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
270
|
+
updateConfigFile(
|
|
271
|
+
path.join(workspace, "config.json"),
|
|
272
|
+
latest => ({
|
|
273
|
+
...latest,
|
|
274
|
+
version: 1,
|
|
275
|
+
channels: {
|
|
276
|
+
...(latest.channels || {}),
|
|
277
|
+
[channel]: {
|
|
278
|
+
accountId: selected.id,
|
|
279
|
+
name: selected.name || null,
|
|
280
|
+
connectedAt: new Date().toISOString(),
|
|
281
|
+
},
|
|
287
282
|
},
|
|
288
|
-
},
|
|
289
|
-
|
|
290
|
-
await writeJsonPrivate(path.join(workspace, "config.json"), nextConfig);
|
|
283
|
+
}),
|
|
284
|
+
);
|
|
291
285
|
io.stdout.write(
|
|
292
286
|
`${definition.label} connected: ${selected.name || selected.id} (${selected.id})\n`,
|
|
293
287
|
);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
closeSync,
|
|
4
|
+
fsyncSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
openSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
renameSync,
|
|
9
|
+
unlinkSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { randomBytes } from "node:crypto";
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const WAIT_ARRAY = new Int32Array(new SharedArrayBuffer(4));
|
|
17
|
+
const LOCK_TIMEOUT_MS = 5_000;
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
function wait(milliseconds) {
|
|
21
|
+
Atomics.wait(WAIT_ARRAY, 0, 0, milliseconds);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
function acquireLock(lockFile) {
|
|
26
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
27
|
+
for (;;) {
|
|
28
|
+
try {
|
|
29
|
+
return openSync(lockFile, "wx", 0o600);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error.code !== "EEXIST") throw error;
|
|
32
|
+
if (Date.now() >= deadline) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`timed out waiting for SignalDash config lock: ${lockFile}; ` +
|
|
35
|
+
"the lock is never stolen automatically because that could corrupt the session",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
wait(25);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
function releaseLock(lockFile, descriptor) {
|
|
45
|
+
try {
|
|
46
|
+
closeSync(descriptor);
|
|
47
|
+
} finally {
|
|
48
|
+
try {
|
|
49
|
+
unlinkSync(lockFile);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error.code !== "ENOENT") throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
export function readConfigFile(file, { optional = false } = {}) {
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error.code === "ENOENT" && optional) return {};
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
export function updateConfigFile(file, update) {
|
|
68
|
+
const directory = path.dirname(file);
|
|
69
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
70
|
+
chmodSync(directory, 0o700);
|
|
71
|
+
const lockFile = `${file}.lock`;
|
|
72
|
+
const descriptor = acquireLock(lockFile);
|
|
73
|
+
let temporary;
|
|
74
|
+
try {
|
|
75
|
+
const current = readConfigFile(file, { optional: true });
|
|
76
|
+
const next = update({ ...current });
|
|
77
|
+
if (!next || typeof next !== "object" || Array.isArray(next)) {
|
|
78
|
+
throw new TypeError("SignalDash config update must return an object");
|
|
79
|
+
}
|
|
80
|
+
temporary = `${file}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
81
|
+
writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`, {
|
|
82
|
+
mode: 0o600,
|
|
83
|
+
flag: "wx",
|
|
84
|
+
});
|
|
85
|
+
const temporaryDescriptor = openSync(temporary, "r");
|
|
86
|
+
try {
|
|
87
|
+
fsyncSync(temporaryDescriptor);
|
|
88
|
+
} finally {
|
|
89
|
+
closeSync(temporaryDescriptor);
|
|
90
|
+
}
|
|
91
|
+
renameSync(temporary, file);
|
|
92
|
+
temporary = undefined;
|
|
93
|
+
chmodSync(file, 0o600);
|
|
94
|
+
const directoryDescriptor = openSync(directory, "r");
|
|
95
|
+
try {
|
|
96
|
+
fsyncSync(directoryDescriptor);
|
|
97
|
+
} finally {
|
|
98
|
+
closeSync(directoryDescriptor);
|
|
99
|
+
}
|
|
100
|
+
return next;
|
|
101
|
+
} finally {
|
|
102
|
+
try {
|
|
103
|
+
if (temporary) {
|
|
104
|
+
try {
|
|
105
|
+
unlinkSync(temporary);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error.code !== "ENOENT") throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} finally {
|
|
111
|
+
releaseLock(lockFile, descriptor);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
package/lib/mcp.js
CHANGED
|
@@ -127,7 +127,7 @@ function cleanMessage(message) {
|
|
|
127
127
|
id: message.id ?? message.provider_id ?? null,
|
|
128
128
|
text: message.text ?? null,
|
|
129
129
|
timestamp: message.timestamp ?? null,
|
|
130
|
-
is_sender:
|
|
130
|
+
is_sender: providerIsOwn(message.is_sender),
|
|
131
131
|
sender_id: message.sender_id ?? null,
|
|
132
132
|
attachments: (message.attachments || []).map((attachment) => ({
|
|
133
133
|
id: attachment.id ?? null,
|
|
@@ -140,6 +140,16 @@ function cleanMessage(message) {
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
function providerIsOwn(value) {
|
|
144
|
+
if (typeof value === "boolean") return value;
|
|
145
|
+
if (typeof value === "number") return value !== 0;
|
|
146
|
+
if (typeof value === "string") {
|
|
147
|
+
const normalized = value.trim().toLowerCase();
|
|
148
|
+
return ["1", "true", "yes", "y"].includes(normalized);
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
function itemList(payload) {
|
|
144
154
|
return Array.isArray(payload?.items)
|
|
145
155
|
? payload.items
|
|
@@ -207,7 +217,7 @@ async function executeTool({ name, args, config, client, guard }) {
|
|
|
207
217
|
const recent = itemList(await client.listMessages(chatId, 20));
|
|
208
218
|
const duplicate = recent.find(
|
|
209
219
|
(message) =>
|
|
210
|
-
message.is_sender &&
|
|
220
|
+
providerIsOwn(message.is_sender) &&
|
|
211
221
|
typeof message.text === "string" &&
|
|
212
222
|
message.text.trim() === text,
|
|
213
223
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floomhq/signaldash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Secure LinkedIn, WhatsApp, and email access for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "node --test",
|
|
21
|
-
"check": "node --check bin/sd.mjs && node --check server/server.cjs && node --check bin/signaldash.js && node --check lib/cli.js && node --check lib/mcp.js && node --check lib/rate-guard.js && node --check lib/secrets.js && node --check lib/unipile.js"
|
|
21
|
+
"check": "node --check bin/sd.mjs && node --check server/server.cjs && node --check server/attachments.cjs && node --check server/write-control.cjs && node --check server/voice-profile.cjs && node --check server/campaign-store.cjs && node --check server/campaign-runner.cjs && node --check server/withdrawal-store.cjs && node --check server/post-scheduler.cjs && node --check bin/signaldash.js && node --check lib/cli.js && node --check lib/config-file.js && node --check lib/mcp.js && node --check lib/rate-guard.js && node --check lib/secrets.js && node --check lib/unipile.js"
|
|
22
22
|
},
|
|
23
23
|
"keywords": [
|
|
24
24
|
"mcp",
|
|
@@ -34,4 +34,4 @@
|
|
|
34
34
|
"ora": "8.0.1",
|
|
35
35
|
"open": "10.1.0"
|
|
36
36
|
}
|
|
37
|
-
}
|
|
37
|
+
}
|