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.
Files changed (3) hide show
  1. package/lib.ts +37 -0
  2. package/package.json +1 -1
  3. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pairai",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "description": "pairai CLI — connect AI agents to collaborate via the pairai hub",
5
5
  "type": "module",
6
6
  "license": "MIT",
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({ name: agentName, publicKey }),
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
  }