@picahq/cli 1.6.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/README.md +3 -1
- package/dist/index.js +428 -195
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ If you already have a config, `pica init` shows your current setup instead of st
|
|
|
46
46
|
Cursor ○ no ○ no
|
|
47
47
|
Windsurf - -
|
|
48
48
|
Codex ● yes ○ no
|
|
49
|
+
Kiro ○ no ○ no
|
|
49
50
|
|
|
50
51
|
- = not detected on this machine
|
|
51
52
|
```
|
|
@@ -54,7 +55,7 @@ Then it offers targeted actions based on what's missing:
|
|
|
54
55
|
|
|
55
56
|
- **Update API key** -- validates the new key, then re-installs to every agent that currently has the MCP (preserving global/project scopes)
|
|
56
57
|
- **Install MCP to more agents** -- only shows detected agents missing the MCP
|
|
57
|
-
- **Install MCP for this project** -- creates `.mcp.json` / `.cursor/mcp.json` / `.codex/config.toml` in cwd for agents that support project scope
|
|
58
|
+
- **Install MCP for this project** -- creates `.mcp.json` / `.cursor/mcp.json` / `.codex/config.toml` / `.kiro/settings/mcp.json` in cwd for agents that support project scope
|
|
58
59
|
- **Start fresh** -- full setup flow from scratch
|
|
59
60
|
|
|
60
61
|
Options that don't apply are hidden. If every detected agent already has the MCP globally, "Install MCP to more agents" won't appear.
|
|
@@ -130,6 +131,7 @@ All API calls route through Pica's passthrough proxy (`api.picaos.com/v1/passthr
|
|
|
130
131
|
| Cursor | `~/.cursor/mcp.json` | `.cursor/mcp.json` |
|
|
131
132
|
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | n/a |
|
|
132
133
|
| Codex | `~/.codex/config.toml` | `.codex/config.toml` |
|
|
134
|
+
| Kiro | `~/.kiro/settings/mcp.json` | `.kiro/settings/mcp.json` |
|
|
133
135
|
|
|
134
136
|
Global installs make the MCP available everywhere. Project installs create config files in your current directory that can be committed and shared with your team (each team member needs their own API key).
|
|
135
137
|
|
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) {
|
|
@@ -130,6 +156,14 @@ var AGENTS = [
|
|
|
130
156
|
detectDir: "~/.codex",
|
|
131
157
|
projectConfigPath: ".codex/config.toml",
|
|
132
158
|
configFormat: "toml"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "kiro",
|
|
162
|
+
name: "Kiro",
|
|
163
|
+
configPath: "~/.kiro/settings/mcp.json",
|
|
164
|
+
configKey: "mcpServers",
|
|
165
|
+
detectDir: "~/.kiro",
|
|
166
|
+
projectConfigPath: ".kiro/settings/mcp.json"
|
|
133
167
|
}
|
|
134
168
|
];
|
|
135
169
|
function getAllAgents() {
|
|
@@ -171,20 +205,35 @@ function writeAgentConfig(agent, config, scope = "global") {
|
|
|
171
205
|
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
172
206
|
}
|
|
173
207
|
}
|
|
174
|
-
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
|
+
}
|
|
175
226
|
return {
|
|
176
227
|
command: "npx",
|
|
177
228
|
args: ["-y", "@picahq/mcp"],
|
|
178
|
-
env
|
|
179
|
-
PICA_SECRET: apiKey
|
|
180
|
-
}
|
|
229
|
+
env
|
|
181
230
|
};
|
|
182
231
|
}
|
|
183
|
-
function installMcpConfig(agent, apiKey, scope = "global") {
|
|
232
|
+
function installMcpConfig(agent, apiKey, scope = "global", accessControl) {
|
|
184
233
|
const config = readAgentConfig(agent, scope);
|
|
185
234
|
const configKey = agent.configKey;
|
|
186
235
|
const mcpServers = config[configKey] || {};
|
|
187
|
-
mcpServers["pica"] = getMcpServerConfig(apiKey);
|
|
236
|
+
mcpServers["pica"] = getMcpServerConfig(apiKey, accessControl);
|
|
188
237
|
config[configKey] = mcpServers;
|
|
189
238
|
writeAgentConfig(agent, config, scope);
|
|
190
239
|
}
|
|
@@ -239,8 +288,8 @@ var PicaApi = class {
|
|
|
239
288
|
}
|
|
240
289
|
const response = await fetch(url, fetchOpts);
|
|
241
290
|
if (!response.ok) {
|
|
242
|
-
const
|
|
243
|
-
throw new ApiError(response.status,
|
|
291
|
+
const text4 = await response.text();
|
|
292
|
+
throw new ApiError(response.status, text4 || `HTTP ${response.status}`);
|
|
244
293
|
}
|
|
245
294
|
return response.json();
|
|
246
295
|
}
|
|
@@ -316,11 +365,171 @@ async function openApiKeyPage() {
|
|
|
316
365
|
await open(getApiKeyUrl());
|
|
317
366
|
}
|
|
318
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
|
+
|
|
319
528
|
// src/commands/init.ts
|
|
320
529
|
import open2 from "open";
|
|
321
530
|
|
|
322
531
|
// src/lib/table.ts
|
|
323
|
-
import
|
|
532
|
+
import pc2 from "picocolors";
|
|
324
533
|
function printTable(columns, rows) {
|
|
325
534
|
if (rows.length === 0) return;
|
|
326
535
|
const gap = " ";
|
|
@@ -332,10 +541,10 @@ function printTable(columns, rows) {
|
|
|
332
541
|
});
|
|
333
542
|
const header = columns.map((col, i) => {
|
|
334
543
|
const padded = col.align === "right" ? col.label.padStart(widths[i]) : col.label.padEnd(widths[i]);
|
|
335
|
-
return
|
|
544
|
+
return pc2.dim(padded);
|
|
336
545
|
}).join(gap);
|
|
337
546
|
console.log(`${indent}${header}`);
|
|
338
|
-
const separator = columns.map((_, i) =>
|
|
547
|
+
const separator = columns.map((_, i) => pc2.dim("\u2500".repeat(widths[i]))).join(gap);
|
|
339
548
|
console.log(`${indent}${separator}`);
|
|
340
549
|
for (const row of rows) {
|
|
341
550
|
const line = columns.map((col, i) => {
|
|
@@ -359,7 +568,7 @@ function stripAnsi(str) {
|
|
|
359
568
|
async function initCommand(options) {
|
|
360
569
|
const existingConfig = readConfig();
|
|
361
570
|
if (existingConfig) {
|
|
362
|
-
|
|
571
|
+
p2.intro(pc3.bgCyan(pc3.black(" Pica ")));
|
|
363
572
|
await handleExistingConfig(existingConfig.apiKey, options);
|
|
364
573
|
return;
|
|
365
574
|
}
|
|
@@ -370,10 +579,10 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
370
579
|
const statuses = getAgentStatuses();
|
|
371
580
|
const masked = maskApiKey(apiKey);
|
|
372
581
|
console.log();
|
|
373
|
-
console.log(` ${
|
|
374
|
-
console.log(` ${
|
|
375
|
-
console.log(` ${
|
|
376
|
-
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()}`);
|
|
377
586
|
console.log();
|
|
378
587
|
printTable(
|
|
379
588
|
[
|
|
@@ -383,15 +592,24 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
383
592
|
],
|
|
384
593
|
statuses.map((s) => ({
|
|
385
594
|
agent: s.agent.name,
|
|
386
|
-
global: !s.detected ?
|
|
387
|
-
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")
|
|
388
597
|
}))
|
|
389
598
|
);
|
|
390
599
|
const notDetected = statuses.filter((s) => !s.detected);
|
|
391
600
|
if (notDetected.length > 0) {
|
|
392
|
-
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();
|
|
393
612
|
}
|
|
394
|
-
console.log();
|
|
395
613
|
const actionOptions = [];
|
|
396
614
|
actionOptions.push({
|
|
397
615
|
value: "update-key",
|
|
@@ -413,16 +631,21 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
413
631
|
hint: agentsMissingProject.map((s) => s.agent.name).join(", ")
|
|
414
632
|
});
|
|
415
633
|
}
|
|
634
|
+
actionOptions.push({
|
|
635
|
+
value: "access-control",
|
|
636
|
+
label: "Configure access control",
|
|
637
|
+
hint: "permissions, connections, actions"
|
|
638
|
+
});
|
|
416
639
|
actionOptions.push({
|
|
417
640
|
value: "start-fresh",
|
|
418
641
|
label: "Start fresh (reconfigure everything)"
|
|
419
642
|
});
|
|
420
|
-
const action = await
|
|
643
|
+
const action = await p2.select({
|
|
421
644
|
message: "What would you like to do?",
|
|
422
645
|
options: actionOptions
|
|
423
646
|
});
|
|
424
|
-
if (
|
|
425
|
-
|
|
647
|
+
if (p2.isCancel(action)) {
|
|
648
|
+
p2.outro("No changes made.");
|
|
426
649
|
return;
|
|
427
650
|
}
|
|
428
651
|
switch (action) {
|
|
@@ -435,26 +658,29 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
435
658
|
case "install-project":
|
|
436
659
|
await handleInstallProject(apiKey, agentsMissingProject);
|
|
437
660
|
break;
|
|
661
|
+
case "access-control":
|
|
662
|
+
await configCommand();
|
|
663
|
+
break;
|
|
438
664
|
case "start-fresh":
|
|
439
665
|
await freshSetup({ yes: true });
|
|
440
666
|
break;
|
|
441
667
|
}
|
|
442
668
|
}
|
|
443
669
|
async function handleUpdateKey(statuses) {
|
|
444
|
-
|
|
445
|
-
${
|
|
446
|
-
const openBrowser = await
|
|
670
|
+
p2.note(`Get your API key at:
|
|
671
|
+
${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
672
|
+
const openBrowser = await p2.confirm({
|
|
447
673
|
message: "Open browser to get API key?",
|
|
448
674
|
initialValue: true
|
|
449
675
|
});
|
|
450
|
-
if (
|
|
451
|
-
|
|
676
|
+
if (p2.isCancel(openBrowser)) {
|
|
677
|
+
p2.cancel("Cancelled.");
|
|
452
678
|
process.exit(0);
|
|
453
679
|
}
|
|
454
680
|
if (openBrowser) {
|
|
455
681
|
await openApiKeyPage();
|
|
456
682
|
}
|
|
457
|
-
const newKey = await
|
|
683
|
+
const newKey = await p2.text({
|
|
458
684
|
message: "Enter your new Pica API key:",
|
|
459
685
|
placeholder: "sk_live_...",
|
|
460
686
|
validate: (value) => {
|
|
@@ -465,28 +691,29 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
465
691
|
return void 0;
|
|
466
692
|
}
|
|
467
693
|
});
|
|
468
|
-
if (
|
|
469
|
-
|
|
694
|
+
if (p2.isCancel(newKey)) {
|
|
695
|
+
p2.cancel("Cancelled.");
|
|
470
696
|
process.exit(0);
|
|
471
697
|
}
|
|
472
|
-
const
|
|
473
|
-
|
|
698
|
+
const spinner5 = p2.spinner();
|
|
699
|
+
spinner5.start("Validating API key...");
|
|
474
700
|
const api = new PicaApi(newKey);
|
|
475
701
|
const isValid = await api.validateApiKey();
|
|
476
702
|
if (!isValid) {
|
|
477
|
-
|
|
478
|
-
|
|
703
|
+
spinner5.stop("Invalid API key");
|
|
704
|
+
p2.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
|
|
479
705
|
process.exit(1);
|
|
480
706
|
}
|
|
481
|
-
|
|
707
|
+
spinner5.stop("API key validated");
|
|
708
|
+
const ac = getAccessControl();
|
|
482
709
|
const reinstalled = [];
|
|
483
710
|
for (const s of statuses) {
|
|
484
711
|
if (s.globalMcp) {
|
|
485
|
-
installMcpConfig(s.agent, newKey, "global");
|
|
712
|
+
installMcpConfig(s.agent, newKey, "global", ac);
|
|
486
713
|
reinstalled.push(`${s.agent.name} (global)`);
|
|
487
714
|
}
|
|
488
715
|
if (s.projectMcp) {
|
|
489
|
-
installMcpConfig(s.agent, newKey, "project");
|
|
716
|
+
installMcpConfig(s.agent, newKey, "project", ac);
|
|
490
717
|
reinstalled.push(`${s.agent.name} (project)`);
|
|
491
718
|
}
|
|
492
719
|
}
|
|
@@ -494,108 +721,111 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
494
721
|
writeConfig({
|
|
495
722
|
apiKey: newKey,
|
|
496
723
|
installedAgents: config?.installedAgents ?? [],
|
|
497
|
-
createdAt: config?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
724
|
+
createdAt: config?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
725
|
+
accessControl: config?.accessControl
|
|
498
726
|
});
|
|
499
727
|
if (reinstalled.length > 0) {
|
|
500
|
-
|
|
728
|
+
p2.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
|
|
501
729
|
}
|
|
502
|
-
|
|
730
|
+
p2.outro("API key updated.");
|
|
503
731
|
}
|
|
504
732
|
async function handleInstallMore(apiKey, missing) {
|
|
733
|
+
const ac = getAccessControl();
|
|
505
734
|
if (missing.length === 1) {
|
|
506
735
|
const agent = missing[0].agent;
|
|
507
|
-
const
|
|
736
|
+
const confirm3 = await p2.confirm({
|
|
508
737
|
message: `Install Pica MCP to ${agent.name}?`,
|
|
509
738
|
initialValue: true
|
|
510
739
|
});
|
|
511
|
-
if (
|
|
512
|
-
|
|
740
|
+
if (p2.isCancel(confirm3) || !confirm3) {
|
|
741
|
+
p2.outro("No changes made.");
|
|
513
742
|
return;
|
|
514
743
|
}
|
|
515
|
-
installMcpConfig(agent, apiKey, "global");
|
|
744
|
+
installMcpConfig(agent, apiKey, "global", ac);
|
|
516
745
|
updateConfigAgents(agent.id);
|
|
517
|
-
|
|
518
|
-
|
|
746
|
+
p2.log.success(`${agent.name}: MCP installed`);
|
|
747
|
+
p2.outro("Done.");
|
|
519
748
|
return;
|
|
520
749
|
}
|
|
521
|
-
const selected = await
|
|
750
|
+
const selected = await p2.multiselect({
|
|
522
751
|
message: "Select agents to install MCP:",
|
|
523
752
|
options: missing.map((s) => ({
|
|
524
753
|
value: s.agent.id,
|
|
525
754
|
label: s.agent.name
|
|
526
755
|
}))
|
|
527
756
|
});
|
|
528
|
-
if (
|
|
529
|
-
|
|
757
|
+
if (p2.isCancel(selected)) {
|
|
758
|
+
p2.outro("No changes made.");
|
|
530
759
|
return;
|
|
531
760
|
}
|
|
532
761
|
const agents = missing.filter((s) => selected.includes(s.agent.id));
|
|
533
762
|
for (const s of agents) {
|
|
534
|
-
installMcpConfig(s.agent, apiKey, "global");
|
|
763
|
+
installMcpConfig(s.agent, apiKey, "global", ac);
|
|
535
764
|
updateConfigAgents(s.agent.id);
|
|
536
|
-
|
|
765
|
+
p2.log.success(`${s.agent.name}: MCP installed`);
|
|
537
766
|
}
|
|
538
|
-
|
|
767
|
+
p2.outro("Done.");
|
|
539
768
|
}
|
|
540
769
|
async function handleInstallProject(apiKey, missing) {
|
|
770
|
+
const ac = getAccessControl();
|
|
541
771
|
if (missing.length === 1) {
|
|
542
772
|
const agent = missing[0].agent;
|
|
543
|
-
const
|
|
773
|
+
const confirm3 = await p2.confirm({
|
|
544
774
|
message: `Install project-level MCP for ${agent.name}?`,
|
|
545
775
|
initialValue: true
|
|
546
776
|
});
|
|
547
|
-
if (
|
|
548
|
-
|
|
777
|
+
if (p2.isCancel(confirm3) || !confirm3) {
|
|
778
|
+
p2.outro("No changes made.");
|
|
549
779
|
return;
|
|
550
780
|
}
|
|
551
|
-
installMcpConfig(agent, apiKey, "project");
|
|
781
|
+
installMcpConfig(agent, apiKey, "project", ac);
|
|
552
782
|
const configPath = getAgentConfigPath(agent, "project");
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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."),
|
|
556
786
|
"Tip"
|
|
557
787
|
);
|
|
558
|
-
|
|
788
|
+
p2.outro("Done.");
|
|
559
789
|
return;
|
|
560
790
|
}
|
|
561
|
-
const selected = await
|
|
791
|
+
const selected = await p2.multiselect({
|
|
562
792
|
message: "Select agents for project-level MCP:",
|
|
563
793
|
options: missing.map((s) => ({
|
|
564
794
|
value: s.agent.id,
|
|
565
795
|
label: s.agent.name
|
|
566
796
|
}))
|
|
567
797
|
});
|
|
568
|
-
if (
|
|
569
|
-
|
|
798
|
+
if (p2.isCancel(selected)) {
|
|
799
|
+
p2.outro("No changes made.");
|
|
570
800
|
return;
|
|
571
801
|
}
|
|
572
802
|
const agents = missing.filter((s) => selected.includes(s.agent.id));
|
|
573
803
|
for (const s of agents) {
|
|
574
|
-
installMcpConfig(s.agent, apiKey, "project");
|
|
804
|
+
installMcpConfig(s.agent, apiKey, "project", ac);
|
|
575
805
|
const configPath = getAgentConfigPath(s.agent, "project");
|
|
576
|
-
|
|
806
|
+
p2.log.success(`${s.agent.name}: ${configPath} created`);
|
|
577
807
|
}
|
|
578
|
-
|
|
579
|
-
|
|
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."),
|
|
580
810
|
"Tip"
|
|
581
811
|
);
|
|
582
|
-
|
|
812
|
+
p2.outro("Done.");
|
|
583
813
|
}
|
|
584
814
|
async function freshSetup(options) {
|
|
585
|
-
|
|
586
|
-
${
|
|
587
|
-
const openBrowser = await
|
|
815
|
+
p2.note(`Get your API key at:
|
|
816
|
+
${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
817
|
+
const openBrowser = await p2.confirm({
|
|
588
818
|
message: "Open browser to get API key?",
|
|
589
819
|
initialValue: true
|
|
590
820
|
});
|
|
591
|
-
if (
|
|
592
|
-
|
|
821
|
+
if (p2.isCancel(openBrowser)) {
|
|
822
|
+
p2.cancel("Setup cancelled.");
|
|
593
823
|
process.exit(0);
|
|
594
824
|
}
|
|
595
825
|
if (openBrowser) {
|
|
596
826
|
await openApiKeyPage();
|
|
597
827
|
}
|
|
598
|
-
const apiKey = await
|
|
828
|
+
const apiKey = await p2.text({
|
|
599
829
|
message: "Enter your Pica API key:",
|
|
600
830
|
placeholder: "sk_live_...",
|
|
601
831
|
validate: (value) => {
|
|
@@ -606,27 +836,27 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
606
836
|
return void 0;
|
|
607
837
|
}
|
|
608
838
|
});
|
|
609
|
-
if (
|
|
610
|
-
|
|
839
|
+
if (p2.isCancel(apiKey)) {
|
|
840
|
+
p2.cancel("Setup cancelled.");
|
|
611
841
|
process.exit(0);
|
|
612
842
|
}
|
|
613
|
-
const
|
|
614
|
-
|
|
843
|
+
const spinner5 = p2.spinner();
|
|
844
|
+
spinner5.start("Validating API key...");
|
|
615
845
|
const api = new PicaApi(apiKey);
|
|
616
846
|
const isValid = await api.validateApiKey();
|
|
617
847
|
if (!isValid) {
|
|
618
|
-
|
|
619
|
-
|
|
848
|
+
spinner5.stop("Invalid API key");
|
|
849
|
+
p2.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
|
|
620
850
|
process.exit(1);
|
|
621
851
|
}
|
|
622
|
-
|
|
852
|
+
spinner5.stop("API key validated");
|
|
623
853
|
writeConfig({
|
|
624
854
|
apiKey,
|
|
625
855
|
installedAgents: [],
|
|
626
856
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
627
857
|
});
|
|
628
858
|
const allAgents = getAllAgents();
|
|
629
|
-
const agentChoice = await
|
|
859
|
+
const agentChoice = await p2.select({
|
|
630
860
|
message: "Where do you want to install the MCP?",
|
|
631
861
|
options: [
|
|
632
862
|
{
|
|
@@ -640,8 +870,8 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
640
870
|
}))
|
|
641
871
|
]
|
|
642
872
|
});
|
|
643
|
-
if (
|
|
644
|
-
|
|
873
|
+
if (p2.isCancel(agentChoice)) {
|
|
874
|
+
p2.cancel("Setup cancelled.");
|
|
645
875
|
process.exit(0);
|
|
646
876
|
}
|
|
647
877
|
const selectedAgents = agentChoice === "all" ? allAgents : allAgents.filter((a) => a.id === agentChoice);
|
|
@@ -652,7 +882,7 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
652
882
|
} else if (options.project) {
|
|
653
883
|
scope = "project";
|
|
654
884
|
} else if (hasProjectScopeAgent) {
|
|
655
|
-
const scopeChoice = await
|
|
885
|
+
const scopeChoice = await p2.select({
|
|
656
886
|
message: "How do you want to install it?",
|
|
657
887
|
options: [
|
|
658
888
|
{
|
|
@@ -667,8 +897,8 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
667
897
|
}
|
|
668
898
|
]
|
|
669
899
|
});
|
|
670
|
-
if (
|
|
671
|
-
|
|
900
|
+
if (p2.isCancel(scopeChoice)) {
|
|
901
|
+
p2.cancel("Setup cancelled.");
|
|
672
902
|
process.exit(0);
|
|
673
903
|
}
|
|
674
904
|
scope = scopeChoice;
|
|
@@ -678,12 +908,12 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
678
908
|
const nonProjectAgents = selectedAgents.filter((a) => !supportsProjectScope(a));
|
|
679
909
|
if (projectAgents.length === 0) {
|
|
680
910
|
const supported = allAgents.filter((a) => supportsProjectScope(a)).map((a) => a.name).join(", ");
|
|
681
|
-
|
|
911
|
+
p2.note(
|
|
682
912
|
`${selectedAgents.map((a) => a.name).join(", ")} does not support project-level MCP.
|
|
683
913
|
Project scope is supported by: ${supported}`,
|
|
684
914
|
"Not Supported"
|
|
685
915
|
);
|
|
686
|
-
|
|
916
|
+
p2.cancel("Run again and choose global scope or a different agent.");
|
|
687
917
|
process.exit(1);
|
|
688
918
|
}
|
|
689
919
|
for (const agent of projectAgents) {
|
|
@@ -691,15 +921,15 @@ Project scope is supported by: ${supported}`,
|
|
|
691
921
|
installMcpConfig(agent, apiKey, "project");
|
|
692
922
|
const configPath = getAgentConfigPath(agent, "project");
|
|
693
923
|
const status = wasInstalled ? "updated" : "created";
|
|
694
|
-
|
|
924
|
+
p2.log.success(`${agent.name}: ${configPath} ${status}`);
|
|
695
925
|
}
|
|
696
926
|
if (nonProjectAgents.length > 0) {
|
|
697
|
-
|
|
927
|
+
p2.log.info(`Installing globally for agents without project scope support:`);
|
|
698
928
|
for (const agent of nonProjectAgents) {
|
|
699
929
|
const wasInstalled = isMcpInstalled(agent, "global");
|
|
700
930
|
installMcpConfig(agent, apiKey, "global");
|
|
701
931
|
const status = wasInstalled ? "updated" : "installed";
|
|
702
|
-
|
|
932
|
+
p2.log.success(`${agent.name}: MCP ${status} (global)`);
|
|
703
933
|
}
|
|
704
934
|
}
|
|
705
935
|
const allInstalled = [...projectAgents, ...nonProjectAgents];
|
|
@@ -708,23 +938,23 @@ Project scope is supported by: ${supported}`,
|
|
|
708
938
|
installedAgents: allInstalled.map((a) => a.id),
|
|
709
939
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
710
940
|
});
|
|
711
|
-
const configPaths = projectAgents.map((a) => ` ${a.name}: ${
|
|
712
|
-
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())}
|
|
713
943
|
MCP configs:
|
|
714
944
|
${configPaths}
|
|
715
945
|
|
|
716
946
|
`;
|
|
717
947
|
if (nonProjectAgents.length > 0) {
|
|
718
|
-
const globalPaths = nonProjectAgents.map((a) => ` ${a.name}: ${
|
|
948
|
+
const globalPaths = nonProjectAgents.map((a) => ` ${a.name}: ${pc3.dim(getAgentConfigPath(a, "global"))}`).join("\n");
|
|
719
949
|
summary += `Global configs:
|
|
720
950
|
${globalPaths}
|
|
721
951
|
|
|
722
952
|
`;
|
|
723
953
|
}
|
|
724
|
-
summary +=
|
|
725
|
-
|
|
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");
|
|
726
956
|
await promptConnectIntegrations(apiKey);
|
|
727
|
-
|
|
957
|
+
p2.outro("Your AI agents now have access to Pica integrations!");
|
|
728
958
|
return;
|
|
729
959
|
}
|
|
730
960
|
const installedAgentIds = [];
|
|
@@ -733,34 +963,34 @@ ${globalPaths}
|
|
|
733
963
|
installMcpConfig(agent, apiKey, "global");
|
|
734
964
|
installedAgentIds.push(agent.id);
|
|
735
965
|
const status = wasInstalled ? "updated" : "installed";
|
|
736
|
-
|
|
966
|
+
p2.log.success(`${agent.name}: MCP ${status}`);
|
|
737
967
|
}
|
|
738
968
|
writeConfig({
|
|
739
969
|
apiKey,
|
|
740
970
|
installedAgents: installedAgentIds,
|
|
741
971
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
742
972
|
});
|
|
743
|
-
|
|
744
|
-
`Config saved to: ${
|
|
973
|
+
p2.note(
|
|
974
|
+
`Config saved to: ${pc3.dim(getConfigPath())}`,
|
|
745
975
|
"Setup Complete"
|
|
746
976
|
);
|
|
747
977
|
await promptConnectIntegrations(apiKey);
|
|
748
|
-
|
|
978
|
+
p2.outro("Your AI agents now have access to Pica integrations!");
|
|
749
979
|
}
|
|
750
980
|
function printBanner() {
|
|
751
981
|
console.log();
|
|
752
|
-
console.log(
|
|
753
|
-
console.log(
|
|
754
|
-
console.log(
|
|
755
|
-
console.log(
|
|
756
|
-
console.log(
|
|
757
|
-
console.log(
|
|
758
|
-
console.log(
|
|
759
|
-
console.log(
|
|
760
|
-
console.log(
|
|
761
|
-
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"));
|
|
762
992
|
console.log();
|
|
763
|
-
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"));
|
|
764
994
|
console.log();
|
|
765
995
|
}
|
|
766
996
|
var TOP_INTEGRATIONS = [
|
|
@@ -794,48 +1024,48 @@ async function promptConnectIntegrations(apiKey) {
|
|
|
794
1024
|
{ value: "skip", label: "Skip for now", hint: "you can always run pica add later" }
|
|
795
1025
|
];
|
|
796
1026
|
const message = first ? "Connect your first integration?" : "Connect another?";
|
|
797
|
-
const choice = await
|
|
798
|
-
if (
|
|
1027
|
+
const choice = await p2.select({ message, options });
|
|
1028
|
+
if (p2.isCancel(choice) || choice === "skip") {
|
|
799
1029
|
break;
|
|
800
1030
|
}
|
|
801
1031
|
if (choice === "more") {
|
|
802
1032
|
try {
|
|
803
1033
|
await open2("https://app.picaos.com/connections");
|
|
804
|
-
|
|
1034
|
+
p2.log.info("Opened Pica dashboard in browser.");
|
|
805
1035
|
} catch {
|
|
806
|
-
|
|
1036
|
+
p2.note("https://app.picaos.com/connections", "Open in browser");
|
|
807
1037
|
}
|
|
808
|
-
|
|
1038
|
+
p2.log.info(`Connect from the dashboard, or use ${pc3.cyan("pica add <platform>")}`);
|
|
809
1039
|
break;
|
|
810
1040
|
}
|
|
811
1041
|
const platform = choice;
|
|
812
1042
|
const integration = TOP_INTEGRATIONS.find((i) => i.value === platform);
|
|
813
1043
|
const label = integration?.label ?? platform;
|
|
814
|
-
|
|
1044
|
+
p2.log.info(`Opening browser to connect ${pc3.cyan(label)}...`);
|
|
815
1045
|
try {
|
|
816
1046
|
await openConnectionPage(platform);
|
|
817
1047
|
} catch {
|
|
818
1048
|
const url = getConnectionUrl(platform);
|
|
819
|
-
|
|
820
|
-
|
|
1049
|
+
p2.log.warn("Could not open browser automatically.");
|
|
1050
|
+
p2.note(url, "Open manually");
|
|
821
1051
|
}
|
|
822
|
-
const
|
|
823
|
-
|
|
1052
|
+
const spinner5 = p2.spinner();
|
|
1053
|
+
spinner5.start("Waiting for connection... (complete auth in browser)");
|
|
824
1054
|
try {
|
|
825
1055
|
await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
|
|
826
|
-
|
|
827
|
-
|
|
1056
|
+
spinner5.stop(`${label} connected!`);
|
|
1057
|
+
p2.log.success(`${pc3.green("\u2713")} ${label} is now available to your AI agents`);
|
|
828
1058
|
connected.push(platform);
|
|
829
1059
|
first = false;
|
|
830
1060
|
} catch (error) {
|
|
831
|
-
|
|
1061
|
+
spinner5.stop("Connection timed out");
|
|
832
1062
|
if (error instanceof TimeoutError) {
|
|
833
|
-
|
|
1063
|
+
p2.log.warn(`No worries. Connect later with: ${pc3.cyan(`pica add ${platform}`)}`);
|
|
834
1064
|
}
|
|
835
1065
|
first = false;
|
|
836
1066
|
}
|
|
837
1067
|
if (TOP_INTEGRATIONS.every((i) => connected.includes(i.value))) {
|
|
838
|
-
|
|
1068
|
+
p2.log.success("All top integrations connected!");
|
|
839
1069
|
break;
|
|
840
1070
|
}
|
|
841
1071
|
}
|
|
@@ -854,23 +1084,23 @@ function updateConfigAgents(agentId) {
|
|
|
854
1084
|
}
|
|
855
1085
|
|
|
856
1086
|
// src/commands/connection.ts
|
|
857
|
-
import * as
|
|
858
|
-
import
|
|
1087
|
+
import * as p3 from "@clack/prompts";
|
|
1088
|
+
import pc4 from "picocolors";
|
|
859
1089
|
|
|
860
1090
|
// src/lib/platforms.ts
|
|
861
1091
|
function findPlatform(platforms, query) {
|
|
862
1092
|
const normalizedQuery = query.toLowerCase().trim();
|
|
863
1093
|
const exact = platforms.find(
|
|
864
|
-
(
|
|
1094
|
+
(p5) => p5.platform.toLowerCase() === normalizedQuery || p5.name.toLowerCase() === normalizedQuery
|
|
865
1095
|
);
|
|
866
1096
|
if (exact) return exact;
|
|
867
1097
|
return null;
|
|
868
1098
|
}
|
|
869
1099
|
function findSimilarPlatforms(platforms, query, limit = 3) {
|
|
870
1100
|
const normalizedQuery = query.toLowerCase().trim();
|
|
871
|
-
const scored = platforms.map((
|
|
872
|
-
const name =
|
|
873
|
-
const slug =
|
|
1101
|
+
const scored = platforms.map((p5) => {
|
|
1102
|
+
const name = p5.name.toLowerCase();
|
|
1103
|
+
const slug = p5.platform.toLowerCase();
|
|
874
1104
|
let score = 0;
|
|
875
1105
|
if (name.includes(normalizedQuery) || slug.includes(normalizedQuery)) {
|
|
876
1106
|
score = 10;
|
|
@@ -879,7 +1109,7 @@ function findSimilarPlatforms(platforms, query, limit = 3) {
|
|
|
879
1109
|
} else {
|
|
880
1110
|
score = countMatchingChars(normalizedQuery, slug);
|
|
881
1111
|
}
|
|
882
|
-
return { platform:
|
|
1112
|
+
return { platform: p5, score };
|
|
883
1113
|
}).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
884
1114
|
return scored.map((item) => item.platform);
|
|
885
1115
|
}
|
|
@@ -894,22 +1124,22 @@ function countMatchingChars(a, b) {
|
|
|
894
1124
|
|
|
895
1125
|
// src/commands/connection.ts
|
|
896
1126
|
async function connectionAddCommand(platformArg) {
|
|
897
|
-
|
|
1127
|
+
p3.intro(pc4.bgCyan(pc4.black(" Pica ")));
|
|
898
1128
|
const apiKey = getApiKey();
|
|
899
1129
|
if (!apiKey) {
|
|
900
|
-
|
|
1130
|
+
p3.cancel("Not configured. Run `pica init` first.");
|
|
901
1131
|
process.exit(1);
|
|
902
1132
|
}
|
|
903
1133
|
const api = new PicaApi(apiKey);
|
|
904
|
-
const
|
|
905
|
-
|
|
1134
|
+
const spinner5 = p3.spinner();
|
|
1135
|
+
spinner5.start("Loading platforms...");
|
|
906
1136
|
let platforms;
|
|
907
1137
|
try {
|
|
908
1138
|
platforms = await api.listPlatforms();
|
|
909
|
-
|
|
1139
|
+
spinner5.stop(`${platforms.length} platforms available`);
|
|
910
1140
|
} catch (error) {
|
|
911
|
-
|
|
912
|
-
|
|
1141
|
+
spinner5.stop("Failed to load platforms");
|
|
1142
|
+
p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
913
1143
|
process.exit(1);
|
|
914
1144
|
}
|
|
915
1145
|
let platform;
|
|
@@ -920,29 +1150,29 @@ async function connectionAddCommand(platformArg) {
|
|
|
920
1150
|
} else {
|
|
921
1151
|
const similar = findSimilarPlatforms(platforms, platformArg);
|
|
922
1152
|
if (similar.length > 0) {
|
|
923
|
-
|
|
924
|
-
const suggestion = await
|
|
1153
|
+
p3.log.warn(`Unknown platform: ${platformArg}`);
|
|
1154
|
+
const suggestion = await p3.select({
|
|
925
1155
|
message: "Did you mean:",
|
|
926
1156
|
options: [
|
|
927
1157
|
...similar.map((s) => ({ value: s.platform, label: `${s.name} (${s.platform})` })),
|
|
928
1158
|
{ value: "__other__", label: "None of these" }
|
|
929
1159
|
]
|
|
930
1160
|
});
|
|
931
|
-
if (
|
|
932
|
-
|
|
933
|
-
|
|
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.");
|
|
934
1164
|
process.exit(0);
|
|
935
1165
|
}
|
|
936
1166
|
platform = suggestion;
|
|
937
1167
|
} else {
|
|
938
|
-
|
|
1168
|
+
p3.cancel(`Unknown platform: ${platformArg}
|
|
939
1169
|
|
|
940
|
-
Run ${
|
|
1170
|
+
Run ${pc4.cyan("pica platforms")} to see available platforms.`);
|
|
941
1171
|
process.exit(1);
|
|
942
1172
|
}
|
|
943
1173
|
}
|
|
944
1174
|
} else {
|
|
945
|
-
const platformInput = await
|
|
1175
|
+
const platformInput = await p3.text({
|
|
946
1176
|
message: "Which platform do you want to connect?",
|
|
947
1177
|
placeholder: "gmail, slack, hubspot...",
|
|
948
1178
|
validate: (value) => {
|
|
@@ -950,51 +1180,51 @@ Run ${pc3.cyan("pica platforms")} to see available platforms.`);
|
|
|
950
1180
|
return void 0;
|
|
951
1181
|
}
|
|
952
1182
|
});
|
|
953
|
-
if (
|
|
954
|
-
|
|
1183
|
+
if (p3.isCancel(platformInput)) {
|
|
1184
|
+
p3.cancel("Connection cancelled.");
|
|
955
1185
|
process.exit(0);
|
|
956
1186
|
}
|
|
957
1187
|
const found = findPlatform(platforms, platformInput);
|
|
958
1188
|
if (found) {
|
|
959
1189
|
platform = found.platform;
|
|
960
1190
|
} else {
|
|
961
|
-
|
|
1191
|
+
p3.cancel(`Unknown platform: ${platformInput}
|
|
962
1192
|
|
|
963
|
-
Run ${
|
|
1193
|
+
Run ${pc4.cyan("pica platforms")} to see available platforms.`);
|
|
964
1194
|
process.exit(1);
|
|
965
1195
|
}
|
|
966
1196
|
}
|
|
967
1197
|
const url = getConnectionUrl(platform);
|
|
968
|
-
|
|
969
|
-
|
|
1198
|
+
p3.log.info(`Opening browser to connect ${pc4.cyan(platform)}...`);
|
|
1199
|
+
p3.note(pc4.dim(url), "URL");
|
|
970
1200
|
try {
|
|
971
1201
|
await openConnectionPage(platform);
|
|
972
1202
|
} catch {
|
|
973
|
-
|
|
974
|
-
|
|
1203
|
+
p3.log.warn("Could not open browser automatically.");
|
|
1204
|
+
p3.note(`Open this URL manually:
|
|
975
1205
|
${url}`);
|
|
976
1206
|
}
|
|
977
|
-
const pollSpinner =
|
|
1207
|
+
const pollSpinner = p3.spinner();
|
|
978
1208
|
pollSpinner.start("Waiting for connection... (complete auth in browser)");
|
|
979
1209
|
try {
|
|
980
1210
|
const connection2 = await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
|
|
981
1211
|
pollSpinner.stop(`${platform} connected!`);
|
|
982
|
-
|
|
983
|
-
|
|
1212
|
+
p3.log.success(`${pc4.green("\u2713")} ${connection2.platform} is now available to your AI agents.`);
|
|
1213
|
+
p3.outro("Connection complete!");
|
|
984
1214
|
} catch (error) {
|
|
985
1215
|
pollSpinner.stop("Connection timed out");
|
|
986
1216
|
if (error instanceof TimeoutError) {
|
|
987
|
-
|
|
1217
|
+
p3.note(
|
|
988
1218
|
`Possible issues:
|
|
989
1219
|
- OAuth flow was not completed in the browser
|
|
990
1220
|
- Browser popup was blocked
|
|
991
1221
|
- Wrong account selected
|
|
992
1222
|
|
|
993
|
-
Try again with: ${
|
|
1223
|
+
Try again with: ${pc4.cyan(`pica connection add ${platform}`)}`,
|
|
994
1224
|
"Timed Out"
|
|
995
1225
|
);
|
|
996
1226
|
} else {
|
|
997
|
-
|
|
1227
|
+
p3.log.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
998
1228
|
}
|
|
999
1229
|
process.exit(1);
|
|
1000
1230
|
}
|
|
@@ -1002,20 +1232,20 @@ Try again with: ${pc3.cyan(`pica connection add ${platform}`)}`,
|
|
|
1002
1232
|
async function connectionListCommand() {
|
|
1003
1233
|
const apiKey = getApiKey();
|
|
1004
1234
|
if (!apiKey) {
|
|
1005
|
-
|
|
1235
|
+
p3.cancel("Not configured. Run `pica init` first.");
|
|
1006
1236
|
process.exit(1);
|
|
1007
1237
|
}
|
|
1008
1238
|
const api = new PicaApi(apiKey);
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1239
|
+
const spinner5 = p3.spinner();
|
|
1240
|
+
spinner5.start("Loading connections...");
|
|
1011
1241
|
try {
|
|
1012
1242
|
const connections = await api.listConnections();
|
|
1013
|
-
|
|
1243
|
+
spinner5.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
|
|
1014
1244
|
if (connections.length === 0) {
|
|
1015
|
-
|
|
1245
|
+
p3.note(
|
|
1016
1246
|
`No connections yet.
|
|
1017
1247
|
|
|
1018
|
-
Add one with: ${
|
|
1248
|
+
Add one with: ${pc4.cyan("pica connection add gmail")}`,
|
|
1019
1249
|
"No Connections"
|
|
1020
1250
|
);
|
|
1021
1251
|
return;
|
|
@@ -1032,46 +1262,46 @@ Add one with: ${pc3.cyan("pica connection add gmail")}`,
|
|
|
1032
1262
|
{ key: "status", label: "" },
|
|
1033
1263
|
{ key: "platform", label: "Platform" },
|
|
1034
1264
|
{ key: "state", label: "Status" },
|
|
1035
|
-
{ key: "key", label: "Connection Key", color:
|
|
1265
|
+
{ key: "key", label: "Connection Key", color: pc4.dim }
|
|
1036
1266
|
],
|
|
1037
1267
|
rows
|
|
1038
1268
|
);
|
|
1039
1269
|
console.log();
|
|
1040
|
-
|
|
1270
|
+
p3.note(`Add more with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
|
|
1041
1271
|
} catch (error) {
|
|
1042
|
-
|
|
1043
|
-
|
|
1272
|
+
spinner5.stop("Failed to load connections");
|
|
1273
|
+
p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1044
1274
|
process.exit(1);
|
|
1045
1275
|
}
|
|
1046
1276
|
}
|
|
1047
1277
|
function getStatusIndicator(state) {
|
|
1048
1278
|
switch (state) {
|
|
1049
1279
|
case "operational":
|
|
1050
|
-
return
|
|
1280
|
+
return pc4.green("\u25CF");
|
|
1051
1281
|
case "degraded":
|
|
1052
|
-
return
|
|
1282
|
+
return pc4.yellow("\u25CF");
|
|
1053
1283
|
case "failed":
|
|
1054
|
-
return
|
|
1284
|
+
return pc4.red("\u25CF");
|
|
1055
1285
|
default:
|
|
1056
|
-
return
|
|
1286
|
+
return pc4.dim("\u25CB");
|
|
1057
1287
|
}
|
|
1058
1288
|
}
|
|
1059
1289
|
|
|
1060
1290
|
// src/commands/platforms.ts
|
|
1061
|
-
import * as
|
|
1062
|
-
import
|
|
1291
|
+
import * as p4 from "@clack/prompts";
|
|
1292
|
+
import pc5 from "picocolors";
|
|
1063
1293
|
async function platformsCommand(options) {
|
|
1064
1294
|
const apiKey = getApiKey();
|
|
1065
1295
|
if (!apiKey) {
|
|
1066
|
-
|
|
1296
|
+
p4.cancel("Not configured. Run `pica init` first.");
|
|
1067
1297
|
process.exit(1);
|
|
1068
1298
|
}
|
|
1069
1299
|
const api = new PicaApi(apiKey);
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1300
|
+
const spinner5 = p4.spinner();
|
|
1301
|
+
spinner5.start("Loading platforms...");
|
|
1072
1302
|
try {
|
|
1073
1303
|
const platforms = await api.listPlatforms();
|
|
1074
|
-
|
|
1304
|
+
spinner5.stop(`${platforms.length} platforms available`);
|
|
1075
1305
|
if (options.json) {
|
|
1076
1306
|
console.log(JSON.stringify(platforms, null, 2));
|
|
1077
1307
|
return;
|
|
@@ -1089,7 +1319,7 @@ async function platformsCommand(options) {
|
|
|
1089
1319
|
const categoryPlatforms = byCategory.get(options.category);
|
|
1090
1320
|
if (!categoryPlatforms) {
|
|
1091
1321
|
const categories = [...byCategory.keys()].sort();
|
|
1092
|
-
|
|
1322
|
+
p4.note(`Available categories:
|
|
1093
1323
|
${categories.join(", ")}`, "Unknown Category");
|
|
1094
1324
|
process.exit(1);
|
|
1095
1325
|
}
|
|
@@ -1121,10 +1351,10 @@ async function platformsCommand(options) {
|
|
|
1121
1351
|
);
|
|
1122
1352
|
}
|
|
1123
1353
|
console.log();
|
|
1124
|
-
|
|
1354
|
+
p4.note(`Connect with: ${pc5.cyan("pica connection add <platform>")}`, "Tip");
|
|
1125
1355
|
} catch (error) {
|
|
1126
|
-
|
|
1127
|
-
|
|
1356
|
+
spinner5.stop("Failed to load platforms");
|
|
1357
|
+
p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1128
1358
|
process.exit(1);
|
|
1129
1359
|
}
|
|
1130
1360
|
}
|
|
@@ -1137,6 +1367,9 @@ program.name("pica").description("CLI for managing Pica").version(version);
|
|
|
1137
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) => {
|
|
1138
1368
|
await initCommand(options);
|
|
1139
1369
|
});
|
|
1370
|
+
program.command("config").description("Configure MCP access control (permissions, connections, actions)").action(async () => {
|
|
1371
|
+
await configCommand();
|
|
1372
|
+
});
|
|
1140
1373
|
var connection = program.command("connection").description("Manage connections");
|
|
1141
1374
|
connection.command("add [platform]").alias("a").description("Add a new connection").action(async (platform) => {
|
|
1142
1375
|
await connectionAddCommand(platform);
|