@a-company/paradigm 3.43.0 → 3.46.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/dist/{chunk-YW5OCVKB.js → chunk-FKJUBQU3.js} +14 -1
- package/dist/chunk-KVDYJLTC.js +121 -0
- package/dist/{symphony-ROEKK7VD.js → chunk-S2HO5MLR.js} +53 -431
- package/dist/{chunk-RGFANZ4Q.js → chunk-ZDHLG5VP.js} +14 -1
- package/dist/{chunk-CZEIK3Y2.js → chunk-ZMQA6SCO.js} +1 -1
- package/dist/{chunk-7WEKMZ46.js → chunk-ZSYVKSY6.js} +1 -1
- package/dist/{commands-LEPFD7S5.js → commands-5N4ILTPH.js} +13 -0
- package/dist/{dist-RVKYUCRU.js → dist-CM3MVWWW.js} +1 -1
- package/dist/{dist-Y7I3CFY5.js → dist-POMVY6WP.js} +2 -2
- package/dist/{habits-O37HTUKE.js → habits-RG5SVKXP.js} +2 -2
- package/dist/index.js +69 -48
- package/dist/mcp.js +89 -9
- package/dist/peers-RFQCWVLV.js +82 -0
- package/dist/{platform-server-KK4OCRTV.js → platform-server-H7Y6Q7O4.js} +10 -1
- package/dist/{reindex-NZQRGKPN.js → reindex-WIJMCJ4A.js} +1 -1
- package/dist/{sentinel-BKYTBT7M.js → sentinel-UOIGJWHH.js} +1 -1
- package/dist/{sentinel-bridge-IZTXYS5M.js → sentinel-bridge-APDXYAZS.js} +1 -1
- package/dist/sentinel-mcp.js +13 -0
- package/dist/sentinel.js +6 -6
- package/dist/{serve-3V2WXLGM.js → serve-KKEHE44G.js} +1 -1
- package/dist/{server-OFEJ2HJP.js → server-JV6UFGWZ.js} +1 -1
- package/dist/symphony-6K3HD7AW.js +791 -0
- package/dist/symphony-YCHBYN3E.js +326 -0
- package/dist/symphony-peers-APOGJPF4.js +120 -0
- package/dist/symphony-peers-HSY3RI3S.js +34 -0
- package/dist/symphony-relay-GTAJRCVF.js +683 -0
- package/dist/{triage-POXJ2TIX.js → triage-IZ4MDYNB.js} +2 -2
- package/dist/university-content/courses/para-501.json +84 -0
- package/package.json +1 -1
- package/platform-ui/dist/assets/{GitSection-DvyJBF_-.js → GitSection-BD3Ze06e.js} +1 -1
- package/platform-ui/dist/assets/{GraphSection-BiQrXqfs.js → GraphSection-SglITfSs.js} +1 -1
- package/platform-ui/dist/assets/{LoreSection-BaH1FaRb.js → LoreSection-bR5Km4Fd.js} +1 -1
- package/platform-ui/dist/assets/{SentinelSection-DemAznjI.js → SentinelSection-QSpAZArG.js} +1 -1
- package/platform-ui/dist/assets/SymphonySection-CobYJgvg.js +1 -0
- package/platform-ui/dist/assets/SymphonySection-zY0C5tFl.css +1 -0
- package/platform-ui/dist/assets/{index-DDKhCt-w.js → index-DbxeSMkV.js} +11 -11
- package/platform-ui/dist/index.html +1 -1
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
approveFileRequest,
|
|
4
|
+
buildMessage,
|
|
5
|
+
cleanStaleAgents,
|
|
6
|
+
createThread,
|
|
7
|
+
denyFileRequest,
|
|
8
|
+
discoverClaudeCodeSessions,
|
|
9
|
+
expireOldRequests,
|
|
10
|
+
getMyIdentity,
|
|
11
|
+
getThreadMessages,
|
|
12
|
+
isAgentAsleep,
|
|
13
|
+
listAgents,
|
|
14
|
+
listFileRequests,
|
|
15
|
+
listThreads,
|
|
16
|
+
loadThread,
|
|
17
|
+
readInbox,
|
|
18
|
+
resolveAgentIdentity,
|
|
19
|
+
resolveThread,
|
|
20
|
+
routeMessage
|
|
21
|
+
} from "./chunk-S2HO5MLR.js";
|
|
22
|
+
import "./chunk-ZXMDA7VB.js";
|
|
23
|
+
|
|
24
|
+
// src/platform-server/routes/symphony.ts
|
|
25
|
+
import { Router } from "express";
|
|
26
|
+
function createSymphonyRouter(projectDir, broadcast) {
|
|
27
|
+
const router = Router();
|
|
28
|
+
router.get("/agents", (_req, res) => {
|
|
29
|
+
try {
|
|
30
|
+
cleanStaleAgents();
|
|
31
|
+
const agents = listAgents();
|
|
32
|
+
const discovered = discoverClaudeCodeSessions();
|
|
33
|
+
const agentIds = new Set(agents.map((a) => a.id));
|
|
34
|
+
for (const d of discovered) {
|
|
35
|
+
if (!agentIds.has(d.id)) agents.push(d);
|
|
36
|
+
}
|
|
37
|
+
const result = agents.map((a) => ({
|
|
38
|
+
id: a.id,
|
|
39
|
+
name: a.name,
|
|
40
|
+
project: a.project,
|
|
41
|
+
role: a.role,
|
|
42
|
+
status: isAgentAsleep(a) ? "asleep" : "awake",
|
|
43
|
+
lastPoll: a.lastPoll,
|
|
44
|
+
startedAt: a.startedAt,
|
|
45
|
+
statusBlurb: a.statusBlurb
|
|
46
|
+
}));
|
|
47
|
+
res.json({ agents: result });
|
|
48
|
+
} catch (err) {
|
|
49
|
+
res.status(500).json({ error: "Failed to list agents", detail: String(err) });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
router.get("/agents/me", (_req, res) => {
|
|
53
|
+
try {
|
|
54
|
+
const identity = getMyIdentity(projectDir);
|
|
55
|
+
res.json({ identity: identity || null });
|
|
56
|
+
} catch (err) {
|
|
57
|
+
res.status(500).json({ error: "Failed to get identity", detail: String(err) });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
router.get("/peers", async (_req, res) => {
|
|
61
|
+
try {
|
|
62
|
+
const { loadPeers } = await import("./symphony-peers-HSY3RI3S.js");
|
|
63
|
+
const peers = loadPeers();
|
|
64
|
+
const result = peers.map((p) => ({
|
|
65
|
+
id: p.id,
|
|
66
|
+
displayName: p.displayName,
|
|
67
|
+
address: p.address,
|
|
68
|
+
connectedAt: p.connectedAt,
|
|
69
|
+
lastSeen: p.lastSeen,
|
|
70
|
+
revoked: p.revoked,
|
|
71
|
+
agents: p.agents || []
|
|
72
|
+
}));
|
|
73
|
+
res.json({ peers: result });
|
|
74
|
+
} catch (err) {
|
|
75
|
+
res.status(500).json({ error: "Failed to list peers", detail: String(err) });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
router.get("/threads", (req, res) => {
|
|
79
|
+
try {
|
|
80
|
+
const statusParam = req.query.status;
|
|
81
|
+
let status;
|
|
82
|
+
if (statusParam === "active" || statusParam === "resolved") {
|
|
83
|
+
status = statusParam;
|
|
84
|
+
}
|
|
85
|
+
const threads = listThreads(status);
|
|
86
|
+
const result = threads.map((t) => ({
|
|
87
|
+
id: t.id,
|
|
88
|
+
topic: t.topic,
|
|
89
|
+
status: t.status,
|
|
90
|
+
participants: t.participants.map((p) => ({ id: p.id, name: p.name, type: p.type })),
|
|
91
|
+
messageCount: t.messageCount,
|
|
92
|
+
lastActivity: t.lastActivity,
|
|
93
|
+
decision: t.decision
|
|
94
|
+
}));
|
|
95
|
+
res.json({ threads: result });
|
|
96
|
+
} catch (err) {
|
|
97
|
+
res.status(500).json({ error: "Failed to list threads", detail: String(err) });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
router.get("/threads/:threadId", (req, res) => {
|
|
101
|
+
try {
|
|
102
|
+
const { threadId } = req.params;
|
|
103
|
+
const thread = loadThread(threadId);
|
|
104
|
+
if (!thread) {
|
|
105
|
+
res.status(404).json({ error: `Thread not found: ${threadId}` });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const messages = getThreadMessages(threadId);
|
|
109
|
+
const symbolsDiscussed = /* @__PURE__ */ new Set();
|
|
110
|
+
for (const msg of messages) {
|
|
111
|
+
for (const sym of msg.symbols) symbolsDiscussed.add(sym);
|
|
112
|
+
}
|
|
113
|
+
res.json({
|
|
114
|
+
thread: {
|
|
115
|
+
id: thread.id,
|
|
116
|
+
topic: thread.topic,
|
|
117
|
+
status: thread.status,
|
|
118
|
+
participants: thread.participants.map((p) => ({ id: p.id, name: p.name, type: p.type })),
|
|
119
|
+
messageCount: thread.messageCount,
|
|
120
|
+
lastActivity: thread.lastActivity,
|
|
121
|
+
decision: thread.decision
|
|
122
|
+
},
|
|
123
|
+
messages: messages.map((m) => ({
|
|
124
|
+
id: m.id,
|
|
125
|
+
sender: { id: m.sender.id, name: m.sender.name, type: m.sender.type },
|
|
126
|
+
intent: m.intent,
|
|
127
|
+
text: m.content.text,
|
|
128
|
+
timestamp: m.timestamp,
|
|
129
|
+
symbols: m.symbols,
|
|
130
|
+
diff: m.content.diff,
|
|
131
|
+
decision: m.content.decision,
|
|
132
|
+
recipients: m.recipients?.map((r) => ({ id: r.id, name: r.name }))
|
|
133
|
+
})),
|
|
134
|
+
symbolsDiscussed: [...symbolsDiscussed]
|
|
135
|
+
});
|
|
136
|
+
} catch (err) {
|
|
137
|
+
res.status(500).json({ error: "Failed to load thread", detail: String(err) });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
router.post("/threads/:threadId/resolve", (req, res) => {
|
|
141
|
+
try {
|
|
142
|
+
const { threadId } = req.params;
|
|
143
|
+
const { decision } = req.body;
|
|
144
|
+
const success = resolveThread(threadId, decision);
|
|
145
|
+
if (!success) {
|
|
146
|
+
res.status(404).json({ error: `Thread not found: ${threadId}` });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (broadcast) {
|
|
150
|
+
broadcast({ type: "symphony:thread_resolved", threadId, decision });
|
|
151
|
+
}
|
|
152
|
+
res.json({ resolved: true, threadId, decision });
|
|
153
|
+
} catch (err) {
|
|
154
|
+
res.status(500).json({ error: "Failed to resolve thread", detail: String(err) });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
router.post("/messages", (req, res) => {
|
|
158
|
+
try {
|
|
159
|
+
const { intent, text, threadRoot, recipients, symbols, diff, decision } = req.body;
|
|
160
|
+
if (!intent || !text) {
|
|
161
|
+
res.status(400).json({ error: "intent and text are required" });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const agentId = resolveAgentIdentity(projectDir);
|
|
165
|
+
const sender = {
|
|
166
|
+
id: `human/${agentId}`,
|
|
167
|
+
name: "Human (Platform UI)",
|
|
168
|
+
type: "human"
|
|
169
|
+
};
|
|
170
|
+
let effectiveThreadRoot = threadRoot;
|
|
171
|
+
let threadCreated = false;
|
|
172
|
+
if (!threadRoot) {
|
|
173
|
+
const topic = text.length > 60 ? text.slice(0, 60) + "..." : text;
|
|
174
|
+
const thread = createThread(topic, sender);
|
|
175
|
+
effectiveThreadRoot = thread.id;
|
|
176
|
+
threadCreated = true;
|
|
177
|
+
}
|
|
178
|
+
let resolvedRecipients;
|
|
179
|
+
if (recipients && recipients.length > 0) {
|
|
180
|
+
const allAgents = listAgents();
|
|
181
|
+
resolvedRecipients = recipients.map((id) => {
|
|
182
|
+
const agent = allAgents.find((a) => a.id === id);
|
|
183
|
+
if (agent) return { id: agent.id, name: agent.name, type: "agent" };
|
|
184
|
+
return { id, name: id, type: "agent" };
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const message = buildMessage({
|
|
188
|
+
sender,
|
|
189
|
+
recipients: resolvedRecipients,
|
|
190
|
+
intent,
|
|
191
|
+
text,
|
|
192
|
+
threadRoot: effectiveThreadRoot,
|
|
193
|
+
symbols,
|
|
194
|
+
diff,
|
|
195
|
+
decision
|
|
196
|
+
});
|
|
197
|
+
const deliveryCount = routeMessage(message);
|
|
198
|
+
if (broadcast) {
|
|
199
|
+
broadcast({
|
|
200
|
+
type: "symphony:message",
|
|
201
|
+
message: {
|
|
202
|
+
id: message.id,
|
|
203
|
+
sender: { id: sender.id, name: sender.name, type: sender.type },
|
|
204
|
+
intent: message.intent,
|
|
205
|
+
text: message.content.text,
|
|
206
|
+
timestamp: message.timestamp,
|
|
207
|
+
symbols: message.symbols,
|
|
208
|
+
diff: message.content.diff,
|
|
209
|
+
decision: message.content.decision
|
|
210
|
+
},
|
|
211
|
+
threadId: effectiveThreadRoot
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
res.json({
|
|
215
|
+
sent: true,
|
|
216
|
+
messageId: message.id,
|
|
217
|
+
threadId: effectiveThreadRoot,
|
|
218
|
+
threadCreated,
|
|
219
|
+
deliveredTo: deliveryCount
|
|
220
|
+
});
|
|
221
|
+
} catch (err) {
|
|
222
|
+
res.status(500).json({ error: "Failed to send message", detail: String(err) });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
router.get("/inbox", (_req, res) => {
|
|
226
|
+
try {
|
|
227
|
+
const agentId = resolveAgentIdentity(projectDir);
|
|
228
|
+
const messages = readInbox(agentId);
|
|
229
|
+
res.json({
|
|
230
|
+
agentId,
|
|
231
|
+
messages: messages.map((m) => ({
|
|
232
|
+
id: m.id,
|
|
233
|
+
sender: { id: m.sender.id, name: m.sender.name, type: m.sender.type },
|
|
234
|
+
intent: m.intent,
|
|
235
|
+
text: m.content.text,
|
|
236
|
+
timestamp: m.timestamp,
|
|
237
|
+
threadRoot: m.threadRoot,
|
|
238
|
+
symbols: m.symbols
|
|
239
|
+
}))
|
|
240
|
+
});
|
|
241
|
+
} catch (err) {
|
|
242
|
+
res.status(500).json({ error: "Failed to read inbox", detail: String(err) });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
router.get("/file-requests", (req, res) => {
|
|
246
|
+
try {
|
|
247
|
+
expireOldRequests();
|
|
248
|
+
const statusParam = req.query.status;
|
|
249
|
+
let status;
|
|
250
|
+
if (statusParam === "pending" || statusParam === "approved" || statusParam === "denied" || statusParam === "expired") {
|
|
251
|
+
status = statusParam;
|
|
252
|
+
}
|
|
253
|
+
const requests = listFileRequests(status);
|
|
254
|
+
const result = requests.map((r) => ({
|
|
255
|
+
requestId: r.request.requestId,
|
|
256
|
+
filePath: r.request.filePath,
|
|
257
|
+
reason: r.request.reason,
|
|
258
|
+
requester: { id: r.request.requester.id, name: r.request.requester.name },
|
|
259
|
+
urgency: r.request.urgency,
|
|
260
|
+
snippet: r.request.snippet,
|
|
261
|
+
status: r.status,
|
|
262
|
+
createdAt: r.createdAt,
|
|
263
|
+
resolvedAt: r.resolvedAt,
|
|
264
|
+
denyReason: r.denyReason
|
|
265
|
+
}));
|
|
266
|
+
res.json({ fileRequests: result });
|
|
267
|
+
} catch (err) {
|
|
268
|
+
res.status(500).json({ error: "Failed to list file requests", detail: String(err) });
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
router.post("/file-requests/:requestId/action", (req, res) => {
|
|
272
|
+
try {
|
|
273
|
+
const { requestId } = req.params;
|
|
274
|
+
const { action, reason } = req.body;
|
|
275
|
+
if (!action) {
|
|
276
|
+
res.status(400).json({ error: "action is required" });
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (action === "deny") {
|
|
280
|
+
const success = denyFileRequest(requestId, reason);
|
|
281
|
+
res.json({ success, requestId, action: "denied", reason });
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const redact = action === "approve-redacted";
|
|
285
|
+
const result = approveFileRequest(requestId, projectDir, redact);
|
|
286
|
+
if (!result.success) {
|
|
287
|
+
res.status(400).json({ success: false, requestId, error: result.error });
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
res.json({
|
|
291
|
+
success: true,
|
|
292
|
+
requestId,
|
|
293
|
+
action: redact ? "approved-redacted" : "approved",
|
|
294
|
+
filePath: result.delivery?.filePath,
|
|
295
|
+
size: result.delivery?.size
|
|
296
|
+
});
|
|
297
|
+
} catch (err) {
|
|
298
|
+
res.status(500).json({ error: "Failed to handle file request", detail: String(err) });
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
router.get("/status", (_req, res) => {
|
|
302
|
+
try {
|
|
303
|
+
cleanStaleAgents();
|
|
304
|
+
const agents = listAgents();
|
|
305
|
+
const awake = agents.filter((a) => !isAgentAsleep(a)).length;
|
|
306
|
+
const threads = listThreads("active");
|
|
307
|
+
const agentId = resolveAgentIdentity(projectDir);
|
|
308
|
+
const unread = readInbox(agentId);
|
|
309
|
+
const pendingRequests = listFileRequests("pending");
|
|
310
|
+
res.json({
|
|
311
|
+
agentCount: agents.length,
|
|
312
|
+
awakeCount: awake,
|
|
313
|
+
asleepCount: agents.length - awake,
|
|
314
|
+
activeThreadCount: threads.length,
|
|
315
|
+
unreadCount: unread.length,
|
|
316
|
+
pendingFileRequests: pendingRequests.length
|
|
317
|
+
});
|
|
318
|
+
} catch (err) {
|
|
319
|
+
res.status(500).json({ error: "Failed to get status", detail: String(err) });
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
return router;
|
|
323
|
+
}
|
|
324
|
+
export {
|
|
325
|
+
createSymphonyRouter
|
|
326
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../paradigm-mcp/src/utils/symphony-peers.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import * as os from "os";
|
|
7
|
+
import * as crypto from "crypto";
|
|
8
|
+
var SCORE_DIR = path.join(os.homedir(), ".paradigm", "score");
|
|
9
|
+
var PEERS_FILE = path.join(SCORE_DIR, "peers.json");
|
|
10
|
+
var PAIRING_TTL_MS = 5 * 60 * 1e3;
|
|
11
|
+
function loadPeers() {
|
|
12
|
+
try {
|
|
13
|
+
if (!fs.existsSync(PEERS_FILE)) return [];
|
|
14
|
+
const content = fs.readFileSync(PEERS_FILE, "utf-8");
|
|
15
|
+
const parsed = JSON.parse(content);
|
|
16
|
+
if (!Array.isArray(parsed)) return [];
|
|
17
|
+
return parsed;
|
|
18
|
+
} catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function savePeers(peers) {
|
|
23
|
+
if (!fs.existsSync(SCORE_DIR)) {
|
|
24
|
+
fs.mkdirSync(SCORE_DIR, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
fs.writeFileSync(PEERS_FILE, JSON.stringify(peers, null, 2), { mode: 384 });
|
|
27
|
+
}
|
|
28
|
+
function findPeer(id) {
|
|
29
|
+
const peers = loadPeers();
|
|
30
|
+
return peers.find((p) => p.id === id) ?? null;
|
|
31
|
+
}
|
|
32
|
+
function addPeer(peer) {
|
|
33
|
+
const peers = loadPeers();
|
|
34
|
+
const idx = peers.findIndex((p) => p.id === peer.id);
|
|
35
|
+
if (idx >= 0) {
|
|
36
|
+
peers[idx] = peer;
|
|
37
|
+
} else {
|
|
38
|
+
peers.push(peer);
|
|
39
|
+
}
|
|
40
|
+
savePeers(peers);
|
|
41
|
+
}
|
|
42
|
+
function revokePeer(id) {
|
|
43
|
+
const peers = loadPeers();
|
|
44
|
+
const peer = peers.find((p) => p.id === id);
|
|
45
|
+
if (!peer) return false;
|
|
46
|
+
peer.revoked = true;
|
|
47
|
+
savePeers(peers);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
function forgetAllPeers() {
|
|
51
|
+
const peers = loadPeers();
|
|
52
|
+
const count = peers.length;
|
|
53
|
+
if (fs.existsSync(PEERS_FILE)) {
|
|
54
|
+
fs.unlinkSync(PEERS_FILE);
|
|
55
|
+
}
|
|
56
|
+
return count;
|
|
57
|
+
}
|
|
58
|
+
function updatePeerLastSeen(id) {
|
|
59
|
+
const peers = loadPeers();
|
|
60
|
+
const peer = peers.find((p) => p.id === id);
|
|
61
|
+
if (!peer) return;
|
|
62
|
+
peer.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
63
|
+
savePeers(peers);
|
|
64
|
+
}
|
|
65
|
+
function updatePeerAgents(id, agents) {
|
|
66
|
+
const peers = loadPeers();
|
|
67
|
+
const peer = peers.find((p) => p.id === id);
|
|
68
|
+
if (!peer) return;
|
|
69
|
+
peer.agents = agents;
|
|
70
|
+
savePeers(peers);
|
|
71
|
+
}
|
|
72
|
+
function generatePairing() {
|
|
73
|
+
const sharedSecret = crypto.randomBytes(32).toString("hex");
|
|
74
|
+
const code = (parseInt(crypto.randomBytes(3).toString("hex"), 16) % 1e6).toString().padStart(6, "0");
|
|
75
|
+
const codeHash = crypto.createHash("sha256").update(code).digest("hex");
|
|
76
|
+
return {
|
|
77
|
+
code,
|
|
78
|
+
codeHash,
|
|
79
|
+
sharedSecret,
|
|
80
|
+
createdAt: Date.now()
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function verifyPairingCode(state, code) {
|
|
84
|
+
if (Date.now() - state.createdAt > PAIRING_TTL_MS) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
88
|
+
return hash === state.codeHash;
|
|
89
|
+
}
|
|
90
|
+
function computeHmacProof(challenge, codeHash) {
|
|
91
|
+
return crypto.createHmac("sha256", codeHash).update(challenge).digest("hex");
|
|
92
|
+
}
|
|
93
|
+
function verifyHmacProof(challenge, codeHash, proof) {
|
|
94
|
+
const expected = computeHmacProof(challenge, codeHash);
|
|
95
|
+
if (expected.length !== proof.length) return false;
|
|
96
|
+
try {
|
|
97
|
+
return crypto.timingSafeEqual(
|
|
98
|
+
Buffer.from(expected, "hex"),
|
|
99
|
+
Buffer.from(proof, "hex")
|
|
100
|
+
);
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
PAIRING_TTL_MS,
|
|
107
|
+
PEERS_FILE,
|
|
108
|
+
addPeer,
|
|
109
|
+
computeHmacProof,
|
|
110
|
+
findPeer,
|
|
111
|
+
forgetAllPeers,
|
|
112
|
+
generatePairing,
|
|
113
|
+
loadPeers,
|
|
114
|
+
revokePeer,
|
|
115
|
+
savePeers,
|
|
116
|
+
updatePeerAgents,
|
|
117
|
+
updatePeerLastSeen,
|
|
118
|
+
verifyHmacProof,
|
|
119
|
+
verifyPairingCode
|
|
120
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
PAIRING_TTL_MS,
|
|
4
|
+
PEERS_FILE,
|
|
5
|
+
addPeer,
|
|
6
|
+
computeHmacProof,
|
|
7
|
+
findPeer,
|
|
8
|
+
forgetAllPeers,
|
|
9
|
+
generatePairing,
|
|
10
|
+
loadPeers,
|
|
11
|
+
revokePeer,
|
|
12
|
+
savePeers,
|
|
13
|
+
updatePeerAgents,
|
|
14
|
+
updatePeerLastSeen,
|
|
15
|
+
verifyHmacProof,
|
|
16
|
+
verifyPairingCode
|
|
17
|
+
} from "./chunk-KVDYJLTC.js";
|
|
18
|
+
import "./chunk-ZXMDA7VB.js";
|
|
19
|
+
export {
|
|
20
|
+
PAIRING_TTL_MS,
|
|
21
|
+
PEERS_FILE,
|
|
22
|
+
addPeer,
|
|
23
|
+
computeHmacProof,
|
|
24
|
+
findPeer,
|
|
25
|
+
forgetAllPeers,
|
|
26
|
+
generatePairing,
|
|
27
|
+
loadPeers,
|
|
28
|
+
revokePeer,
|
|
29
|
+
savePeers,
|
|
30
|
+
updatePeerAgents,
|
|
31
|
+
updatePeerLastSeen,
|
|
32
|
+
verifyHmacProof,
|
|
33
|
+
verifyPairingCode
|
|
34
|
+
};
|