pairai 0.5.2 → 0.7.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/lib.ts +37 -0
- package/package.json +1 -1
- package/pairai.ts +44 -6
package/lib.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { homedir } from "node:os";
|
|
|
8
8
|
import {
|
|
9
9
|
publicEncrypt, privateDecrypt, sign, verify,
|
|
10
10
|
randomBytes, createCipheriv, createDecipheriv,
|
|
11
|
+
createHash,
|
|
11
12
|
constants as cryptoConstants,
|
|
12
13
|
} from "node:crypto";
|
|
13
14
|
|
|
@@ -302,6 +303,42 @@ export function localEncrypt(
|
|
|
302
303
|
/**
|
|
303
304
|
* Verify signature, unwrap AES key with own private key, decrypt AES-256-GCM.
|
|
304
305
|
*/
|
|
306
|
+
/**
|
|
307
|
+
* Solve a PoW challenge by finding a nonce such that
|
|
308
|
+
* SHA-256(challenge + nonce) has at least `difficulty` leading zero bits.
|
|
309
|
+
*/
|
|
310
|
+
export function solveChallenge(challenge: string, difficulty: number): string {
|
|
311
|
+
let nonce = 0;
|
|
312
|
+
while (true) {
|
|
313
|
+
const solution = nonce.toString(16).padStart(8, "0");
|
|
314
|
+
const hash = createHash("sha256").update(challenge + solution).digest();
|
|
315
|
+
let zeroBits = 0;
|
|
316
|
+
for (const byte of hash) {
|
|
317
|
+
if (byte === 0) { zeroBits += 8; continue; }
|
|
318
|
+
zeroBits += Math.clz32(byte) - 24;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
if (zeroBits >= difficulty) return solution;
|
|
322
|
+
nonce++;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Fetch a PoW challenge from the hub and solve it.
|
|
328
|
+
* Returns { challenge, solution } or null if the hub doesn't support PoW.
|
|
329
|
+
*/
|
|
330
|
+
export async function solveHubChallenge(hubUrl: string): Promise<{ challenge: string; solution: string } | null> {
|
|
331
|
+
try {
|
|
332
|
+
const res = await fetch(`${hubUrl}/agents/challenge`);
|
|
333
|
+
if (!res.ok) return null;
|
|
334
|
+
const data = await res.json() as { challenge: string; difficulty: number };
|
|
335
|
+
const solution = solveChallenge(data.challenge, data.difficulty);
|
|
336
|
+
return { challenge: data.challenge, solution };
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
305
342
|
export function localDecrypt(
|
|
306
343
|
ciphertext: string,
|
|
307
344
|
sig: string,
|
package/package.json
CHANGED
package/pairai.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { writeFileSync, mkdirSync, readFileSync, existsSync, appendFileSync, ope
|
|
|
24
24
|
import { homedir } from "node:os";
|
|
25
25
|
import { join, dirname, resolve as pathResolve, sep as pathSep, basename, extname } from "node:path";
|
|
26
26
|
import { fileURLToPath } from "node:url";
|
|
27
|
-
import { validateProvider, detectProvider, checkExistingConfig, formatKeyBackupBox, acquireLock, releaseLock, getProviderConfig, localEncrypt as _localEncrypt, localDecrypt as _localDecrypt } from "./lib.js";
|
|
27
|
+
import { validateProvider, detectProvider, checkExistingConfig, formatKeyBackupBox, acquireLock, releaseLock, getProviderConfig, localEncrypt as _localEncrypt, localDecrypt as _localDecrypt, solveHubChallenge } from "./lib.js";
|
|
28
28
|
import type { Provider } from "./lib.js";
|
|
29
29
|
import select from "@inquirer/select";
|
|
30
30
|
import input from "@inquirer/input";
|
|
@@ -417,10 +417,27 @@ if (command === "setup") {
|
|
|
417
417
|
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
418
418
|
});
|
|
419
419
|
|
|
420
|
+
// Solve PoW challenge (falls back gracefully if hub doesn't support PoW)
|
|
421
|
+
process.stdout.write(" Solving registration challenge...");
|
|
422
|
+
const startTime = Date.now();
|
|
423
|
+
const powResult = await solveHubChallenge(hubUrl);
|
|
424
|
+
if (powResult) {
|
|
425
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
426
|
+
console.log(` done (${elapsed}s)`);
|
|
427
|
+
} else {
|
|
428
|
+
console.log(" (skipped — hub may not require PoW)");
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const registrationBody: Record<string, unknown> = { name: agentName, publicKey };
|
|
432
|
+
if (powResult) {
|
|
433
|
+
registrationBody.challenge = powResult.challenge;
|
|
434
|
+
registrationBody.solution = powResult.solution;
|
|
435
|
+
}
|
|
436
|
+
|
|
420
437
|
const res = await fetch(`${hubUrl}/agents`, {
|
|
421
438
|
method: "POST",
|
|
422
439
|
headers: { "Content-Type": "application/json" },
|
|
423
|
-
body: JSON.stringify(
|
|
440
|
+
body: JSON.stringify(registrationBody),
|
|
424
441
|
});
|
|
425
442
|
|
|
426
443
|
if (!res.ok) {
|
|
@@ -1060,6 +1077,14 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
1060
1077
|
required: ["agent_id"],
|
|
1061
1078
|
},
|
|
1062
1079
|
},
|
|
1080
|
+
{
|
|
1081
|
+
name: "pairai_export_my_data",
|
|
1082
|
+
description: "Export all your data — profile, connections, tasks, messages, files, blocks, and events. Rate limited to one export per 10 minutes.",
|
|
1083
|
+
inputSchema: {
|
|
1084
|
+
type: "object" as const,
|
|
1085
|
+
properties: {},
|
|
1086
|
+
},
|
|
1087
|
+
},
|
|
1063
1088
|
],
|
|
1064
1089
|
}));
|
|
1065
1090
|
|
|
@@ -1364,7 +1389,8 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1364
1389
|
if (data.encrypted) {
|
|
1365
1390
|
try {
|
|
1366
1391
|
data.description = decryptTaskDescription(data, data.id);
|
|
1367
|
-
} catch {
|
|
1392
|
+
} catch (err) {
|
|
1393
|
+
console.error(`[pairai] [crypto] task description decryption failed for ${data.id}: ${(err as Error).message}`);
|
|
1368
1394
|
data.description = "[decryption failed]";
|
|
1369
1395
|
}
|
|
1370
1396
|
}
|
|
@@ -1377,7 +1403,8 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1377
1403
|
try {
|
|
1378
1404
|
const d = decryptMessage(m, data.id);
|
|
1379
1405
|
return { ...m, content: d.content, contentType: d.contentType };
|
|
1380
|
-
} catch {
|
|
1406
|
+
} catch (err) {
|
|
1407
|
+
console.error(`[pairai] [crypto] message decryption failed for task ${data.id}: ${(err as Error).message}`);
|
|
1381
1408
|
return { ...m, content: "[decryption failed]", contentType: "text" };
|
|
1382
1409
|
}
|
|
1383
1410
|
}
|
|
@@ -1723,6 +1750,15 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1723
1750
|
}
|
|
1724
1751
|
}
|
|
1725
1752
|
|
|
1753
|
+
if (name === "pairai_export_my_data") {
|
|
1754
|
+
try {
|
|
1755
|
+
const data = await hubGet("/agents/me/export");
|
|
1756
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
1757
|
+
} catch (err) {
|
|
1758
|
+
return { content: [{ type: "text" as const, text: `Error: ${(err as Error).message}` }], isError: true };
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1726
1762
|
throw new Error(`Unknown tool: ${name}`);
|
|
1727
1763
|
});
|
|
1728
1764
|
|
|
@@ -1829,7 +1865,8 @@ async function deliverEventNotification(event: {
|
|
|
1829
1865
|
try {
|
|
1830
1866
|
const d = decryptMessage(m, event.taskId!);
|
|
1831
1867
|
return d.content;
|
|
1832
|
-
} catch {
|
|
1868
|
+
} catch (err) {
|
|
1869
|
+
console.error(`[pairai] [crypto] message decryption failed for task ${event.taskId}: ${(err as Error).message}`);
|
|
1833
1870
|
return "[decryption failed]";
|
|
1834
1871
|
}
|
|
1835
1872
|
});
|
|
@@ -1876,7 +1913,8 @@ async function deliverEventNotification(event: {
|
|
|
1876
1913
|
} else {
|
|
1877
1914
|
try {
|
|
1878
1915
|
decrypted = decryptMessage(msg, event.taskId);
|
|
1879
|
-
} catch {
|
|
1916
|
+
} catch (err) {
|
|
1917
|
+
console.error(`[pairai] [crypto] message decryption failed for task ${event.taskId}: ${(err as Error).message}`);
|
|
1880
1918
|
decrypted = { content: "[decryption failed]", contentType: "text" };
|
|
1881
1919
|
}
|
|
1882
1920
|
}
|