@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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +428 -195
  3. 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 p from "@clack/prompts";
9
- import pc2 from "picocolors";
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(p4) {
51
- if (p4.startsWith("~/")) {
52
- return path2.join(os2.homedir(), p4.slice(2));
76
+ function expandPath(p5) {
77
+ if (p5.startsWith("~/")) {
78
+ return path2.join(os2.homedir(), p5.slice(2));
53
79
  }
54
- return p4;
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 text3 = await response.text();
243
- throw new ApiError(response.status, text3 || `HTTP ${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 pc from "picocolors";
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 pc.dim(padded);
544
+ return pc2.dim(padded);
336
545
  }).join(gap);
337
546
  console.log(`${indent}${header}`);
338
- const separator = columns.map((_, i) => pc.dim("\u2500".repeat(widths[i]))).join(gap);
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
- p.intro(pc2.bgCyan(pc2.black(" Pica ")));
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(` ${pc2.bold("Current Setup")}`);
374
- console.log(` ${pc2.dim("\u2500".repeat(42))}`);
375
- console.log(` ${pc2.dim("API Key:")} ${masked}`);
376
- console.log(` ${pc2.dim("Config:")} ${getConfigPath()}`);
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 ? pc2.dim("-") : s.globalMcp ? pc2.green("\u25CF yes") : pc2.yellow("\u25CB no"),
387
- project: s.projectMcp === null ? pc2.dim("-") : s.projectMcp ? pc2.green("\u25CF yes") : pc2.yellow("\u25CB no")
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(` ${pc2.dim("- = not detected on this machine")}`);
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 p.select({
643
+ const action = await p2.select({
421
644
  message: "What would you like to do?",
422
645
  options: actionOptions
423
646
  });
424
- if (p.isCancel(action)) {
425
- p.outro("No changes made.");
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
- p.note(`Get your API key at:
445
- ${pc2.cyan(getApiKeyUrl())}`, "API Key");
446
- const openBrowser = await p.confirm({
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 (p.isCancel(openBrowser)) {
451
- p.cancel("Cancelled.");
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 p.text({
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 (p.isCancel(newKey)) {
469
- p.cancel("Cancelled.");
694
+ if (p2.isCancel(newKey)) {
695
+ p2.cancel("Cancelled.");
470
696
  process.exit(0);
471
697
  }
472
- const spinner4 = p.spinner();
473
- spinner4.start("Validating API key...");
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
- spinner4.stop("Invalid API key");
478
- p.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
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
- spinner4.stop("API key validated");
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
- p.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
728
+ p2.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
501
729
  }
502
- p.outro("API key updated.");
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 confirm2 = await p.confirm({
736
+ const confirm3 = await p2.confirm({
508
737
  message: `Install Pica MCP to ${agent.name}?`,
509
738
  initialValue: true
510
739
  });
511
- if (p.isCancel(confirm2) || !confirm2) {
512
- p.outro("No changes made.");
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
- p.log.success(`${agent.name}: MCP installed`);
518
- p.outro("Done.");
746
+ p2.log.success(`${agent.name}: MCP installed`);
747
+ p2.outro("Done.");
519
748
  return;
520
749
  }
521
- const selected = await p.multiselect({
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 (p.isCancel(selected)) {
529
- p.outro("No changes made.");
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
- p.log.success(`${s.agent.name}: MCP installed`);
765
+ p2.log.success(`${s.agent.name}: MCP installed`);
537
766
  }
538
- p.outro("Done.");
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 confirm2 = await p.confirm({
773
+ const confirm3 = await p2.confirm({
544
774
  message: `Install project-level MCP for ${agent.name}?`,
545
775
  initialValue: true
546
776
  });
547
- if (p.isCancel(confirm2) || !confirm2) {
548
- p.outro("No changes made.");
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
- p.log.success(`${agent.name}: ${configPath} created`);
554
- p.note(
555
- pc2.yellow("Project config files can be committed to share with your team.\n") + pc2.yellow("Team members will need their own API key."),
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
- p.outro("Done.");
788
+ p2.outro("Done.");
559
789
  return;
560
790
  }
561
- const selected = await p.multiselect({
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 (p.isCancel(selected)) {
569
- p.outro("No changes made.");
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
- p.log.success(`${s.agent.name}: ${configPath} created`);
806
+ p2.log.success(`${s.agent.name}: ${configPath} created`);
577
807
  }
578
- p.note(
579
- pc2.yellow("Project config files can be committed to share with your team.\n") + pc2.yellow("Team members will need their own API key."),
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
- p.outro("Done.");
812
+ p2.outro("Done.");
583
813
  }
584
814
  async function freshSetup(options) {
585
- p.note(`Get your API key at:
586
- ${pc2.cyan(getApiKeyUrl())}`, "API Key");
587
- const openBrowser = await p.confirm({
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 (p.isCancel(openBrowser)) {
592
- p.cancel("Setup cancelled.");
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 p.text({
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 (p.isCancel(apiKey)) {
610
- p.cancel("Setup cancelled.");
839
+ if (p2.isCancel(apiKey)) {
840
+ p2.cancel("Setup cancelled.");
611
841
  process.exit(0);
612
842
  }
613
- const spinner4 = p.spinner();
614
- spinner4.start("Validating API key...");
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
- spinner4.stop("Invalid API key");
619
- p.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
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
- spinner4.stop("API key validated");
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 p.select({
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 (p.isCancel(agentChoice)) {
644
- p.cancel("Setup cancelled.");
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 p.select({
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 (p.isCancel(scopeChoice)) {
671
- p.cancel("Setup cancelled.");
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
- p.note(
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
- p.cancel("Run again and choose global scope or a different agent.");
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
- p.log.success(`${agent.name}: ${configPath} ${status}`);
924
+ p2.log.success(`${agent.name}: ${configPath} ${status}`);
695
925
  }
696
926
  if (nonProjectAgents.length > 0) {
697
- p.log.info(`Installing globally for agents without project scope support:`);
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
- p.log.success(`${agent.name}: MCP ${status} (global)`);
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}: ${pc2.dim(getAgentConfigPath(a, "project"))}`).join("\n");
712
- let summary = `Config saved to: ${pc2.dim(getConfigPath())}
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}: ${pc2.dim(getAgentConfigPath(a, "global"))}`).join("\n");
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 += pc2.yellow("Note: Project config files can be committed to share with your team.\n") + pc2.yellow("Team members will need their own API key.");
725
- p.note(summary, "Setup Complete");
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
- p.outro("Your AI agents now have access to Pica integrations!");
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
- p.log.success(`${agent.name}: MCP ${status}`);
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
- p.note(
744
- `Config saved to: ${pc2.dim(getConfigPath())}`,
973
+ p2.note(
974
+ `Config saved to: ${pc3.dim(getConfigPath())}`,
745
975
  "Setup Complete"
746
976
  );
747
977
  await promptConnectIntegrations(apiKey);
748
- p.outro("Your AI agents now have access to Pica integrations!");
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(pc2.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"));
753
- console.log(pc2.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"));
754
- console.log(pc2.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"));
755
- console.log(pc2.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"));
756
- console.log(pc2.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"));
757
- console.log(pc2.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"));
758
- console.log(pc2.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"));
759
- console.log(pc2.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"));
760
- console.log(pc2.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"));
761
- console.log(pc2.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"));
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(pc2.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"));
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 p.select({ message, options });
798
- if (p.isCancel(choice) || choice === "skip") {
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
- p.log.info("Opened Pica dashboard in browser.");
1034
+ p2.log.info("Opened Pica dashboard in browser.");
805
1035
  } catch {
806
- p.note("https://app.picaos.com/connections", "Open in browser");
1036
+ p2.note("https://app.picaos.com/connections", "Open in browser");
807
1037
  }
808
- p.log.info(`Connect from the dashboard, or use ${pc2.cyan("pica add <platform>")}`);
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
- p.log.info(`Opening browser to connect ${pc2.cyan(label)}...`);
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
- p.log.warn("Could not open browser automatically.");
820
- p.note(url, "Open manually");
1049
+ p2.log.warn("Could not open browser automatically.");
1050
+ p2.note(url, "Open manually");
821
1051
  }
822
- const spinner4 = p.spinner();
823
- spinner4.start("Waiting for connection... (complete auth in browser)");
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
- spinner4.stop(`${label} connected!`);
827
- p.log.success(`${pc2.green("\u2713")} ${label} is now available to your AI agents`);
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
- spinner4.stop("Connection timed out");
1061
+ spinner5.stop("Connection timed out");
832
1062
  if (error instanceof TimeoutError) {
833
- p.log.warn(`No worries. Connect later with: ${pc2.cyan(`pica add ${platform}`)}`);
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
- p.log.success("All top integrations connected!");
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 p2 from "@clack/prompts";
858
- import pc3 from "picocolors";
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
- (p4) => p4.platform.toLowerCase() === normalizedQuery || p4.name.toLowerCase() === normalizedQuery
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((p4) => {
872
- const name = p4.name.toLowerCase();
873
- const slug = p4.platform.toLowerCase();
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: p4, score };
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
- p2.intro(pc3.bgCyan(pc3.black(" Pica ")));
1127
+ p3.intro(pc4.bgCyan(pc4.black(" Pica ")));
898
1128
  const apiKey = getApiKey();
899
1129
  if (!apiKey) {
900
- p2.cancel("Not configured. Run `pica init` first.");
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 spinner4 = p2.spinner();
905
- spinner4.start("Loading platforms...");
1134
+ const spinner5 = p3.spinner();
1135
+ spinner5.start("Loading platforms...");
906
1136
  let platforms;
907
1137
  try {
908
1138
  platforms = await api.listPlatforms();
909
- spinner4.stop(`${platforms.length} platforms available`);
1139
+ spinner5.stop(`${platforms.length} platforms available`);
910
1140
  } catch (error) {
911
- spinner4.stop("Failed to load platforms");
912
- p2.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
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
- p2.log.warn(`Unknown platform: ${platformArg}`);
924
- const suggestion = await p2.select({
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 (p2.isCancel(suggestion) || suggestion === "__other__") {
932
- p2.note(`Run ${pc3.cyan("pica platforms")} to see all available platforms.`);
933
- p2.cancel("Connection cancelled.");
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
- p2.cancel(`Unknown platform: ${platformArg}
1168
+ p3.cancel(`Unknown platform: ${platformArg}
939
1169
 
940
- Run ${pc3.cyan("pica platforms")} to see available platforms.`);
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 p2.text({
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 (p2.isCancel(platformInput)) {
954
- p2.cancel("Connection cancelled.");
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
- p2.cancel(`Unknown platform: ${platformInput}
1191
+ p3.cancel(`Unknown platform: ${platformInput}
962
1192
 
963
- Run ${pc3.cyan("pica platforms")} to see available platforms.`);
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
- p2.log.info(`Opening browser to connect ${pc3.cyan(platform)}...`);
969
- p2.note(pc3.dim(url), "URL");
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
- p2.log.warn("Could not open browser automatically.");
974
- p2.note(`Open this URL manually:
1203
+ p3.log.warn("Could not open browser automatically.");
1204
+ p3.note(`Open this URL manually:
975
1205
  ${url}`);
976
1206
  }
977
- const pollSpinner = p2.spinner();
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
- p2.log.success(`${pc3.green("\u2713")} ${connection2.platform} is now available to your AI agents.`);
983
- p2.outro("Connection complete!");
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
- p2.note(
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: ${pc3.cyan(`pica connection add ${platform}`)}`,
1223
+ Try again with: ${pc4.cyan(`pica connection add ${platform}`)}`,
994
1224
  "Timed Out"
995
1225
  );
996
1226
  } else {
997
- p2.log.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
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
- p2.cancel("Not configured. Run `pica init` first.");
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 spinner4 = p2.spinner();
1010
- spinner4.start("Loading connections...");
1239
+ const spinner5 = p3.spinner();
1240
+ spinner5.start("Loading connections...");
1011
1241
  try {
1012
1242
  const connections = await api.listConnections();
1013
- spinner4.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
1243
+ spinner5.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
1014
1244
  if (connections.length === 0) {
1015
- p2.note(
1245
+ p3.note(
1016
1246
  `No connections yet.
1017
1247
 
1018
- Add one with: ${pc3.cyan("pica connection add gmail")}`,
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: pc3.dim }
1265
+ { key: "key", label: "Connection Key", color: pc4.dim }
1036
1266
  ],
1037
1267
  rows
1038
1268
  );
1039
1269
  console.log();
1040
- p2.note(`Add more with: ${pc3.cyan("pica connection add <platform>")}`, "Tip");
1270
+ p3.note(`Add more with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
1041
1271
  } catch (error) {
1042
- spinner4.stop("Failed to load connections");
1043
- p2.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
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 pc3.green("\u25CF");
1280
+ return pc4.green("\u25CF");
1051
1281
  case "degraded":
1052
- return pc3.yellow("\u25CF");
1282
+ return pc4.yellow("\u25CF");
1053
1283
  case "failed":
1054
- return pc3.red("\u25CF");
1284
+ return pc4.red("\u25CF");
1055
1285
  default:
1056
- return pc3.dim("\u25CB");
1286
+ return pc4.dim("\u25CB");
1057
1287
  }
1058
1288
  }
1059
1289
 
1060
1290
  // src/commands/platforms.ts
1061
- import * as p3 from "@clack/prompts";
1062
- import pc4 from "picocolors";
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
- p3.cancel("Not configured. Run `pica init` first.");
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 spinner4 = p3.spinner();
1071
- spinner4.start("Loading platforms...");
1300
+ const spinner5 = p4.spinner();
1301
+ spinner5.start("Loading platforms...");
1072
1302
  try {
1073
1303
  const platforms = await api.listPlatforms();
1074
- spinner4.stop(`${platforms.length} platforms available`);
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
- p3.note(`Available categories:
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
- p3.note(`Connect with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
1354
+ p4.note(`Connect with: ${pc5.cyan("pica connection add <platform>")}`, "Tip");
1125
1355
  } catch (error) {
1126
- spinner4.stop("Failed to load platforms");
1127
- p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picahq/cli",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "CLI for managing Pica",
5
5
  "type": "module",
6
6
  "files": [