@picahq/cli 1.7.0 → 1.8.0

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