@picahq/cli 1.7.0 → 1.8.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/index.js +420 -195
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { createRequire } from "module";
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/init.ts
|
|
8
|
-
import * as
|
|
9
|
-
import
|
|
8
|
+
import * as p2 from "@clack/prompts";
|
|
9
|
+
import pc3 from "picocolors";
|
|
10
10
|
|
|
11
11
|
// src/lib/config.ts
|
|
12
12
|
import fs from "fs";
|
|
@@ -41,17 +41,43 @@ function getApiKey() {
|
|
|
41
41
|
const config = readConfig();
|
|
42
42
|
return config?.apiKey ?? null;
|
|
43
43
|
}
|
|
44
|
+
function getAccessControl() {
|
|
45
|
+
return readConfig()?.accessControl ?? {};
|
|
46
|
+
}
|
|
47
|
+
function updateAccessControl(settings) {
|
|
48
|
+
const config = readConfig();
|
|
49
|
+
if (!config) return;
|
|
50
|
+
const cleaned = {};
|
|
51
|
+
if (settings.permissions && settings.permissions !== "admin") {
|
|
52
|
+
cleaned.permissions = settings.permissions;
|
|
53
|
+
}
|
|
54
|
+
if (settings.connectionKeys && !(settings.connectionKeys.length === 1 && settings.connectionKeys[0] === "*")) {
|
|
55
|
+
cleaned.connectionKeys = settings.connectionKeys;
|
|
56
|
+
}
|
|
57
|
+
if (settings.actionIds && !(settings.actionIds.length === 1 && settings.actionIds[0] === "*")) {
|
|
58
|
+
cleaned.actionIds = settings.actionIds;
|
|
59
|
+
}
|
|
60
|
+
if (settings.knowledgeAgent) {
|
|
61
|
+
cleaned.knowledgeAgent = true;
|
|
62
|
+
}
|
|
63
|
+
if (Object.keys(cleaned).length === 0) {
|
|
64
|
+
delete config.accessControl;
|
|
65
|
+
} else {
|
|
66
|
+
config.accessControl = cleaned;
|
|
67
|
+
}
|
|
68
|
+
writeConfig(config);
|
|
69
|
+
}
|
|
44
70
|
|
|
45
71
|
// src/lib/agents.ts
|
|
46
72
|
import fs2 from "fs";
|
|
47
73
|
import path2 from "path";
|
|
48
74
|
import os2 from "os";
|
|
49
75
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
50
|
-
function expandPath(
|
|
51
|
-
if (
|
|
52
|
-
return path2.join(os2.homedir(),
|
|
76
|
+
function expandPath(p5) {
|
|
77
|
+
if (p5.startsWith("~/")) {
|
|
78
|
+
return path2.join(os2.homedir(), p5.slice(2));
|
|
53
79
|
}
|
|
54
|
-
return
|
|
80
|
+
return p5;
|
|
55
81
|
}
|
|
56
82
|
function getClaudeDesktopConfigPath() {
|
|
57
83
|
switch (process.platform) {
|
|
@@ -179,20 +205,35 @@ function writeAgentConfig(agent, config, scope = "global") {
|
|
|
179
205
|
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
180
206
|
}
|
|
181
207
|
}
|
|
182
|
-
function getMcpServerConfig(apiKey) {
|
|
208
|
+
function getMcpServerConfig(apiKey, accessControl) {
|
|
209
|
+
const env = {
|
|
210
|
+
PICA_SECRET: apiKey
|
|
211
|
+
};
|
|
212
|
+
if (accessControl) {
|
|
213
|
+
if (accessControl.permissions && accessControl.permissions !== "admin") {
|
|
214
|
+
env.PICA_PERMISSIONS = accessControl.permissions;
|
|
215
|
+
}
|
|
216
|
+
if (accessControl.connectionKeys && !(accessControl.connectionKeys.length === 1 && accessControl.connectionKeys[0] === "*")) {
|
|
217
|
+
env.PICA_CONNECTION_KEYS = accessControl.connectionKeys.join(",");
|
|
218
|
+
}
|
|
219
|
+
if (accessControl.actionIds && !(accessControl.actionIds.length === 1 && accessControl.actionIds[0] === "*")) {
|
|
220
|
+
env.PICA_ACTION_IDS = accessControl.actionIds.join(",");
|
|
221
|
+
}
|
|
222
|
+
if (accessControl.knowledgeAgent) {
|
|
223
|
+
env.PICA_KNOWLEDGE_AGENT = "true";
|
|
224
|
+
}
|
|
225
|
+
}
|
|
183
226
|
return {
|
|
184
227
|
command: "npx",
|
|
185
228
|
args: ["-y", "@picahq/mcp"],
|
|
186
|
-
env
|
|
187
|
-
PICA_SECRET: apiKey
|
|
188
|
-
}
|
|
229
|
+
env
|
|
189
230
|
};
|
|
190
231
|
}
|
|
191
|
-
function installMcpConfig(agent, apiKey, scope = "global") {
|
|
232
|
+
function installMcpConfig(agent, apiKey, scope = "global", accessControl) {
|
|
192
233
|
const config = readAgentConfig(agent, scope);
|
|
193
234
|
const configKey = agent.configKey;
|
|
194
235
|
const mcpServers = config[configKey] || {};
|
|
195
|
-
mcpServers["pica"] = getMcpServerConfig(apiKey);
|
|
236
|
+
mcpServers["pica"] = getMcpServerConfig(apiKey, accessControl);
|
|
196
237
|
config[configKey] = mcpServers;
|
|
197
238
|
writeAgentConfig(agent, config, scope);
|
|
198
239
|
}
|
|
@@ -247,8 +288,8 @@ var PicaApi = class {
|
|
|
247
288
|
}
|
|
248
289
|
const response = await fetch(url, fetchOpts);
|
|
249
290
|
if (!response.ok) {
|
|
250
|
-
const
|
|
251
|
-
throw new ApiError(response.status,
|
|
291
|
+
const text4 = await response.text();
|
|
292
|
+
throw new ApiError(response.status, text4 || `HTTP ${response.status}`);
|
|
252
293
|
}
|
|
253
294
|
return response.json();
|
|
254
295
|
}
|
|
@@ -324,11 +365,171 @@ async function openApiKeyPage() {
|
|
|
324
365
|
await open(getApiKeyUrl());
|
|
325
366
|
}
|
|
326
367
|
|
|
368
|
+
// src/commands/config.ts
|
|
369
|
+
import * as p from "@clack/prompts";
|
|
370
|
+
import pc from "picocolors";
|
|
371
|
+
async function configCommand() {
|
|
372
|
+
const config = readConfig();
|
|
373
|
+
if (!config) {
|
|
374
|
+
p.log.error(`No Pica config found. Run ${pc.cyan("pica init")} first.`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
p.intro(pc.bgCyan(pc.black(" Pica Access Control ")));
|
|
378
|
+
const current = getAccessControl();
|
|
379
|
+
console.log();
|
|
380
|
+
console.log(` ${pc.bold("Current Access Control")}`);
|
|
381
|
+
console.log(` ${pc.dim("\u2500".repeat(42))}`);
|
|
382
|
+
console.log(` ${pc.dim("Permissions:")} ${current.permissions ?? "admin"}`);
|
|
383
|
+
console.log(` ${pc.dim("Connections:")} ${formatList(current.connectionKeys)}`);
|
|
384
|
+
console.log(` ${pc.dim("Action IDs:")} ${formatList(current.actionIds)}`);
|
|
385
|
+
console.log(` ${pc.dim("Knowledge only:")} ${current.knowledgeAgent ? "yes" : "no"}`);
|
|
386
|
+
console.log();
|
|
387
|
+
const permissions = await p.select({
|
|
388
|
+
message: "Permission level",
|
|
389
|
+
options: [
|
|
390
|
+
{ value: "admin", label: "Admin", hint: "Full access (GET, POST, PUT, PATCH, DELETE)" },
|
|
391
|
+
{ value: "write", label: "Write", hint: "GET, POST, PUT, PATCH" },
|
|
392
|
+
{ value: "read", label: "Read", hint: "GET only" }
|
|
393
|
+
],
|
|
394
|
+
initialValue: current.permissions ?? "admin"
|
|
395
|
+
});
|
|
396
|
+
if (p.isCancel(permissions)) {
|
|
397
|
+
p.outro("No changes made.");
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const connectionMode = await p.select({
|
|
401
|
+
message: "Connection scope",
|
|
402
|
+
options: [
|
|
403
|
+
{ value: "all", label: "All connections" },
|
|
404
|
+
{ value: "specific", label: "Select specific connections" }
|
|
405
|
+
],
|
|
406
|
+
initialValue: current.connectionKeys ? "specific" : "all"
|
|
407
|
+
});
|
|
408
|
+
if (p.isCancel(connectionMode)) {
|
|
409
|
+
p.outro("No changes made.");
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
let connectionKeys;
|
|
413
|
+
if (connectionMode === "specific") {
|
|
414
|
+
connectionKeys = await selectConnections(config.apiKey);
|
|
415
|
+
if (connectionKeys === void 0) {
|
|
416
|
+
p.outro("No changes made.");
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (connectionKeys.length === 0) {
|
|
420
|
+
p.log.info(`No connections found. Defaulting to all. Use ${pc.cyan("pica add")} to connect platforms.`);
|
|
421
|
+
connectionKeys = void 0;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const actionMode = await p.select({
|
|
425
|
+
message: "Action scope",
|
|
426
|
+
options: [
|
|
427
|
+
{ value: "all", label: "All actions" },
|
|
428
|
+
{ value: "specific", label: "Restrict to specific action IDs" }
|
|
429
|
+
],
|
|
430
|
+
initialValue: current.actionIds ? "specific" : "all"
|
|
431
|
+
});
|
|
432
|
+
if (p.isCancel(actionMode)) {
|
|
433
|
+
p.outro("No changes made.");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
let actionIds;
|
|
437
|
+
if (actionMode === "specific") {
|
|
438
|
+
const actionInput = await p.text({
|
|
439
|
+
message: "Enter action IDs (comma-separated):",
|
|
440
|
+
placeholder: "action-id-1, action-id-2",
|
|
441
|
+
initialValue: current.actionIds?.join(", ") ?? "",
|
|
442
|
+
validate: (value) => {
|
|
443
|
+
if (!value.trim()) return "At least one action ID is required";
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
if (p.isCancel(actionInput)) {
|
|
448
|
+
p.outro("No changes made.");
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
actionIds = actionInput.split(",").map((s) => s.trim()).filter(Boolean);
|
|
452
|
+
}
|
|
453
|
+
const knowledgeAgent = await p.confirm({
|
|
454
|
+
message: "Enable knowledge-only mode? (disables action execution)",
|
|
455
|
+
initialValue: current.knowledgeAgent ?? false
|
|
456
|
+
});
|
|
457
|
+
if (p.isCancel(knowledgeAgent)) {
|
|
458
|
+
p.outro("No changes made.");
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const settings = {
|
|
462
|
+
permissions,
|
|
463
|
+
connectionKeys: connectionKeys ?? ["*"],
|
|
464
|
+
actionIds: actionIds ?? ["*"],
|
|
465
|
+
knowledgeAgent
|
|
466
|
+
};
|
|
467
|
+
updateAccessControl(settings);
|
|
468
|
+
const ac = getAccessControl();
|
|
469
|
+
const statuses = getAgentStatuses();
|
|
470
|
+
const reinstalled = [];
|
|
471
|
+
for (const s of statuses) {
|
|
472
|
+
if (s.globalMcp) {
|
|
473
|
+
installMcpConfig(s.agent, config.apiKey, "global", ac);
|
|
474
|
+
reinstalled.push(`${s.agent.name} (global)`);
|
|
475
|
+
}
|
|
476
|
+
if (s.projectMcp) {
|
|
477
|
+
installMcpConfig(s.agent, config.apiKey, "project", ac);
|
|
478
|
+
reinstalled.push(`${s.agent.name} (project)`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (reinstalled.length > 0) {
|
|
482
|
+
p.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
|
|
483
|
+
}
|
|
484
|
+
p.outro("Access control updated.");
|
|
485
|
+
}
|
|
486
|
+
async function selectConnections(apiKey) {
|
|
487
|
+
const spinner5 = p.spinner();
|
|
488
|
+
spinner5.start("Fetching connections...");
|
|
489
|
+
let connections;
|
|
490
|
+
try {
|
|
491
|
+
const api = new PicaApi(apiKey);
|
|
492
|
+
const rawConnections = await api.listConnections();
|
|
493
|
+
connections = rawConnections.map((c) => ({ platform: c.platform, key: c.key }));
|
|
494
|
+
spinner5.stop(`Found ${connections.length} connection(s)`);
|
|
495
|
+
} catch {
|
|
496
|
+
spinner5.stop("Could not fetch connections");
|
|
497
|
+
const manual = await p.text({
|
|
498
|
+
message: "Enter connection keys manually (comma-separated):",
|
|
499
|
+
placeholder: "conn_key_1, conn_key_2",
|
|
500
|
+
validate: (value) => {
|
|
501
|
+
if (!value.trim()) return "At least one connection key is required";
|
|
502
|
+
return void 0;
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
if (p.isCancel(manual)) return void 0;
|
|
506
|
+
return manual.split(",").map((s) => s.trim()).filter(Boolean);
|
|
507
|
+
}
|
|
508
|
+
if (connections.length === 0) {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
const selected = await p.multiselect({
|
|
512
|
+
message: "Select connections:",
|
|
513
|
+
options: connections.map((c) => ({
|
|
514
|
+
value: c.key,
|
|
515
|
+
label: `${c.platform}`,
|
|
516
|
+
hint: c.key
|
|
517
|
+
}))
|
|
518
|
+
});
|
|
519
|
+
if (p.isCancel(selected)) return void 0;
|
|
520
|
+
return selected;
|
|
521
|
+
}
|
|
522
|
+
function formatList(list) {
|
|
523
|
+
if (!list || list.length === 0) return "all";
|
|
524
|
+
if (list.length === 1 && list[0] === "*") return "all";
|
|
525
|
+
return list.join(", ");
|
|
526
|
+
}
|
|
527
|
+
|
|
327
528
|
// src/commands/init.ts
|
|
328
529
|
import open2 from "open";
|
|
329
530
|
|
|
330
531
|
// src/lib/table.ts
|
|
331
|
-
import
|
|
532
|
+
import pc2 from "picocolors";
|
|
332
533
|
function printTable(columns, rows) {
|
|
333
534
|
if (rows.length === 0) return;
|
|
334
535
|
const gap = " ";
|
|
@@ -340,10 +541,10 @@ function printTable(columns, rows) {
|
|
|
340
541
|
});
|
|
341
542
|
const header = columns.map((col, i) => {
|
|
342
543
|
const padded = col.align === "right" ? col.label.padStart(widths[i]) : col.label.padEnd(widths[i]);
|
|
343
|
-
return
|
|
544
|
+
return pc2.dim(padded);
|
|
344
545
|
}).join(gap);
|
|
345
546
|
console.log(`${indent}${header}`);
|
|
346
|
-
const separator = columns.map((_, i) =>
|
|
547
|
+
const separator = columns.map((_, i) => pc2.dim("\u2500".repeat(widths[i]))).join(gap);
|
|
347
548
|
console.log(`${indent}${separator}`);
|
|
348
549
|
for (const row of rows) {
|
|
349
550
|
const line = columns.map((col, i) => {
|
|
@@ -367,7 +568,7 @@ function stripAnsi(str) {
|
|
|
367
568
|
async function initCommand(options) {
|
|
368
569
|
const existingConfig = readConfig();
|
|
369
570
|
if (existingConfig) {
|
|
370
|
-
|
|
571
|
+
p2.intro(pc3.bgCyan(pc3.black(" Pica ")));
|
|
371
572
|
await handleExistingConfig(existingConfig.apiKey, options);
|
|
372
573
|
return;
|
|
373
574
|
}
|
|
@@ -378,10 +579,10 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
378
579
|
const statuses = getAgentStatuses();
|
|
379
580
|
const masked = maskApiKey(apiKey);
|
|
380
581
|
console.log();
|
|
381
|
-
console.log(` ${
|
|
382
|
-
console.log(` ${
|
|
383
|
-
console.log(` ${
|
|
384
|
-
console.log(` ${
|
|
582
|
+
console.log(` ${pc3.bold("Current Setup")}`);
|
|
583
|
+
console.log(` ${pc3.dim("\u2500".repeat(42))}`);
|
|
584
|
+
console.log(` ${pc3.dim("API Key:")} ${masked}`);
|
|
585
|
+
console.log(` ${pc3.dim("Config:")} ${getConfigPath()}`);
|
|
385
586
|
console.log();
|
|
386
587
|
printTable(
|
|
387
588
|
[
|
|
@@ -391,15 +592,24 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
391
592
|
],
|
|
392
593
|
statuses.map((s) => ({
|
|
393
594
|
agent: s.agent.name,
|
|
394
|
-
global: !s.detected ?
|
|
395
|
-
project: s.projectMcp === null ?
|
|
595
|
+
global: !s.detected ? pc3.dim("-") : s.globalMcp ? pc3.green("\u25CF yes") : pc3.yellow("\u25CB no"),
|
|
596
|
+
project: s.projectMcp === null ? pc3.dim("-") : s.projectMcp ? pc3.green("\u25CF yes") : pc3.yellow("\u25CB no")
|
|
396
597
|
}))
|
|
397
598
|
);
|
|
398
599
|
const notDetected = statuses.filter((s) => !s.detected);
|
|
399
600
|
if (notDetected.length > 0) {
|
|
400
|
-
console.log(` ${
|
|
601
|
+
console.log(` ${pc3.dim("- = not detected on this machine")}`);
|
|
602
|
+
}
|
|
603
|
+
const ac = getAccessControl();
|
|
604
|
+
if (Object.keys(ac).length > 0) {
|
|
605
|
+
console.log(` ${pc3.bold("Access Control")}`);
|
|
606
|
+
console.log(` ${pc3.dim("\u2500".repeat(42))}`);
|
|
607
|
+
if (ac.permissions) console.log(` ${pc3.dim("Permissions:")} ${ac.permissions}`);
|
|
608
|
+
if (ac.connectionKeys) console.log(` ${pc3.dim("Connections:")} ${ac.connectionKeys.join(", ")}`);
|
|
609
|
+
if (ac.actionIds) console.log(` ${pc3.dim("Action IDs:")} ${ac.actionIds.join(", ")}`);
|
|
610
|
+
if (ac.knowledgeAgent) console.log(` ${pc3.dim("Knowledge only:")} yes`);
|
|
611
|
+
console.log();
|
|
401
612
|
}
|
|
402
|
-
console.log();
|
|
403
613
|
const actionOptions = [];
|
|
404
614
|
actionOptions.push({
|
|
405
615
|
value: "update-key",
|
|
@@ -421,16 +631,21 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
421
631
|
hint: agentsMissingProject.map((s) => s.agent.name).join(", ")
|
|
422
632
|
});
|
|
423
633
|
}
|
|
634
|
+
actionOptions.push({
|
|
635
|
+
value: "access-control",
|
|
636
|
+
label: "Configure access control",
|
|
637
|
+
hint: "permissions, connections, actions"
|
|
638
|
+
});
|
|
424
639
|
actionOptions.push({
|
|
425
640
|
value: "start-fresh",
|
|
426
641
|
label: "Start fresh (reconfigure everything)"
|
|
427
642
|
});
|
|
428
|
-
const action = await
|
|
643
|
+
const action = await p2.select({
|
|
429
644
|
message: "What would you like to do?",
|
|
430
645
|
options: actionOptions
|
|
431
646
|
});
|
|
432
|
-
if (
|
|
433
|
-
|
|
647
|
+
if (p2.isCancel(action)) {
|
|
648
|
+
p2.outro("No changes made.");
|
|
434
649
|
return;
|
|
435
650
|
}
|
|
436
651
|
switch (action) {
|
|
@@ -443,26 +658,29 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
443
658
|
case "install-project":
|
|
444
659
|
await handleInstallProject(apiKey, agentsMissingProject);
|
|
445
660
|
break;
|
|
661
|
+
case "access-control":
|
|
662
|
+
await configCommand();
|
|
663
|
+
break;
|
|
446
664
|
case "start-fresh":
|
|
447
665
|
await freshSetup({ yes: true });
|
|
448
666
|
break;
|
|
449
667
|
}
|
|
450
668
|
}
|
|
451
669
|
async function handleUpdateKey(statuses) {
|
|
452
|
-
|
|
453
|
-
${
|
|
454
|
-
const openBrowser = await
|
|
670
|
+
p2.note(`Get your API key at:
|
|
671
|
+
${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
672
|
+
const openBrowser = await p2.confirm({
|
|
455
673
|
message: "Open browser to get API key?",
|
|
456
674
|
initialValue: true
|
|
457
675
|
});
|
|
458
|
-
if (
|
|
459
|
-
|
|
676
|
+
if (p2.isCancel(openBrowser)) {
|
|
677
|
+
p2.cancel("Cancelled.");
|
|
460
678
|
process.exit(0);
|
|
461
679
|
}
|
|
462
680
|
if (openBrowser) {
|
|
463
681
|
await openApiKeyPage();
|
|
464
682
|
}
|
|
465
|
-
const newKey = await
|
|
683
|
+
const newKey = await p2.text({
|
|
466
684
|
message: "Enter your new Pica API key:",
|
|
467
685
|
placeholder: "sk_live_...",
|
|
468
686
|
validate: (value) => {
|
|
@@ -473,28 +691,29 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
473
691
|
return void 0;
|
|
474
692
|
}
|
|
475
693
|
});
|
|
476
|
-
if (
|
|
477
|
-
|
|
694
|
+
if (p2.isCancel(newKey)) {
|
|
695
|
+
p2.cancel("Cancelled.");
|
|
478
696
|
process.exit(0);
|
|
479
697
|
}
|
|
480
|
-
const
|
|
481
|
-
|
|
698
|
+
const spinner5 = p2.spinner();
|
|
699
|
+
spinner5.start("Validating API key...");
|
|
482
700
|
const api = new PicaApi(newKey);
|
|
483
701
|
const isValid = await api.validateApiKey();
|
|
484
702
|
if (!isValid) {
|
|
485
|
-
|
|
486
|
-
|
|
703
|
+
spinner5.stop("Invalid API key");
|
|
704
|
+
p2.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
|
|
487
705
|
process.exit(1);
|
|
488
706
|
}
|
|
489
|
-
|
|
707
|
+
spinner5.stop("API key validated");
|
|
708
|
+
const ac = getAccessControl();
|
|
490
709
|
const reinstalled = [];
|
|
491
710
|
for (const s of statuses) {
|
|
492
711
|
if (s.globalMcp) {
|
|
493
|
-
installMcpConfig(s.agent, newKey, "global");
|
|
712
|
+
installMcpConfig(s.agent, newKey, "global", ac);
|
|
494
713
|
reinstalled.push(`${s.agent.name} (global)`);
|
|
495
714
|
}
|
|
496
715
|
if (s.projectMcp) {
|
|
497
|
-
installMcpConfig(s.agent, newKey, "project");
|
|
716
|
+
installMcpConfig(s.agent, newKey, "project", ac);
|
|
498
717
|
reinstalled.push(`${s.agent.name} (project)`);
|
|
499
718
|
}
|
|
500
719
|
}
|
|
@@ -502,108 +721,111 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
502
721
|
writeConfig({
|
|
503
722
|
apiKey: newKey,
|
|
504
723
|
installedAgents: config?.installedAgents ?? [],
|
|
505
|
-
createdAt: config?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
724
|
+
createdAt: config?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
725
|
+
accessControl: config?.accessControl
|
|
506
726
|
});
|
|
507
727
|
if (reinstalled.length > 0) {
|
|
508
|
-
|
|
728
|
+
p2.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
|
|
509
729
|
}
|
|
510
|
-
|
|
730
|
+
p2.outro("API key updated.");
|
|
511
731
|
}
|
|
512
732
|
async function handleInstallMore(apiKey, missing) {
|
|
733
|
+
const ac = getAccessControl();
|
|
513
734
|
if (missing.length === 1) {
|
|
514
735
|
const agent = missing[0].agent;
|
|
515
|
-
const
|
|
736
|
+
const confirm3 = await p2.confirm({
|
|
516
737
|
message: `Install Pica MCP to ${agent.name}?`,
|
|
517
738
|
initialValue: true
|
|
518
739
|
});
|
|
519
|
-
if (
|
|
520
|
-
|
|
740
|
+
if (p2.isCancel(confirm3) || !confirm3) {
|
|
741
|
+
p2.outro("No changes made.");
|
|
521
742
|
return;
|
|
522
743
|
}
|
|
523
|
-
installMcpConfig(agent, apiKey, "global");
|
|
744
|
+
installMcpConfig(agent, apiKey, "global", ac);
|
|
524
745
|
updateConfigAgents(agent.id);
|
|
525
|
-
|
|
526
|
-
|
|
746
|
+
p2.log.success(`${agent.name}: MCP installed`);
|
|
747
|
+
p2.outro("Done.");
|
|
527
748
|
return;
|
|
528
749
|
}
|
|
529
|
-
const selected = await
|
|
750
|
+
const selected = await p2.multiselect({
|
|
530
751
|
message: "Select agents to install MCP:",
|
|
531
752
|
options: missing.map((s) => ({
|
|
532
753
|
value: s.agent.id,
|
|
533
754
|
label: s.agent.name
|
|
534
755
|
}))
|
|
535
756
|
});
|
|
536
|
-
if (
|
|
537
|
-
|
|
757
|
+
if (p2.isCancel(selected)) {
|
|
758
|
+
p2.outro("No changes made.");
|
|
538
759
|
return;
|
|
539
760
|
}
|
|
540
761
|
const agents = missing.filter((s) => selected.includes(s.agent.id));
|
|
541
762
|
for (const s of agents) {
|
|
542
|
-
installMcpConfig(s.agent, apiKey, "global");
|
|
763
|
+
installMcpConfig(s.agent, apiKey, "global", ac);
|
|
543
764
|
updateConfigAgents(s.agent.id);
|
|
544
|
-
|
|
765
|
+
p2.log.success(`${s.agent.name}: MCP installed`);
|
|
545
766
|
}
|
|
546
|
-
|
|
767
|
+
p2.outro("Done.");
|
|
547
768
|
}
|
|
548
769
|
async function handleInstallProject(apiKey, missing) {
|
|
770
|
+
const ac = getAccessControl();
|
|
549
771
|
if (missing.length === 1) {
|
|
550
772
|
const agent = missing[0].agent;
|
|
551
|
-
const
|
|
773
|
+
const confirm3 = await p2.confirm({
|
|
552
774
|
message: `Install project-level MCP for ${agent.name}?`,
|
|
553
775
|
initialValue: true
|
|
554
776
|
});
|
|
555
|
-
if (
|
|
556
|
-
|
|
777
|
+
if (p2.isCancel(confirm3) || !confirm3) {
|
|
778
|
+
p2.outro("No changes made.");
|
|
557
779
|
return;
|
|
558
780
|
}
|
|
559
|
-
installMcpConfig(agent, apiKey, "project");
|
|
781
|
+
installMcpConfig(agent, apiKey, "project", ac);
|
|
560
782
|
const configPath = getAgentConfigPath(agent, "project");
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
783
|
+
p2.log.success(`${agent.name}: ${configPath} created`);
|
|
784
|
+
p2.note(
|
|
785
|
+
pc3.yellow("Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key."),
|
|
564
786
|
"Tip"
|
|
565
787
|
);
|
|
566
|
-
|
|
788
|
+
p2.outro("Done.");
|
|
567
789
|
return;
|
|
568
790
|
}
|
|
569
|
-
const selected = await
|
|
791
|
+
const selected = await p2.multiselect({
|
|
570
792
|
message: "Select agents for project-level MCP:",
|
|
571
793
|
options: missing.map((s) => ({
|
|
572
794
|
value: s.agent.id,
|
|
573
795
|
label: s.agent.name
|
|
574
796
|
}))
|
|
575
797
|
});
|
|
576
|
-
if (
|
|
577
|
-
|
|
798
|
+
if (p2.isCancel(selected)) {
|
|
799
|
+
p2.outro("No changes made.");
|
|
578
800
|
return;
|
|
579
801
|
}
|
|
580
802
|
const agents = missing.filter((s) => selected.includes(s.agent.id));
|
|
581
803
|
for (const s of agents) {
|
|
582
|
-
installMcpConfig(s.agent, apiKey, "project");
|
|
804
|
+
installMcpConfig(s.agent, apiKey, "project", ac);
|
|
583
805
|
const configPath = getAgentConfigPath(s.agent, "project");
|
|
584
|
-
|
|
806
|
+
p2.log.success(`${s.agent.name}: ${configPath} created`);
|
|
585
807
|
}
|
|
586
|
-
|
|
587
|
-
|
|
808
|
+
p2.note(
|
|
809
|
+
pc3.yellow("Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key."),
|
|
588
810
|
"Tip"
|
|
589
811
|
);
|
|
590
|
-
|
|
812
|
+
p2.outro("Done.");
|
|
591
813
|
}
|
|
592
814
|
async function freshSetup(options) {
|
|
593
|
-
|
|
594
|
-
${
|
|
595
|
-
const openBrowser = await
|
|
815
|
+
p2.note(`Get your API key at:
|
|
816
|
+
${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
817
|
+
const openBrowser = await p2.confirm({
|
|
596
818
|
message: "Open browser to get API key?",
|
|
597
819
|
initialValue: true
|
|
598
820
|
});
|
|
599
|
-
if (
|
|
600
|
-
|
|
821
|
+
if (p2.isCancel(openBrowser)) {
|
|
822
|
+
p2.cancel("Setup cancelled.");
|
|
601
823
|
process.exit(0);
|
|
602
824
|
}
|
|
603
825
|
if (openBrowser) {
|
|
604
826
|
await openApiKeyPage();
|
|
605
827
|
}
|
|
606
|
-
const apiKey = await
|
|
828
|
+
const apiKey = await p2.text({
|
|
607
829
|
message: "Enter your Pica API key:",
|
|
608
830
|
placeholder: "sk_live_...",
|
|
609
831
|
validate: (value) => {
|
|
@@ -614,27 +836,27 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
614
836
|
return void 0;
|
|
615
837
|
}
|
|
616
838
|
});
|
|
617
|
-
if (
|
|
618
|
-
|
|
839
|
+
if (p2.isCancel(apiKey)) {
|
|
840
|
+
p2.cancel("Setup cancelled.");
|
|
619
841
|
process.exit(0);
|
|
620
842
|
}
|
|
621
|
-
const
|
|
622
|
-
|
|
843
|
+
const spinner5 = p2.spinner();
|
|
844
|
+
spinner5.start("Validating API key...");
|
|
623
845
|
const api = new PicaApi(apiKey);
|
|
624
846
|
const isValid = await api.validateApiKey();
|
|
625
847
|
if (!isValid) {
|
|
626
|
-
|
|
627
|
-
|
|
848
|
+
spinner5.stop("Invalid API key");
|
|
849
|
+
p2.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
|
|
628
850
|
process.exit(1);
|
|
629
851
|
}
|
|
630
|
-
|
|
852
|
+
spinner5.stop("API key validated");
|
|
631
853
|
writeConfig({
|
|
632
854
|
apiKey,
|
|
633
855
|
installedAgents: [],
|
|
634
856
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
635
857
|
});
|
|
636
858
|
const allAgents = getAllAgents();
|
|
637
|
-
const agentChoice = await
|
|
859
|
+
const agentChoice = await p2.select({
|
|
638
860
|
message: "Where do you want to install the MCP?",
|
|
639
861
|
options: [
|
|
640
862
|
{
|
|
@@ -648,8 +870,8 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
648
870
|
}))
|
|
649
871
|
]
|
|
650
872
|
});
|
|
651
|
-
if (
|
|
652
|
-
|
|
873
|
+
if (p2.isCancel(agentChoice)) {
|
|
874
|
+
p2.cancel("Setup cancelled.");
|
|
653
875
|
process.exit(0);
|
|
654
876
|
}
|
|
655
877
|
const selectedAgents = agentChoice === "all" ? allAgents : allAgents.filter((a) => a.id === agentChoice);
|
|
@@ -660,7 +882,7 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
660
882
|
} else if (options.project) {
|
|
661
883
|
scope = "project";
|
|
662
884
|
} else if (hasProjectScopeAgent) {
|
|
663
|
-
const scopeChoice = await
|
|
885
|
+
const scopeChoice = await p2.select({
|
|
664
886
|
message: "How do you want to install it?",
|
|
665
887
|
options: [
|
|
666
888
|
{
|
|
@@ -675,8 +897,8 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
675
897
|
}
|
|
676
898
|
]
|
|
677
899
|
});
|
|
678
|
-
if (
|
|
679
|
-
|
|
900
|
+
if (p2.isCancel(scopeChoice)) {
|
|
901
|
+
p2.cancel("Setup cancelled.");
|
|
680
902
|
process.exit(0);
|
|
681
903
|
}
|
|
682
904
|
scope = scopeChoice;
|
|
@@ -686,12 +908,12 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
686
908
|
const nonProjectAgents = selectedAgents.filter((a) => !supportsProjectScope(a));
|
|
687
909
|
if (projectAgents.length === 0) {
|
|
688
910
|
const supported = allAgents.filter((a) => supportsProjectScope(a)).map((a) => a.name).join(", ");
|
|
689
|
-
|
|
911
|
+
p2.note(
|
|
690
912
|
`${selectedAgents.map((a) => a.name).join(", ")} does not support project-level MCP.
|
|
691
913
|
Project scope is supported by: ${supported}`,
|
|
692
914
|
"Not Supported"
|
|
693
915
|
);
|
|
694
|
-
|
|
916
|
+
p2.cancel("Run again and choose global scope or a different agent.");
|
|
695
917
|
process.exit(1);
|
|
696
918
|
}
|
|
697
919
|
for (const agent of projectAgents) {
|
|
@@ -699,15 +921,15 @@ Project scope is supported by: ${supported}`,
|
|
|
699
921
|
installMcpConfig(agent, apiKey, "project");
|
|
700
922
|
const configPath = getAgentConfigPath(agent, "project");
|
|
701
923
|
const status = wasInstalled ? "updated" : "created";
|
|
702
|
-
|
|
924
|
+
p2.log.success(`${agent.name}: ${configPath} ${status}`);
|
|
703
925
|
}
|
|
704
926
|
if (nonProjectAgents.length > 0) {
|
|
705
|
-
|
|
927
|
+
p2.log.info(`Installing globally for agents without project scope support:`);
|
|
706
928
|
for (const agent of nonProjectAgents) {
|
|
707
929
|
const wasInstalled = isMcpInstalled(agent, "global");
|
|
708
930
|
installMcpConfig(agent, apiKey, "global");
|
|
709
931
|
const status = wasInstalled ? "updated" : "installed";
|
|
710
|
-
|
|
932
|
+
p2.log.success(`${agent.name}: MCP ${status} (global)`);
|
|
711
933
|
}
|
|
712
934
|
}
|
|
713
935
|
const allInstalled = [...projectAgents, ...nonProjectAgents];
|
|
@@ -716,23 +938,23 @@ Project scope is supported by: ${supported}`,
|
|
|
716
938
|
installedAgents: allInstalled.map((a) => a.id),
|
|
717
939
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
718
940
|
});
|
|
719
|
-
const configPaths = projectAgents.map((a) => ` ${a.name}: ${
|
|
720
|
-
let summary = `Config saved to: ${
|
|
941
|
+
const configPaths = projectAgents.map((a) => ` ${a.name}: ${pc3.dim(getAgentConfigPath(a, "project"))}`).join("\n");
|
|
942
|
+
let summary = `Config saved to: ${pc3.dim(getConfigPath())}
|
|
721
943
|
MCP configs:
|
|
722
944
|
${configPaths}
|
|
723
945
|
|
|
724
946
|
`;
|
|
725
947
|
if (nonProjectAgents.length > 0) {
|
|
726
|
-
const globalPaths = nonProjectAgents.map((a) => ` ${a.name}: ${
|
|
948
|
+
const globalPaths = nonProjectAgents.map((a) => ` ${a.name}: ${pc3.dim(getAgentConfigPath(a, "global"))}`).join("\n");
|
|
727
949
|
summary += `Global configs:
|
|
728
950
|
${globalPaths}
|
|
729
951
|
|
|
730
952
|
`;
|
|
731
953
|
}
|
|
732
|
-
summary +=
|
|
733
|
-
|
|
954
|
+
summary += pc3.yellow("Note: Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key.");
|
|
955
|
+
p2.note(summary, "Setup Complete");
|
|
734
956
|
await promptConnectIntegrations(apiKey);
|
|
735
|
-
|
|
957
|
+
p2.outro("Your AI agents now have access to Pica integrations!");
|
|
736
958
|
return;
|
|
737
959
|
}
|
|
738
960
|
const installedAgentIds = [];
|
|
@@ -741,34 +963,34 @@ ${globalPaths}
|
|
|
741
963
|
installMcpConfig(agent, apiKey, "global");
|
|
742
964
|
installedAgentIds.push(agent.id);
|
|
743
965
|
const status = wasInstalled ? "updated" : "installed";
|
|
744
|
-
|
|
966
|
+
p2.log.success(`${agent.name}: MCP ${status}`);
|
|
745
967
|
}
|
|
746
968
|
writeConfig({
|
|
747
969
|
apiKey,
|
|
748
970
|
installedAgents: installedAgentIds,
|
|
749
971
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
750
972
|
});
|
|
751
|
-
|
|
752
|
-
`Config saved to: ${
|
|
973
|
+
p2.note(
|
|
974
|
+
`Config saved to: ${pc3.dim(getConfigPath())}`,
|
|
753
975
|
"Setup Complete"
|
|
754
976
|
);
|
|
755
977
|
await promptConnectIntegrations(apiKey);
|
|
756
|
-
|
|
978
|
+
p2.outro("Your AI agents now have access to Pica integrations!");
|
|
757
979
|
}
|
|
758
980
|
function printBanner() {
|
|
759
981
|
console.log();
|
|
760
|
-
console.log(
|
|
761
|
-
console.log(
|
|
762
|
-
console.log(
|
|
763
|
-
console.log(
|
|
764
|
-
console.log(
|
|
765
|
-
console.log(
|
|
766
|
-
console.log(
|
|
767
|
-
console.log(
|
|
768
|
-
console.log(
|
|
769
|
-
console.log(
|
|
982
|
+
console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
|
|
983
|
+
console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
|
|
984
|
+
console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
|
|
985
|
+
console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
|
|
986
|
+
console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
|
|
987
|
+
console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
|
|
988
|
+
console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
|
|
989
|
+
console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
|
|
990
|
+
console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
|
|
991
|
+
console.log(pc3.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
|
|
770
992
|
console.log();
|
|
771
|
-
console.log(
|
|
993
|
+
console.log(pc3.dim(" U N I V E R S A L I N T E G R A T I O N S F O R A I"));
|
|
772
994
|
console.log();
|
|
773
995
|
}
|
|
774
996
|
var TOP_INTEGRATIONS = [
|
|
@@ -802,48 +1024,48 @@ async function promptConnectIntegrations(apiKey) {
|
|
|
802
1024
|
{ value: "skip", label: "Skip for now", hint: "you can always run pica add later" }
|
|
803
1025
|
];
|
|
804
1026
|
const message = first ? "Connect your first integration?" : "Connect another?";
|
|
805
|
-
const choice = await
|
|
806
|
-
if (
|
|
1027
|
+
const choice = await p2.select({ message, options });
|
|
1028
|
+
if (p2.isCancel(choice) || choice === "skip") {
|
|
807
1029
|
break;
|
|
808
1030
|
}
|
|
809
1031
|
if (choice === "more") {
|
|
810
1032
|
try {
|
|
811
1033
|
await open2("https://app.picaos.com/connections");
|
|
812
|
-
|
|
1034
|
+
p2.log.info("Opened Pica dashboard in browser.");
|
|
813
1035
|
} catch {
|
|
814
|
-
|
|
1036
|
+
p2.note("https://app.picaos.com/connections", "Open in browser");
|
|
815
1037
|
}
|
|
816
|
-
|
|
1038
|
+
p2.log.info(`Connect from the dashboard, or use ${pc3.cyan("pica add <platform>")}`);
|
|
817
1039
|
break;
|
|
818
1040
|
}
|
|
819
1041
|
const platform = choice;
|
|
820
1042
|
const integration = TOP_INTEGRATIONS.find((i) => i.value === platform);
|
|
821
1043
|
const label = integration?.label ?? platform;
|
|
822
|
-
|
|
1044
|
+
p2.log.info(`Opening browser to connect ${pc3.cyan(label)}...`);
|
|
823
1045
|
try {
|
|
824
1046
|
await openConnectionPage(platform);
|
|
825
1047
|
} catch {
|
|
826
1048
|
const url = getConnectionUrl(platform);
|
|
827
|
-
|
|
828
|
-
|
|
1049
|
+
p2.log.warn("Could not open browser automatically.");
|
|
1050
|
+
p2.note(url, "Open manually");
|
|
829
1051
|
}
|
|
830
|
-
const
|
|
831
|
-
|
|
1052
|
+
const spinner5 = p2.spinner();
|
|
1053
|
+
spinner5.start("Waiting for connection... (complete auth in browser)");
|
|
832
1054
|
try {
|
|
833
1055
|
await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
|
|
834
|
-
|
|
835
|
-
|
|
1056
|
+
spinner5.stop(`${label} connected!`);
|
|
1057
|
+
p2.log.success(`${pc3.green("\u2713")} ${label} is now available to your AI agents`);
|
|
836
1058
|
connected.push(platform);
|
|
837
1059
|
first = false;
|
|
838
1060
|
} catch (error) {
|
|
839
|
-
|
|
1061
|
+
spinner5.stop("Connection timed out");
|
|
840
1062
|
if (error instanceof TimeoutError) {
|
|
841
|
-
|
|
1063
|
+
p2.log.warn(`No worries. Connect later with: ${pc3.cyan(`pica add ${platform}`)}`);
|
|
842
1064
|
}
|
|
843
1065
|
first = false;
|
|
844
1066
|
}
|
|
845
1067
|
if (TOP_INTEGRATIONS.every((i) => connected.includes(i.value))) {
|
|
846
|
-
|
|
1068
|
+
p2.log.success("All top integrations connected!");
|
|
847
1069
|
break;
|
|
848
1070
|
}
|
|
849
1071
|
}
|
|
@@ -862,23 +1084,23 @@ function updateConfigAgents(agentId) {
|
|
|
862
1084
|
}
|
|
863
1085
|
|
|
864
1086
|
// src/commands/connection.ts
|
|
865
|
-
import * as
|
|
866
|
-
import
|
|
1087
|
+
import * as p3 from "@clack/prompts";
|
|
1088
|
+
import pc4 from "picocolors";
|
|
867
1089
|
|
|
868
1090
|
// src/lib/platforms.ts
|
|
869
1091
|
function findPlatform(platforms, query) {
|
|
870
1092
|
const normalizedQuery = query.toLowerCase().trim();
|
|
871
1093
|
const exact = platforms.find(
|
|
872
|
-
(
|
|
1094
|
+
(p5) => p5.platform.toLowerCase() === normalizedQuery || p5.name.toLowerCase() === normalizedQuery
|
|
873
1095
|
);
|
|
874
1096
|
if (exact) return exact;
|
|
875
1097
|
return null;
|
|
876
1098
|
}
|
|
877
1099
|
function findSimilarPlatforms(platforms, query, limit = 3) {
|
|
878
1100
|
const normalizedQuery = query.toLowerCase().trim();
|
|
879
|
-
const scored = platforms.map((
|
|
880
|
-
const name =
|
|
881
|
-
const slug =
|
|
1101
|
+
const scored = platforms.map((p5) => {
|
|
1102
|
+
const name = p5.name.toLowerCase();
|
|
1103
|
+
const slug = p5.platform.toLowerCase();
|
|
882
1104
|
let score = 0;
|
|
883
1105
|
if (name.includes(normalizedQuery) || slug.includes(normalizedQuery)) {
|
|
884
1106
|
score = 10;
|
|
@@ -887,7 +1109,7 @@ function findSimilarPlatforms(platforms, query, limit = 3) {
|
|
|
887
1109
|
} else {
|
|
888
1110
|
score = countMatchingChars(normalizedQuery, slug);
|
|
889
1111
|
}
|
|
890
|
-
return { platform:
|
|
1112
|
+
return { platform: p5, score };
|
|
891
1113
|
}).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
892
1114
|
return scored.map((item) => item.platform);
|
|
893
1115
|
}
|
|
@@ -902,22 +1124,22 @@ function countMatchingChars(a, b) {
|
|
|
902
1124
|
|
|
903
1125
|
// src/commands/connection.ts
|
|
904
1126
|
async function connectionAddCommand(platformArg) {
|
|
905
|
-
|
|
1127
|
+
p3.intro(pc4.bgCyan(pc4.black(" Pica ")));
|
|
906
1128
|
const apiKey = getApiKey();
|
|
907
1129
|
if (!apiKey) {
|
|
908
|
-
|
|
1130
|
+
p3.cancel("Not configured. Run `pica init` first.");
|
|
909
1131
|
process.exit(1);
|
|
910
1132
|
}
|
|
911
1133
|
const api = new PicaApi(apiKey);
|
|
912
|
-
const
|
|
913
|
-
|
|
1134
|
+
const spinner5 = p3.spinner();
|
|
1135
|
+
spinner5.start("Loading platforms...");
|
|
914
1136
|
let platforms;
|
|
915
1137
|
try {
|
|
916
1138
|
platforms = await api.listPlatforms();
|
|
917
|
-
|
|
1139
|
+
spinner5.stop(`${platforms.length} platforms available`);
|
|
918
1140
|
} catch (error) {
|
|
919
|
-
|
|
920
|
-
|
|
1141
|
+
spinner5.stop("Failed to load platforms");
|
|
1142
|
+
p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
921
1143
|
process.exit(1);
|
|
922
1144
|
}
|
|
923
1145
|
let platform;
|
|
@@ -928,29 +1150,29 @@ async function connectionAddCommand(platformArg) {
|
|
|
928
1150
|
} else {
|
|
929
1151
|
const similar = findSimilarPlatforms(platforms, platformArg);
|
|
930
1152
|
if (similar.length > 0) {
|
|
931
|
-
|
|
932
|
-
const suggestion = await
|
|
1153
|
+
p3.log.warn(`Unknown platform: ${platformArg}`);
|
|
1154
|
+
const suggestion = await p3.select({
|
|
933
1155
|
message: "Did you mean:",
|
|
934
1156
|
options: [
|
|
935
1157
|
...similar.map((s) => ({ value: s.platform, label: `${s.name} (${s.platform})` })),
|
|
936
1158
|
{ value: "__other__", label: "None of these" }
|
|
937
1159
|
]
|
|
938
1160
|
});
|
|
939
|
-
if (
|
|
940
|
-
|
|
941
|
-
|
|
1161
|
+
if (p3.isCancel(suggestion) || suggestion === "__other__") {
|
|
1162
|
+
p3.note(`Run ${pc4.cyan("pica platforms")} to see all available platforms.`);
|
|
1163
|
+
p3.cancel("Connection cancelled.");
|
|
942
1164
|
process.exit(0);
|
|
943
1165
|
}
|
|
944
1166
|
platform = suggestion;
|
|
945
1167
|
} else {
|
|
946
|
-
|
|
1168
|
+
p3.cancel(`Unknown platform: ${platformArg}
|
|
947
1169
|
|
|
948
|
-
Run ${
|
|
1170
|
+
Run ${pc4.cyan("pica platforms")} to see available platforms.`);
|
|
949
1171
|
process.exit(1);
|
|
950
1172
|
}
|
|
951
1173
|
}
|
|
952
1174
|
} else {
|
|
953
|
-
const platformInput = await
|
|
1175
|
+
const platformInput = await p3.text({
|
|
954
1176
|
message: "Which platform do you want to connect?",
|
|
955
1177
|
placeholder: "gmail, slack, hubspot...",
|
|
956
1178
|
validate: (value) => {
|
|
@@ -958,51 +1180,51 @@ Run ${pc3.cyan("pica platforms")} to see available platforms.`);
|
|
|
958
1180
|
return void 0;
|
|
959
1181
|
}
|
|
960
1182
|
});
|
|
961
|
-
if (
|
|
962
|
-
|
|
1183
|
+
if (p3.isCancel(platformInput)) {
|
|
1184
|
+
p3.cancel("Connection cancelled.");
|
|
963
1185
|
process.exit(0);
|
|
964
1186
|
}
|
|
965
1187
|
const found = findPlatform(platforms, platformInput);
|
|
966
1188
|
if (found) {
|
|
967
1189
|
platform = found.platform;
|
|
968
1190
|
} else {
|
|
969
|
-
|
|
1191
|
+
p3.cancel(`Unknown platform: ${platformInput}
|
|
970
1192
|
|
|
971
|
-
Run ${
|
|
1193
|
+
Run ${pc4.cyan("pica platforms")} to see available platforms.`);
|
|
972
1194
|
process.exit(1);
|
|
973
1195
|
}
|
|
974
1196
|
}
|
|
975
1197
|
const url = getConnectionUrl(platform);
|
|
976
|
-
|
|
977
|
-
|
|
1198
|
+
p3.log.info(`Opening browser to connect ${pc4.cyan(platform)}...`);
|
|
1199
|
+
p3.note(pc4.dim(url), "URL");
|
|
978
1200
|
try {
|
|
979
1201
|
await openConnectionPage(platform);
|
|
980
1202
|
} catch {
|
|
981
|
-
|
|
982
|
-
|
|
1203
|
+
p3.log.warn("Could not open browser automatically.");
|
|
1204
|
+
p3.note(`Open this URL manually:
|
|
983
1205
|
${url}`);
|
|
984
1206
|
}
|
|
985
|
-
const pollSpinner =
|
|
1207
|
+
const pollSpinner = p3.spinner();
|
|
986
1208
|
pollSpinner.start("Waiting for connection... (complete auth in browser)");
|
|
987
1209
|
try {
|
|
988
1210
|
const connection2 = await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
|
|
989
1211
|
pollSpinner.stop(`${platform} connected!`);
|
|
990
|
-
|
|
991
|
-
|
|
1212
|
+
p3.log.success(`${pc4.green("\u2713")} ${connection2.platform} is now available to your AI agents.`);
|
|
1213
|
+
p3.outro("Connection complete!");
|
|
992
1214
|
} catch (error) {
|
|
993
1215
|
pollSpinner.stop("Connection timed out");
|
|
994
1216
|
if (error instanceof TimeoutError) {
|
|
995
|
-
|
|
1217
|
+
p3.note(
|
|
996
1218
|
`Possible issues:
|
|
997
1219
|
- OAuth flow was not completed in the browser
|
|
998
1220
|
- Browser popup was blocked
|
|
999
1221
|
- Wrong account selected
|
|
1000
1222
|
|
|
1001
|
-
Try again with: ${
|
|
1223
|
+
Try again with: ${pc4.cyan(`pica connection add ${platform}`)}`,
|
|
1002
1224
|
"Timed Out"
|
|
1003
1225
|
);
|
|
1004
1226
|
} else {
|
|
1005
|
-
|
|
1227
|
+
p3.log.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1006
1228
|
}
|
|
1007
1229
|
process.exit(1);
|
|
1008
1230
|
}
|
|
@@ -1010,20 +1232,20 @@ Try again with: ${pc3.cyan(`pica connection add ${platform}`)}`,
|
|
|
1010
1232
|
async function connectionListCommand() {
|
|
1011
1233
|
const apiKey = getApiKey();
|
|
1012
1234
|
if (!apiKey) {
|
|
1013
|
-
|
|
1235
|
+
p3.cancel("Not configured. Run `pica init` first.");
|
|
1014
1236
|
process.exit(1);
|
|
1015
1237
|
}
|
|
1016
1238
|
const api = new PicaApi(apiKey);
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1239
|
+
const spinner5 = p3.spinner();
|
|
1240
|
+
spinner5.start("Loading connections...");
|
|
1019
1241
|
try {
|
|
1020
1242
|
const connections = await api.listConnections();
|
|
1021
|
-
|
|
1243
|
+
spinner5.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
|
|
1022
1244
|
if (connections.length === 0) {
|
|
1023
|
-
|
|
1245
|
+
p3.note(
|
|
1024
1246
|
`No connections yet.
|
|
1025
1247
|
|
|
1026
|
-
Add one with: ${
|
|
1248
|
+
Add one with: ${pc4.cyan("pica connection add gmail")}`,
|
|
1027
1249
|
"No Connections"
|
|
1028
1250
|
);
|
|
1029
1251
|
return;
|
|
@@ -1040,46 +1262,46 @@ Add one with: ${pc3.cyan("pica connection add gmail")}`,
|
|
|
1040
1262
|
{ key: "status", label: "" },
|
|
1041
1263
|
{ key: "platform", label: "Platform" },
|
|
1042
1264
|
{ key: "state", label: "Status" },
|
|
1043
|
-
{ key: "key", label: "Connection Key", color:
|
|
1265
|
+
{ key: "key", label: "Connection Key", color: pc4.dim }
|
|
1044
1266
|
],
|
|
1045
1267
|
rows
|
|
1046
1268
|
);
|
|
1047
1269
|
console.log();
|
|
1048
|
-
|
|
1270
|
+
p3.note(`Add more with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
|
|
1049
1271
|
} catch (error) {
|
|
1050
|
-
|
|
1051
|
-
|
|
1272
|
+
spinner5.stop("Failed to load connections");
|
|
1273
|
+
p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1052
1274
|
process.exit(1);
|
|
1053
1275
|
}
|
|
1054
1276
|
}
|
|
1055
1277
|
function getStatusIndicator(state) {
|
|
1056
1278
|
switch (state) {
|
|
1057
1279
|
case "operational":
|
|
1058
|
-
return
|
|
1280
|
+
return pc4.green("\u25CF");
|
|
1059
1281
|
case "degraded":
|
|
1060
|
-
return
|
|
1282
|
+
return pc4.yellow("\u25CF");
|
|
1061
1283
|
case "failed":
|
|
1062
|
-
return
|
|
1284
|
+
return pc4.red("\u25CF");
|
|
1063
1285
|
default:
|
|
1064
|
-
return
|
|
1286
|
+
return pc4.dim("\u25CB");
|
|
1065
1287
|
}
|
|
1066
1288
|
}
|
|
1067
1289
|
|
|
1068
1290
|
// src/commands/platforms.ts
|
|
1069
|
-
import * as
|
|
1070
|
-
import
|
|
1291
|
+
import * as p4 from "@clack/prompts";
|
|
1292
|
+
import pc5 from "picocolors";
|
|
1071
1293
|
async function platformsCommand(options) {
|
|
1072
1294
|
const apiKey = getApiKey();
|
|
1073
1295
|
if (!apiKey) {
|
|
1074
|
-
|
|
1296
|
+
p4.cancel("Not configured. Run `pica init` first.");
|
|
1075
1297
|
process.exit(1);
|
|
1076
1298
|
}
|
|
1077
1299
|
const api = new PicaApi(apiKey);
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1300
|
+
const spinner5 = p4.spinner();
|
|
1301
|
+
spinner5.start("Loading platforms...");
|
|
1080
1302
|
try {
|
|
1081
1303
|
const platforms = await api.listPlatforms();
|
|
1082
|
-
|
|
1304
|
+
spinner5.stop(`${platforms.length} platforms available`);
|
|
1083
1305
|
if (options.json) {
|
|
1084
1306
|
console.log(JSON.stringify(platforms, null, 2));
|
|
1085
1307
|
return;
|
|
@@ -1097,7 +1319,7 @@ async function platformsCommand(options) {
|
|
|
1097
1319
|
const categoryPlatforms = byCategory.get(options.category);
|
|
1098
1320
|
if (!categoryPlatforms) {
|
|
1099
1321
|
const categories = [...byCategory.keys()].sort();
|
|
1100
|
-
|
|
1322
|
+
p4.note(`Available categories:
|
|
1101
1323
|
${categories.join(", ")}`, "Unknown Category");
|
|
1102
1324
|
process.exit(1);
|
|
1103
1325
|
}
|
|
@@ -1129,10 +1351,10 @@ async function platformsCommand(options) {
|
|
|
1129
1351
|
);
|
|
1130
1352
|
}
|
|
1131
1353
|
console.log();
|
|
1132
|
-
|
|
1354
|
+
p4.note(`Connect with: ${pc5.cyan("pica connection add <platform>")}`, "Tip");
|
|
1133
1355
|
} catch (error) {
|
|
1134
|
-
|
|
1135
|
-
|
|
1356
|
+
spinner5.stop("Failed to load platforms");
|
|
1357
|
+
p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1136
1358
|
process.exit(1);
|
|
1137
1359
|
}
|
|
1138
1360
|
}
|
|
@@ -1145,6 +1367,9 @@ program.name("pica").description("CLI for managing Pica").version(version);
|
|
|
1145
1367
|
program.command("init").description("Set up Pica and install MCP to your AI agents").option("-y, --yes", "Skip confirmations").option("-g, --global", "Install MCP globally (available in all projects)").option("-p, --project", "Install MCP for this project only (creates .mcp.json)").action(async (options) => {
|
|
1146
1368
|
await initCommand(options);
|
|
1147
1369
|
});
|
|
1370
|
+
program.command("config").description("Configure MCP access control (permissions, connections, actions)").action(async () => {
|
|
1371
|
+
await configCommand();
|
|
1372
|
+
});
|
|
1148
1373
|
var connection = program.command("connection").description("Manage connections");
|
|
1149
1374
|
connection.command("add [platform]").alias("a").description("Add a new connection").action(async (platform) => {
|
|
1150
1375
|
await connectionAddCommand(platform);
|