pairai 0.2.1 → 0.2.3
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/package.json +1 -1
- package/pairai.ts +158 -16
package/package.json
CHANGED
package/pairai.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* pairai CLI — connect AI agents via the pairai hub
|
|
4
4
|
*
|
|
5
5
|
* Commands:
|
|
6
|
-
* npx pairai setup "Agent Name" [--hub URL] [--provider claude|gemini] [--global]
|
|
6
|
+
* npx pairai setup "Agent Name" [--hub URL] [--provider claude|gemini] [--global] [--force]
|
|
7
7
|
* npx pairai serve [--provider claude|gemini]
|
|
8
8
|
* npx pairai upgrade — update to latest version (preserves keys and config)
|
|
9
9
|
* npx pairai version — show current version
|
|
@@ -93,16 +93,41 @@ if (command === "setup") {
|
|
|
93
93
|
const hubIdx = rest.indexOf("--hub");
|
|
94
94
|
const hubUrl = hubIdx !== -1 ? rest.splice(hubIdx, 2)[1] : "https://pairai.pro";
|
|
95
95
|
const providerIdx = rest.indexOf("--provider");
|
|
96
|
-
const
|
|
96
|
+
const providerArg = providerIdx !== -1 ? rest.splice(providerIdx, 2)[1] : undefined;
|
|
97
|
+
if (providerArg && providerArg !== "claude" && providerArg !== "gemini") {
|
|
98
|
+
console.error(` Unknown provider "${providerArg}". Must be "claude" or "gemini".`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const provider = providerArg ?? detectProvider();
|
|
97
102
|
const globalIdx = rest.indexOf("--global");
|
|
98
103
|
const useGlobal = globalIdx !== -1 ? (rest.splice(globalIdx, 1), true) : false;
|
|
99
104
|
const agentName = rest.find((a) => !a.startsWith("--"));
|
|
100
105
|
|
|
106
|
+
const forceIdx = rest.indexOf("--force");
|
|
107
|
+
const useForce = forceIdx !== -1 ? (rest.splice(forceIdx, 1), true) : false;
|
|
101
108
|
if (!agentName) {
|
|
102
|
-
console.error('Usage: npx pairai setup "Agent Name" [--hub URL] [--provider claude|gemini] [--global]');
|
|
109
|
+
console.error('Usage: npx pairai setup "Agent Name" [--hub URL] [--provider claude|gemini] [--global] [--force]');
|
|
103
110
|
process.exit(1);
|
|
104
111
|
}
|
|
105
112
|
|
|
113
|
+
// Check for existing config to avoid accidental overwrites
|
|
114
|
+
const existingConfigPath = provider === "gemini"
|
|
115
|
+
? join(useGlobal ? join(homedir(), ".gemini") : join(process.cwd(), ".gemini"), "settings.json")
|
|
116
|
+
: join(process.cwd(), ".mcp.json");
|
|
117
|
+
const mcpKey = provider === "gemini" ? "pairai" : "pairai-channel";
|
|
118
|
+
if (!useForce && existsSync(existingConfigPath)) {
|
|
119
|
+
try {
|
|
120
|
+
const existing = JSON.parse(readFileSync(existingConfigPath, "utf-8"));
|
|
121
|
+
const servers = existing.mcpServers ?? {};
|
|
122
|
+
if (servers[mcpKey]) {
|
|
123
|
+
console.error(`\n pairai is already configured in ${existingConfigPath}`);
|
|
124
|
+
console.error(` Running setup again would overwrite the existing API key and config.`);
|
|
125
|
+
console.error(`\n To force a fresh setup, run: npx pairai setup "${agentName}" --force\n`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
} catch {}
|
|
129
|
+
}
|
|
130
|
+
|
|
106
131
|
console.log(`\n Registering "${agentName}" on ${hubUrl}...\n`);
|
|
107
132
|
|
|
108
133
|
console.log(" Generating RSA-4096 keypair...");
|
|
@@ -133,18 +158,22 @@ if (command === "setup") {
|
|
|
133
158
|
const keyPath = join(keyDir, `${id}.pem`);
|
|
134
159
|
writeFileSync(keyPath, privateKey, { mode: 0o600 });
|
|
135
160
|
console.log(` Private key: ${keyPath}`);
|
|
161
|
+
const lines = [
|
|
162
|
+
"BACK UP YOUR PRIVATE KEY",
|
|
163
|
+
"",
|
|
164
|
+
keyPath,
|
|
165
|
+
"",
|
|
166
|
+
"This key is stored only on your machine.",
|
|
167
|
+
"The hub never sees it. If lost, you must re-register",
|
|
168
|
+
"and re-pair — all encrypted history becomes unreadable.",
|
|
169
|
+
"",
|
|
170
|
+
"Copy it to a password manager or secure backup now.",
|
|
171
|
+
];
|
|
172
|
+
const w = Math.max(...lines.map((l) => l.length)) + 2;
|
|
136
173
|
console.log();
|
|
137
|
-
console.log(`
|
|
138
|
-
console.log(` │
|
|
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(` └──────────────────────────────────────────────────────────┘`);
|
|
174
|
+
console.log(` ┌${"─".repeat(w + 2)}┐`);
|
|
175
|
+
for (const l of lines) console.log(` │ ${l.padEnd(w)}│`);
|
|
176
|
+
console.log(` └${"─".repeat(w + 2)}┘`);
|
|
148
177
|
console.log();
|
|
149
178
|
|
|
150
179
|
if (provider === "gemini") {
|
|
@@ -213,7 +242,7 @@ if (command === "setup") {
|
|
|
213
242
|
if (command !== "serve") {
|
|
214
243
|
console.error(`pairai v${VERSION}\n`);
|
|
215
244
|
console.error("Usage:");
|
|
216
|
-
console.error(' npx pairai setup "Agent Name" [--hub URL] [--provider claude|gemini] [--global]');
|
|
245
|
+
console.error(' npx pairai setup "Agent Name" [--hub URL] [--provider claude|gemini] [--global] [--force]');
|
|
217
246
|
console.error(" npx pairai serve [--provider claude|gemini]");
|
|
218
247
|
console.error(" npx pairai upgrade — update to latest version");
|
|
219
248
|
console.error(" npx pairai version — show current version");
|
|
@@ -226,7 +255,12 @@ const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelco
|
|
|
226
255
|
|
|
227
256
|
const serveArgs = args.slice(1);
|
|
228
257
|
const serveProviderIdx = serveArgs.indexOf("--provider");
|
|
229
|
-
const
|
|
258
|
+
const serveProviderArg = serveProviderIdx !== -1 ? serveArgs[serveProviderIdx + 1] : undefined;
|
|
259
|
+
if (serveProviderArg && serveProviderArg !== "claude" && serveProviderArg !== "gemini") {
|
|
260
|
+
console.error(` Unknown provider "${serveProviderArg}". Must be "claude" or "gemini".`);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
const serveProvider = serveProviderArg ?? "claude";
|
|
230
264
|
|
|
231
265
|
const HUB_URL = process.env.PAIRAI_HUB_URL ?? process.env.PAIRAI_URL ?? "https://pairai.pro";
|
|
232
266
|
const API_KEY = process.env.PAIRAI_AGENT_CRED ?? process.env.PAIRAI_API_KEY;
|
|
@@ -402,6 +436,11 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
402
436
|
required: ["task_id", "status"],
|
|
403
437
|
},
|
|
404
438
|
},
|
|
439
|
+
{
|
|
440
|
+
name: "pairai_get_profile",
|
|
441
|
+
description: "Get your own agent profile — name, ID, description, capabilities.",
|
|
442
|
+
inputSchema: { type: "object" as const, properties: {} },
|
|
443
|
+
},
|
|
405
444
|
{
|
|
406
445
|
name: "pairai_list_connections",
|
|
407
446
|
description: "List agents you are connected with.",
|
|
@@ -436,6 +475,66 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
436
475
|
required: ["code"],
|
|
437
476
|
},
|
|
438
477
|
},
|
|
478
|
+
{
|
|
479
|
+
name: "pairai_update_profile",
|
|
480
|
+
description: "Update your agent's profile — name, description, capabilities, and metadata. Returns the updated profile.",
|
|
481
|
+
inputSchema: {
|
|
482
|
+
type: "object" as const,
|
|
483
|
+
properties: {
|
|
484
|
+
name: { type: "string", description: "Display name (1-64 chars)" },
|
|
485
|
+
description: { type: "string", description: "What this agent does (max 500 chars)" },
|
|
486
|
+
capabilities: { type: "array", items: { type: "string" }, description: "List of capabilities, e.g. ['scheduling', 'code-review']" },
|
|
487
|
+
metadata: { type: "object", description: "Arbitrary JSON metadata (max 4KB)" },
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "pairai_set_alias",
|
|
493
|
+
description: "Set a local alias for a connected agent. Only you see this alias. Set to null to clear.",
|
|
494
|
+
inputSchema: {
|
|
495
|
+
type: "object" as const,
|
|
496
|
+
properties: {
|
|
497
|
+
connection_id: { type: "string", description: "Connection ID" },
|
|
498
|
+
alias: { type: ["string", "null"], description: "Local alias, or null to clear" },
|
|
499
|
+
},
|
|
500
|
+
required: ["connection_id"],
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
name: "pairai_list_tasks",
|
|
505
|
+
description: "List all tasks you are involved in (as initiator or target).",
|
|
506
|
+
inputSchema: {
|
|
507
|
+
type: "object" as const,
|
|
508
|
+
properties: {
|
|
509
|
+
status: { type: "string", enum: ["submitted", "working", "input-required", "completed", "failed", "cancelled"], description: "Filter by status" },
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "pairai_get_task",
|
|
515
|
+
description: "Get full details of a task including all messages.",
|
|
516
|
+
inputSchema: {
|
|
517
|
+
type: "object" as const,
|
|
518
|
+
properties: {
|
|
519
|
+
task_id: { type: "string", description: "Task ID" },
|
|
520
|
+
},
|
|
521
|
+
required: ["task_id"],
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: "pairai_upload_file",
|
|
526
|
+
description: "Upload a file to a task. Provide base64-encoded content.",
|
|
527
|
+
inputSchema: {
|
|
528
|
+
type: "object" as const,
|
|
529
|
+
properties: {
|
|
530
|
+
task_id: { type: "string", description: "Task ID" },
|
|
531
|
+
filename: { type: "string", description: "Original filename, e.g. photo.png" },
|
|
532
|
+
mime_type: { type: "string", description: "MIME type, e.g. image/png" },
|
|
533
|
+
base64_content: { type: "string", description: "Base64-encoded file content" },
|
|
534
|
+
},
|
|
535
|
+
required: ["task_id", "filename", "mime_type", "base64_content"],
|
|
536
|
+
},
|
|
537
|
+
},
|
|
439
538
|
{
|
|
440
539
|
name: "pairai_create_encrypted_task",
|
|
441
540
|
description: "Create an encrypted task. Title and description are encrypted — the hub cannot read them.",
|
|
@@ -496,6 +595,11 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
496
595
|
return { content: [{ type: "text" as const, text: `Status → ${args.status}` }] };
|
|
497
596
|
}
|
|
498
597
|
|
|
598
|
+
if (name === "pairai_get_profile") {
|
|
599
|
+
const data = await hubGet("/agents/me");
|
|
600
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
601
|
+
}
|
|
602
|
+
|
|
499
603
|
if (name === "pairai_list_connections") {
|
|
500
604
|
const data = await hubGet("/connections");
|
|
501
605
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
@@ -550,6 +654,43 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
550
654
|
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
551
655
|
}
|
|
552
656
|
|
|
657
|
+
if (name === "pairai_update_profile") {
|
|
658
|
+
const body: Record<string, unknown> = {};
|
|
659
|
+
if (args.name !== undefined) body.name = args.name;
|
|
660
|
+
if (args.description !== undefined) body.description = args.description;
|
|
661
|
+
if (args.capabilities !== undefined) body.capabilities = args.capabilities;
|
|
662
|
+
if (args.metadata !== undefined) body.metadata = args.metadata;
|
|
663
|
+
const data = await hubPatch("/agents/me", body);
|
|
664
|
+
return { content: [{ type: "text" as const, text: "Profile updated.\n" + JSON.stringify(data, null, 2) }] };
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (name === "pairai_set_alias") {
|
|
668
|
+
const { connection_id, alias } = args as { connection_id: string; alias?: string | null };
|
|
669
|
+
const data = await hubPatch(`/connections/${connection_id}`, { alias: alias ?? null });
|
|
670
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (name === "pairai_list_tasks") {
|
|
674
|
+
const data = (await hubGet("/tasks")) as Array<{ status: string }>;
|
|
675
|
+
const filtered = args.status ? data.filter((t) => t.status === args.status) : data;
|
|
676
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(filtered, null, 2) }] };
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (name === "pairai_get_task") {
|
|
680
|
+
const data = await hubGet(`/tasks/${args.task_id}`);
|
|
681
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (name === "pairai_upload_file") {
|
|
685
|
+
const { task_id, filename, mime_type, base64_content } = args as {
|
|
686
|
+
task_id: string; filename: string; mime_type: string; base64_content: string;
|
|
687
|
+
};
|
|
688
|
+
const data = await hubPost(`/tasks/${task_id}/files/json`, {
|
|
689
|
+
filename, mimeType: mime_type, base64Content: base64_content,
|
|
690
|
+
});
|
|
691
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
|
|
692
|
+
}
|
|
693
|
+
|
|
553
694
|
if (name === "pairai_create_encrypted_task") {
|
|
554
695
|
if (!PRIVATE_KEY)
|
|
555
696
|
return { content: [{ type: "text" as const, text: "No private key configured. Re-run setup." }] };
|
|
@@ -701,6 +842,7 @@ async function poll() {
|
|
|
701
842
|
}>;
|
|
702
843
|
};
|
|
703
844
|
|
|
845
|
+
if (!full?.messages) continue;
|
|
704
846
|
for (const msg of full.messages.slice(-unread.count)) {
|
|
705
847
|
const key = `msg:${msg.id}`;
|
|
706
848
|
if (seenMessages.has(key)) continue;
|