@floomhq/signaldash 0.12.0 → 0.22.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 +121 -12
- package/bin/sd.mjs +397 -27
- package/lib/cli.js +16 -22
- package/lib/config-file.js +114 -0
- package/package.json +3 -3
- package/skills/signaldash/SKILL.md +1250 -95
- 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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floomhq/signaldash",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Secure LinkedIn and
|
|
3
|
+
"version": "0.22.0",
|
|
4
|
+
"description": "Secure LinkedIn, WhatsApp, and email access for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"signaldash": "bin/sd.mjs"
|
|
@@ -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/write-control.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",
|