pairai 0.2.0 → 0.2.1
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/bin.js +1 -1
- package/package.json +3 -3
- package/{pairai-channel.ts → pairai.ts} +132 -25
package/bin.js
CHANGED
|
@@ -9,7 +9,7 @@ const require = createRequire(import.meta.url);
|
|
|
9
9
|
|
|
10
10
|
// Resolve tsx from this package's node_modules as a file:// URL
|
|
11
11
|
const tsxPath = pathToFileURL(require.resolve("tsx")).href;
|
|
12
|
-
const script = join(__dirname, "pairai
|
|
12
|
+
const script = join(__dirname, "pairai.ts");
|
|
13
13
|
|
|
14
14
|
const result = spawnSync(process.execPath, ["--import", tsxPath, script, ...process.argv.slice(2)], {
|
|
15
15
|
stdio: "inherit",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pairai",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "pairai CLI — connect AI agents to collaborate via the pairai hub",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://pairai.pro",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"bin.js",
|
|
26
|
-
"pairai
|
|
26
|
+
"pairai.ts",
|
|
27
27
|
"README.md"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env npx tsx
|
|
2
2
|
/**
|
|
3
|
-
* pairai — connect AI agents via the pairai hub
|
|
3
|
+
* pairai CLI — connect AI agents via the pairai hub
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Commands:
|
|
6
6
|
* npx pairai setup "Agent Name" [--hub URL] [--provider claude|gemini] [--global]
|
|
7
|
-
*
|
|
8
|
-
* Runtime (spawned by Claude Code / Gemini CLI, not called manually):
|
|
9
7
|
* npx pairai serve [--provider claude|gemini]
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* npx pairai upgrade — update to latest version (preserves keys and config)
|
|
9
|
+
* npx pairai version — show current version
|
|
10
|
+
*
|
|
11
|
+
* Env: PAIRAI_HUB_URL, PAIRAI_AGENT_CRED, PAIRAI_KEY_FILE, PAIRAI_POLL_MS
|
|
12
|
+
* Legacy: PAIRAI_URL, PAIRAI_API_KEY, PAIRAI_PRIVATE_KEY_PATH
|
|
12
13
|
*/
|
|
13
14
|
import { execSync } from "node:child_process";
|
|
14
15
|
import {
|
|
@@ -18,11 +19,67 @@ import {
|
|
|
18
19
|
} from "node:crypto";
|
|
19
20
|
import { writeFileSync, mkdirSync, readFileSync, statSync, existsSync } from "node:fs";
|
|
20
21
|
import { homedir } from "node:os";
|
|
21
|
-
import { join } from "node:path";
|
|
22
|
+
import { join, dirname } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const PKG = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf-8"));
|
|
27
|
+
const VERSION: string = PKG.version;
|
|
22
28
|
|
|
23
29
|
const args = process.argv.slice(2);
|
|
24
30
|
const command = args[0];
|
|
25
31
|
|
|
32
|
+
// ── Version ─────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
if (command === "version" || args.includes("--version") || args.includes("-v")) {
|
|
35
|
+
console.log(`pairai v${VERSION}`);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Upgrade ─────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
if (command === "upgrade") {
|
|
42
|
+
console.log(`\n Current version: v${VERSION}`);
|
|
43
|
+
console.log(` Checking for updates...\n`);
|
|
44
|
+
try {
|
|
45
|
+
const latest = execSync("npm view pairai version", { encoding: "utf-8" }).trim();
|
|
46
|
+
if (latest === VERSION) {
|
|
47
|
+
console.log(` Already on latest version (v${VERSION}).\n`);
|
|
48
|
+
} else {
|
|
49
|
+
console.log(` New version available: v${latest}`);
|
|
50
|
+
console.log(` Upgrading...\n`);
|
|
51
|
+
execSync("npm install -g pairai@latest", { stdio: "inherit" });
|
|
52
|
+
console.log(`\n Upgraded to v${latest}.`);
|
|
53
|
+
console.log(` Keys and config are unchanged.\n`);
|
|
54
|
+
|
|
55
|
+
// Update pinned version in config files
|
|
56
|
+
const configPaths = [
|
|
57
|
+
join(process.cwd(), ".mcp.json"),
|
|
58
|
+
join(process.cwd(), ".gemini", "settings.json"),
|
|
59
|
+
join(homedir(), ".gemini", "settings.json"),
|
|
60
|
+
];
|
|
61
|
+
for (const p of configPaths) {
|
|
62
|
+
try {
|
|
63
|
+
if (!existsSync(p)) continue;
|
|
64
|
+
const content = readFileSync(p, "utf-8");
|
|
65
|
+
const updated = content.replace(
|
|
66
|
+
new RegExp(`pairai@${VERSION.replace(/\./g, "\\.")}`, "g"),
|
|
67
|
+
`pairai@${latest}`,
|
|
68
|
+
);
|
|
69
|
+
if (updated !== content) {
|
|
70
|
+
writeFileSync(p, updated);
|
|
71
|
+
console.log(` Updated version in ${p}`);
|
|
72
|
+
}
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error(` Upgrade failed: ${(err as Error).message}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
|
|
26
83
|
function detectProvider(): "claude" | "gemini" {
|
|
27
84
|
if (process.env.GEMINI_CLI) return "gemini";
|
|
28
85
|
try { if (statSync(".gemini").isDirectory()) return "gemini"; } catch {}
|
|
@@ -75,7 +132,20 @@ if (command === "setup") {
|
|
|
75
132
|
mkdirSync(keyDir, { recursive: true });
|
|
76
133
|
const keyPath = join(keyDir, `${id}.pem`);
|
|
77
134
|
writeFileSync(keyPath, privateKey, { mode: 0o600 });
|
|
78
|
-
console.log(` Private key: ${keyPath}
|
|
135
|
+
console.log(` Private key: ${keyPath}`);
|
|
136
|
+
console.log();
|
|
137
|
+
console.log(` ┌──────────────────────────────────────────────────────────┐`);
|
|
138
|
+
console.log(` │ BACK UP YOUR PRIVATE KEY │`);
|
|
139
|
+
console.log(` │ │`);
|
|
140
|
+
console.log(` │ ${keyPath.padEnd(56)}│`);
|
|
141
|
+
console.log(` │ │`);
|
|
142
|
+
console.log(` │ This key is stored only on your machine. │`);
|
|
143
|
+
console.log(` │ The hub never sees it. If lost, you must re-register │`);
|
|
144
|
+
console.log(` │ and re-pair — all encrypted history becomes unreadable. │`);
|
|
145
|
+
console.log(` │ │`);
|
|
146
|
+
console.log(` │ Copy it to a password manager or secure backup now. │`);
|
|
147
|
+
console.log(` └──────────────────────────────────────────────────────────┘`);
|
|
148
|
+
console.log();
|
|
79
149
|
|
|
80
150
|
if (provider === "gemini") {
|
|
81
151
|
// Write .gemini/settings.json (project or global)
|
|
@@ -96,7 +166,7 @@ if (command === "setup") {
|
|
|
96
166
|
if (!existing.mcpServers) existing.mcpServers = {};
|
|
97
167
|
existing.mcpServers.pairai = {
|
|
98
168
|
command: "npx",
|
|
99
|
-
args: [
|
|
169
|
+
args: [`pairai@${VERSION}`, "serve"],
|
|
100
170
|
env: {
|
|
101
171
|
PAIRAI_HUB_URL: hubUrl,
|
|
102
172
|
PAIRAI_AGENT_CRED: apiKey,
|
|
@@ -105,19 +175,19 @@ if (command === "setup") {
|
|
|
105
175
|
};
|
|
106
176
|
|
|
107
177
|
writeFileSync(settingsPath, JSON.stringify(existing, null, 2) + "\n");
|
|
108
|
-
console.log(` Config
|
|
109
|
-
console.log(
|
|
178
|
+
console.log(` Config: ${settingsPath}`);
|
|
179
|
+
console.log();
|
|
180
|
+
console.log(` Next steps:`);
|
|
110
181
|
console.log(` 1. Restart Gemini CLI to activate the pairai MCP server`);
|
|
111
182
|
console.log(` 2. Ask Gemini: "Generate a pairing code"`);
|
|
112
|
-
console.log(` 3. Share the code with another agent to connect
|
|
113
|
-
console.log(` E2E encryption is enabled. Back up ${keyPath} — it cannot be recovered.\n`);
|
|
183
|
+
console.log(` 3. Share the code with another agent to connect`);
|
|
114
184
|
} else {
|
|
115
185
|
// Write .mcp.json for Claude Code
|
|
116
186
|
const mcpConfig = {
|
|
117
187
|
mcpServers: {
|
|
118
188
|
"pairai-channel": {
|
|
119
189
|
command: "npx",
|
|
120
|
-
args: [
|
|
190
|
+
args: [`pairai@${VERSION}`, "serve"],
|
|
121
191
|
env: { PAIRAI_AGENT_CRED: apiKey, PAIRAI_HUB_URL: hubUrl, PAIRAI_KEY_FILE: keyPath },
|
|
122
192
|
},
|
|
123
193
|
},
|
|
@@ -126,20 +196,27 @@ if (command === "setup") {
|
|
|
126
196
|
const cwd = process.cwd();
|
|
127
197
|
const mcpJsonPath = join(cwd, ".mcp.json");
|
|
128
198
|
writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
129
|
-
console.log(`
|
|
130
|
-
console.log(
|
|
131
|
-
console.log(`
|
|
199
|
+
console.log(` Config: ${mcpJsonPath}`);
|
|
200
|
+
console.log();
|
|
201
|
+
console.log(` Next steps:`);
|
|
202
|
+
console.log(` 1. Start Claude Code in this directory`);
|
|
203
|
+
console.log(` 2. Ask Claude: "Generate a pairing code"`);
|
|
204
|
+
console.log(` 3. Share the code with another agent to connect`);
|
|
132
205
|
}
|
|
133
206
|
|
|
207
|
+
console.log();
|
|
134
208
|
process.exit(0);
|
|
135
209
|
}
|
|
136
210
|
|
|
137
211
|
// ── Serve: stdio MCP channel server ──────────────────────────────────────────
|
|
138
212
|
|
|
139
213
|
if (command !== "serve") {
|
|
214
|
+
console.error(`pairai v${VERSION}\n`);
|
|
140
215
|
console.error("Usage:");
|
|
141
216
|
console.error(' npx pairai setup "Agent Name" [--hub URL] [--provider claude|gemini] [--global]');
|
|
142
217
|
console.error(" npx pairai serve [--provider claude|gemini]");
|
|
218
|
+
console.error(" npx pairai upgrade — update to latest version");
|
|
219
|
+
console.error(" npx pairai version — show current version");
|
|
143
220
|
process.exit(1);
|
|
144
221
|
}
|
|
145
222
|
|
|
@@ -271,7 +348,8 @@ function localDecrypt(
|
|
|
271
348
|
// ── MCP Server ───────────────────────────────────────────────────────────────
|
|
272
349
|
|
|
273
350
|
const instructions = [
|
|
274
|
-
|
|
351
|
+
"You are connected to the pairai agent hub. Messages from other AI agents arrive as notifications.",
|
|
352
|
+
"The channel server polls for updates automatically — you don't need to poll manually.",
|
|
275
353
|
"",
|
|
276
354
|
"Notification attributes:",
|
|
277
355
|
" task_id — the task this message belongs to",
|
|
@@ -279,10 +357,11 @@ const instructions = [
|
|
|
279
357
|
" from_agent — name of the agent who sent it",
|
|
280
358
|
" event_type — 'new_task' or 'new_message'",
|
|
281
359
|
"",
|
|
282
|
-
"
|
|
283
|
-
"To
|
|
284
|
-
"To
|
|
285
|
-
"To
|
|
360
|
+
"When you receive a notification:",
|
|
361
|
+
" - To respond, use the reply tool with the task_id and your message.",
|
|
362
|
+
" - To accept a task, use the update_status tool with status 'working'.",
|
|
363
|
+
" - To finish a task, use the update_status tool with status 'completed'.",
|
|
364
|
+
" - To ask for more info, use update_status with 'input-required' and send a message.",
|
|
286
365
|
].join("\n");
|
|
287
366
|
|
|
288
367
|
const capabilities = serveProvider === "claude"
|
|
@@ -423,10 +502,38 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
423
502
|
}
|
|
424
503
|
|
|
425
504
|
if (name === "pairai_create_task") {
|
|
505
|
+
const { target_agent_id, title, description } = args as {
|
|
506
|
+
target_agent_id: string; title: string; description?: string;
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// Auto-encrypt when both agents have keys and we have a private key
|
|
510
|
+
await loadPublicKeys();
|
|
511
|
+
const otherPub = pubKeyCache.get(target_agent_id);
|
|
512
|
+
if (PRIVATE_KEY && otherPub && myPublicKey) {
|
|
513
|
+
const { nanoid } = await import("nanoid");
|
|
514
|
+
const taskId = nanoid();
|
|
515
|
+
const payload = JSON.stringify({ title, description: description ?? "" });
|
|
516
|
+
const { ciphertext, signature, encryptedKeys } = localEncrypt(payload, taskId, {
|
|
517
|
+
[myAgentId]: myPublicKey,
|
|
518
|
+
[target_agent_id]: otherPub,
|
|
519
|
+
});
|
|
520
|
+
await hubPost("/tasks", {
|
|
521
|
+
id: taskId,
|
|
522
|
+
targetAgentId: target_agent_id,
|
|
523
|
+
title: "Encrypted Task",
|
|
524
|
+
description: ciphertext,
|
|
525
|
+
encrypted: true,
|
|
526
|
+
descriptionKeys: encryptedKeys,
|
|
527
|
+
senderSignature: signature,
|
|
528
|
+
});
|
|
529
|
+
return { content: [{ type: "text" as const, text: `Task created (encrypted). ID: ${taskId}` }] };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Fallback: plaintext (no keys available)
|
|
426
533
|
const data = await hubPost("/tasks", {
|
|
427
|
-
targetAgentId:
|
|
428
|
-
title
|
|
429
|
-
description
|
|
534
|
+
targetAgentId: target_agent_id,
|
|
535
|
+
title,
|
|
536
|
+
description,
|
|
430
537
|
});
|
|
431
538
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
432
539
|
}
|