@picahq/cli 1.9.1 → 1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +360 -253
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { createRequire } from "module";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/commands/init.ts
8
- import * as p2 from "@clack/prompts";
8
+ import * as p3 from "@clack/prompts";
9
9
  import pc3 from "picocolors";
10
10
 
11
11
  // src/lib/config.ts
@@ -37,9 +37,49 @@ function writeConfig(config) {
37
37
  }
38
38
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
39
39
  }
40
+ function readPicaRc() {
41
+ const rcPath = path.join(process.cwd(), ".picarc");
42
+ if (!fs.existsSync(rcPath)) return {};
43
+ try {
44
+ const content = fs.readFileSync(rcPath, "utf-8");
45
+ const result = {};
46
+ for (const line of content.split("\n")) {
47
+ const trimmed = line.trim();
48
+ if (!trimmed || trimmed.startsWith("#")) continue;
49
+ const eqIndex = trimmed.indexOf("=");
50
+ if (eqIndex === -1) continue;
51
+ const key = trimmed.slice(0, eqIndex).trim();
52
+ const value = trimmed.slice(eqIndex + 1).trim();
53
+ result[key] = value;
54
+ }
55
+ return result;
56
+ } catch {
57
+ return {};
58
+ }
59
+ }
40
60
  function getApiKey() {
41
- const config = readConfig();
42
- return config?.apiKey ?? null;
61
+ if (process.env.PICA_SECRET) return process.env.PICA_SECRET;
62
+ const rc = readPicaRc();
63
+ if (rc.PICA_SECRET) return rc.PICA_SECRET;
64
+ return readConfig()?.apiKey ?? null;
65
+ }
66
+ function getAccessControlFromAllSources() {
67
+ const rc = readPicaRc();
68
+ const fileAc = getAccessControl();
69
+ const merged = { ...fileAc };
70
+ if (rc.PICA_PERMISSIONS) {
71
+ merged.permissions = rc.PICA_PERMISSIONS;
72
+ }
73
+ if (rc.PICA_CONNECTION_KEYS) {
74
+ merged.connectionKeys = rc.PICA_CONNECTION_KEYS.split(",").map((s) => s.trim()).filter(Boolean);
75
+ }
76
+ if (rc.PICA_ACTION_IDS) {
77
+ merged.actionIds = rc.PICA_ACTION_IDS.split(",").map((s) => s.trim()).filter(Boolean);
78
+ }
79
+ if (rc.PICA_KNOWLEDGE_AGENT) {
80
+ merged.knowledgeAgent = rc.PICA_KNOWLEDGE_AGENT === "true";
81
+ }
82
+ return merged;
43
83
  }
44
84
  function getAccessControl() {
45
85
  return readConfig()?.accessControl ?? {};
@@ -73,11 +113,11 @@ import fs2 from "fs";
73
113
  import path2 from "path";
74
114
  import os2 from "os";
75
115
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
76
- function expandPath(p6) {
77
- if (p6.startsWith("~/")) {
78
- return path2.join(os2.homedir(), p6.slice(2));
116
+ function expandPath(p7) {
117
+ if (p7.startsWith("~/")) {
118
+ return path2.join(os2.homedir(), p7.slice(2));
79
119
  }
80
- return p6;
120
+ return p7;
81
121
  }
82
122
  function getClaudeDesktopConfigPath() {
83
123
  switch (process.platform) {
@@ -297,11 +337,11 @@ var PicaApi = class {
297
337
  try {
298
338
  await this.listConnections();
299
339
  return true;
300
- } catch (error) {
301
- if (error instanceof ApiError && error.status === 401) {
340
+ } catch (error2) {
341
+ if (error2 instanceof ApiError && error2.status === 401) {
302
342
  return false;
303
343
  }
304
- throw error;
344
+ throw error2;
305
345
  }
306
346
  }
307
347
  async listConnections() {
@@ -566,15 +606,52 @@ async function openApiKeyPage() {
566
606
  }
567
607
 
568
608
  // src/commands/config.ts
569
- import * as p from "@clack/prompts";
609
+ import * as p2 from "@clack/prompts";
570
610
  import pc from "picocolors";
611
+
612
+ // src/lib/output.ts
613
+ import * as p from "@clack/prompts";
614
+ var _agentMode = false;
615
+ function setAgentMode(value) {
616
+ _agentMode = value;
617
+ }
618
+ function isAgentMode() {
619
+ return _agentMode || process.env.PICA_AGENT === "1";
620
+ }
621
+ function createSpinner() {
622
+ if (isAgentMode()) {
623
+ return { start() {
624
+ }, stop() {
625
+ } };
626
+ }
627
+ return p.spinner();
628
+ }
629
+ function intro2(msg) {
630
+ if (!isAgentMode()) p.intro(msg);
631
+ }
632
+ function json(data) {
633
+ process.stdout.write(JSON.stringify(data) + "\n");
634
+ }
635
+ function error(message, exitCode = 1) {
636
+ if (isAgentMode()) {
637
+ json({ error: message });
638
+ } else {
639
+ p.cancel(message);
640
+ }
641
+ process.exit(exitCode);
642
+ }
643
+
644
+ // src/commands/config.ts
571
645
  async function configCommand() {
646
+ if (isAgentMode()) {
647
+ error("This command requires interactive input. Run without --agent.");
648
+ }
572
649
  const config = readConfig();
573
650
  if (!config) {
574
- p.log.error(`No Pica config found. Run ${pc.cyan("pica init")} first.`);
651
+ p2.log.error(`No Pica config found. Run ${pc.cyan("pica init")} first.`);
575
652
  return;
576
653
  }
577
- p.intro(pc.bgCyan(pc.black(" Pica Access Control ")));
654
+ p2.intro(pc.bgCyan(pc.black(" Pica Access Control ")));
578
655
  const current = getAccessControl();
579
656
  console.log();
580
657
  console.log(` ${pc.bold("Current Access Control")}`);
@@ -584,7 +661,7 @@ async function configCommand() {
584
661
  console.log(` ${pc.dim("Action IDs:")} ${formatList(current.actionIds)}`);
585
662
  console.log(` ${pc.dim("Knowledge only:")} ${current.knowledgeAgent ? "yes" : "no"}`);
586
663
  console.log();
587
- const permissions = await p.select({
664
+ const permissions = await p2.select({
588
665
  message: "Permission level",
589
666
  options: [
590
667
  { value: "admin", label: "Admin", hint: "Full access (GET, POST, PUT, PATCH, DELETE)" },
@@ -593,11 +670,11 @@ async function configCommand() {
593
670
  ],
594
671
  initialValue: current.permissions ?? "admin"
595
672
  });
596
- if (p.isCancel(permissions)) {
597
- p.outro("No changes made.");
673
+ if (p2.isCancel(permissions)) {
674
+ p2.outro("No changes made.");
598
675
  return;
599
676
  }
600
- const connectionMode = await p.select({
677
+ const connectionMode = await p2.select({
601
678
  message: "Connection scope",
602
679
  options: [
603
680
  { value: "all", label: "All connections" },
@@ -605,23 +682,23 @@ async function configCommand() {
605
682
  ],
606
683
  initialValue: current.connectionKeys ? "specific" : "all"
607
684
  });
608
- if (p.isCancel(connectionMode)) {
609
- p.outro("No changes made.");
685
+ if (p2.isCancel(connectionMode)) {
686
+ p2.outro("No changes made.");
610
687
  return;
611
688
  }
612
689
  let connectionKeys;
613
690
  if (connectionMode === "specific") {
614
691
  connectionKeys = await selectConnections(config.apiKey);
615
692
  if (connectionKeys === void 0) {
616
- p.outro("No changes made.");
693
+ p2.outro("No changes made.");
617
694
  return;
618
695
  }
619
696
  if (connectionKeys.length === 0) {
620
- p.log.info(`No connections found. Defaulting to all. Use ${pc.cyan("pica add")} to connect platforms.`);
697
+ p2.log.info(`No connections found. Defaulting to all. Use ${pc.cyan("pica add")} to connect platforms.`);
621
698
  connectionKeys = void 0;
622
699
  }
623
700
  }
624
- const actionMode = await p.select({
701
+ const actionMode = await p2.select({
625
702
  message: "Action scope",
626
703
  options: [
627
704
  { value: "all", label: "All actions" },
@@ -629,13 +706,13 @@ async function configCommand() {
629
706
  ],
630
707
  initialValue: current.actionIds ? "specific" : "all"
631
708
  });
632
- if (p.isCancel(actionMode)) {
633
- p.outro("No changes made.");
709
+ if (p2.isCancel(actionMode)) {
710
+ p2.outro("No changes made.");
634
711
  return;
635
712
  }
636
713
  let actionIds;
637
714
  if (actionMode === "specific") {
638
- const actionInput = await p.text({
715
+ const actionInput = await p2.text({
639
716
  message: "Enter action IDs (comma-separated):",
640
717
  placeholder: "action-id-1, action-id-2",
641
718
  initialValue: current.actionIds?.join(", ") ?? "",
@@ -644,18 +721,18 @@ async function configCommand() {
644
721
  return void 0;
645
722
  }
646
723
  });
647
- if (p.isCancel(actionInput)) {
648
- p.outro("No changes made.");
724
+ if (p2.isCancel(actionInput)) {
725
+ p2.outro("No changes made.");
649
726
  return;
650
727
  }
651
728
  actionIds = actionInput.split(",").map((s) => s.trim()).filter(Boolean);
652
729
  }
653
- const knowledgeAgent = await p.confirm({
730
+ const knowledgeAgent = await p2.confirm({
654
731
  message: "Enable knowledge-only mode? (disables action execution)",
655
732
  initialValue: current.knowledgeAgent ?? false
656
733
  });
657
- if (p.isCancel(knowledgeAgent)) {
658
- p.outro("No changes made.");
734
+ if (p2.isCancel(knowledgeAgent)) {
735
+ p2.outro("No changes made.");
659
736
  return;
660
737
  }
661
738
  const settings = {
@@ -679,22 +756,22 @@ async function configCommand() {
679
756
  }
680
757
  }
681
758
  if (reinstalled.length > 0) {
682
- p.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
759
+ p2.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
683
760
  }
684
- p.outro("Access control updated.");
761
+ p2.outro("Access control updated.");
685
762
  }
686
763
  async function selectConnections(apiKey) {
687
- const spinner6 = p.spinner();
688
- spinner6.start("Fetching connections...");
764
+ const spinner5 = p2.spinner();
765
+ spinner5.start("Fetching connections...");
689
766
  let connections;
690
767
  try {
691
768
  const api = new PicaApi(apiKey);
692
769
  const rawConnections = await api.listConnections();
693
770
  connections = rawConnections.map((c) => ({ platform: c.platform, key: c.key }));
694
- spinner6.stop(`Found ${connections.length} connection(s)`);
771
+ spinner5.stop(`Found ${connections.length} connection(s)`);
695
772
  } catch {
696
- spinner6.stop("Could not fetch connections");
697
- const manual = await p.text({
773
+ spinner5.stop("Could not fetch connections");
774
+ const manual = await p2.text({
698
775
  message: "Enter connection keys manually (comma-separated):",
699
776
  placeholder: "conn_key_1, conn_key_2",
700
777
  validate: (value) => {
@@ -702,13 +779,13 @@ async function selectConnections(apiKey) {
702
779
  return void 0;
703
780
  }
704
781
  });
705
- if (p.isCancel(manual)) return void 0;
782
+ if (p2.isCancel(manual)) return void 0;
706
783
  return manual.split(",").map((s) => s.trim()).filter(Boolean);
707
784
  }
708
785
  if (connections.length === 0) {
709
786
  return [];
710
787
  }
711
- const selected = await p.multiselect({
788
+ const selected = await p2.multiselect({
712
789
  message: "Select connections:",
713
790
  options: connections.map((c) => ({
714
791
  value: c.key,
@@ -716,7 +793,7 @@ async function selectConnections(apiKey) {
716
793
  hint: c.key
717
794
  }))
718
795
  });
719
- if (p.isCancel(selected)) return void 0;
796
+ if (p2.isCancel(selected)) return void 0;
720
797
  return selected;
721
798
  }
722
799
  function formatList(list) {
@@ -766,9 +843,12 @@ function stripAnsi(str) {
766
843
 
767
844
  // src/commands/init.ts
768
845
  async function initCommand(options) {
846
+ if (isAgentMode()) {
847
+ error("This command requires interactive input. Run without --agent.");
848
+ }
769
849
  const existingConfig = readConfig();
770
850
  if (existingConfig) {
771
- p2.intro(pc3.bgCyan(pc3.black(" Pica ")));
851
+ p3.intro(pc3.bgCyan(pc3.black(" Pica ")));
772
852
  await handleExistingConfig(existingConfig.apiKey, options);
773
853
  return;
774
854
  }
@@ -840,12 +920,12 @@ async function handleExistingConfig(apiKey, options) {
840
920
  value: "start-fresh",
841
921
  label: "Start fresh (reconfigure everything)"
842
922
  });
843
- const action = await p2.select({
923
+ const action = await p3.select({
844
924
  message: "What would you like to do?",
845
925
  options: actionOptions
846
926
  });
847
- if (p2.isCancel(action)) {
848
- p2.outro("No changes made.");
927
+ if (p3.isCancel(action)) {
928
+ p3.outro("No changes made.");
849
929
  return;
850
930
  }
851
931
  switch (action) {
@@ -867,20 +947,20 @@ async function handleExistingConfig(apiKey, options) {
867
947
  }
868
948
  }
869
949
  async function handleUpdateKey(statuses) {
870
- p2.note(`Get your API key at:
950
+ p3.note(`Get your API key at:
871
951
  ${pc3.cyan(getApiKeyUrl())}`, "API Key");
872
- const openBrowser = await p2.confirm({
952
+ const openBrowser = await p3.confirm({
873
953
  message: "Open browser to get API key?",
874
954
  initialValue: true
875
955
  });
876
- if (p2.isCancel(openBrowser)) {
877
- p2.cancel("Cancelled.");
956
+ if (p3.isCancel(openBrowser)) {
957
+ p3.cancel("Cancelled.");
878
958
  process.exit(0);
879
959
  }
880
960
  if (openBrowser) {
881
961
  await openApiKeyPage();
882
962
  }
883
- const newKey = await p2.text({
963
+ const newKey = await p3.text({
884
964
  message: "Enter your new Pica API key:",
885
965
  placeholder: "sk_live_...",
886
966
  validate: (value) => {
@@ -891,20 +971,20 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
891
971
  return void 0;
892
972
  }
893
973
  });
894
- if (p2.isCancel(newKey)) {
895
- p2.cancel("Cancelled.");
974
+ if (p3.isCancel(newKey)) {
975
+ p3.cancel("Cancelled.");
896
976
  process.exit(0);
897
977
  }
898
- const spinner6 = p2.spinner();
899
- spinner6.start("Validating API key...");
978
+ const spinner5 = p3.spinner();
979
+ spinner5.start("Validating API key...");
900
980
  const api = new PicaApi(newKey);
901
981
  const isValid = await api.validateApiKey();
902
982
  if (!isValid) {
903
- spinner6.stop("Invalid API key");
904
- p2.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
983
+ spinner5.stop("Invalid API key");
984
+ p3.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
905
985
  process.exit(1);
906
986
  }
907
- spinner6.stop("API key validated");
987
+ spinner5.stop("API key validated");
908
988
  const ac = getAccessControl();
909
989
  const reinstalled = [];
910
990
  for (const s of statuses) {
@@ -925,107 +1005,107 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
925
1005
  accessControl: config?.accessControl
926
1006
  });
927
1007
  if (reinstalled.length > 0) {
928
- p2.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
1008
+ p3.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
929
1009
  }
930
- p2.outro("API key updated.");
1010
+ p3.outro("API key updated.");
931
1011
  }
932
1012
  async function handleInstallMore(apiKey, missing) {
933
1013
  const ac = getAccessControl();
934
1014
  if (missing.length === 1) {
935
1015
  const agent = missing[0].agent;
936
- const confirm3 = await p2.confirm({
1016
+ const confirm3 = await p3.confirm({
937
1017
  message: `Install Pica MCP to ${agent.name}?`,
938
1018
  initialValue: true
939
1019
  });
940
- if (p2.isCancel(confirm3) || !confirm3) {
941
- p2.outro("No changes made.");
1020
+ if (p3.isCancel(confirm3) || !confirm3) {
1021
+ p3.outro("No changes made.");
942
1022
  return;
943
1023
  }
944
1024
  installMcpConfig(agent, apiKey, "global", ac);
945
1025
  updateConfigAgents(agent.id);
946
- p2.log.success(`${agent.name}: MCP installed`);
947
- p2.outro("Done.");
1026
+ p3.log.success(`${agent.name}: MCP installed`);
1027
+ p3.outro("Done.");
948
1028
  return;
949
1029
  }
950
- const selected = await p2.multiselect({
1030
+ const selected = await p3.multiselect({
951
1031
  message: "Select agents to install MCP:",
952
1032
  options: missing.map((s) => ({
953
1033
  value: s.agent.id,
954
1034
  label: s.agent.name
955
1035
  }))
956
1036
  });
957
- if (p2.isCancel(selected)) {
958
- p2.outro("No changes made.");
1037
+ if (p3.isCancel(selected)) {
1038
+ p3.outro("No changes made.");
959
1039
  return;
960
1040
  }
961
1041
  const agents = missing.filter((s) => selected.includes(s.agent.id));
962
1042
  for (const s of agents) {
963
1043
  installMcpConfig(s.agent, apiKey, "global", ac);
964
1044
  updateConfigAgents(s.agent.id);
965
- p2.log.success(`${s.agent.name}: MCP installed`);
1045
+ p3.log.success(`${s.agent.name}: MCP installed`);
966
1046
  }
967
- p2.outro("Done.");
1047
+ p3.outro("Done.");
968
1048
  }
969
1049
  async function handleInstallProject(apiKey, missing) {
970
1050
  const ac = getAccessControl();
971
1051
  if (missing.length === 1) {
972
1052
  const agent = missing[0].agent;
973
- const confirm3 = await p2.confirm({
1053
+ const confirm3 = await p3.confirm({
974
1054
  message: `Install project-level MCP for ${agent.name}?`,
975
1055
  initialValue: true
976
1056
  });
977
- if (p2.isCancel(confirm3) || !confirm3) {
978
- p2.outro("No changes made.");
1057
+ if (p3.isCancel(confirm3) || !confirm3) {
1058
+ p3.outro("No changes made.");
979
1059
  return;
980
1060
  }
981
1061
  installMcpConfig(agent, apiKey, "project", ac);
982
1062
  const configPath = getAgentConfigPath(agent, "project");
983
- p2.log.success(`${agent.name}: ${configPath} created`);
984
- p2.note(
1063
+ p3.log.success(`${agent.name}: ${configPath} created`);
1064
+ p3.note(
985
1065
  pc3.yellow("Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key."),
986
1066
  "Tip"
987
1067
  );
988
- p2.outro("Done.");
1068
+ p3.outro("Done.");
989
1069
  return;
990
1070
  }
991
- const selected = await p2.multiselect({
1071
+ const selected = await p3.multiselect({
992
1072
  message: "Select agents for project-level MCP:",
993
1073
  options: missing.map((s) => ({
994
1074
  value: s.agent.id,
995
1075
  label: s.agent.name
996
1076
  }))
997
1077
  });
998
- if (p2.isCancel(selected)) {
999
- p2.outro("No changes made.");
1078
+ if (p3.isCancel(selected)) {
1079
+ p3.outro("No changes made.");
1000
1080
  return;
1001
1081
  }
1002
1082
  const agents = missing.filter((s) => selected.includes(s.agent.id));
1003
1083
  for (const s of agents) {
1004
1084
  installMcpConfig(s.agent, apiKey, "project", ac);
1005
1085
  const configPath = getAgentConfigPath(s.agent, "project");
1006
- p2.log.success(`${s.agent.name}: ${configPath} created`);
1086
+ p3.log.success(`${s.agent.name}: ${configPath} created`);
1007
1087
  }
1008
- p2.note(
1088
+ p3.note(
1009
1089
  pc3.yellow("Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key."),
1010
1090
  "Tip"
1011
1091
  );
1012
- p2.outro("Done.");
1092
+ p3.outro("Done.");
1013
1093
  }
1014
1094
  async function freshSetup(options) {
1015
- p2.note(`Get your API key at:
1095
+ p3.note(`Get your API key at:
1016
1096
  ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1017
- const openBrowser = await p2.confirm({
1097
+ const openBrowser = await p3.confirm({
1018
1098
  message: "Open browser to get API key?",
1019
1099
  initialValue: true
1020
1100
  });
1021
- if (p2.isCancel(openBrowser)) {
1022
- p2.cancel("Setup cancelled.");
1101
+ if (p3.isCancel(openBrowser)) {
1102
+ p3.cancel("Setup cancelled.");
1023
1103
  process.exit(0);
1024
1104
  }
1025
1105
  if (openBrowser) {
1026
1106
  await openApiKeyPage();
1027
1107
  }
1028
- const apiKey = await p2.text({
1108
+ const apiKey = await p3.text({
1029
1109
  message: "Enter your Pica API key:",
1030
1110
  placeholder: "sk_live_...",
1031
1111
  validate: (value) => {
@@ -1036,27 +1116,27 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1036
1116
  return void 0;
1037
1117
  }
1038
1118
  });
1039
- if (p2.isCancel(apiKey)) {
1040
- p2.cancel("Setup cancelled.");
1119
+ if (p3.isCancel(apiKey)) {
1120
+ p3.cancel("Setup cancelled.");
1041
1121
  process.exit(0);
1042
1122
  }
1043
- const spinner6 = p2.spinner();
1044
- spinner6.start("Validating API key...");
1123
+ const spinner5 = p3.spinner();
1124
+ spinner5.start("Validating API key...");
1045
1125
  const api = new PicaApi(apiKey);
1046
1126
  const isValid = await api.validateApiKey();
1047
1127
  if (!isValid) {
1048
- spinner6.stop("Invalid API key");
1049
- p2.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
1128
+ spinner5.stop("Invalid API key");
1129
+ p3.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
1050
1130
  process.exit(1);
1051
1131
  }
1052
- spinner6.stop("API key validated");
1132
+ spinner5.stop("API key validated");
1053
1133
  writeConfig({
1054
1134
  apiKey,
1055
1135
  installedAgents: [],
1056
1136
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1057
1137
  });
1058
1138
  const allAgents = getAllAgents();
1059
- const agentChoice = await p2.select({
1139
+ const agentChoice = await p3.select({
1060
1140
  message: "Where do you want to install the MCP?",
1061
1141
  options: [
1062
1142
  {
@@ -1070,8 +1150,8 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1070
1150
  }))
1071
1151
  ]
1072
1152
  });
1073
- if (p2.isCancel(agentChoice)) {
1074
- p2.cancel("Setup cancelled.");
1153
+ if (p3.isCancel(agentChoice)) {
1154
+ p3.cancel("Setup cancelled.");
1075
1155
  process.exit(0);
1076
1156
  }
1077
1157
  const selectedAgents = agentChoice === "all" ? allAgents : allAgents.filter((a) => a.id === agentChoice);
@@ -1082,7 +1162,7 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1082
1162
  } else if (options.project) {
1083
1163
  scope = "project";
1084
1164
  } else if (hasProjectScopeAgent) {
1085
- const scopeChoice = await p2.select({
1165
+ const scopeChoice = await p3.select({
1086
1166
  message: "How do you want to install it?",
1087
1167
  options: [
1088
1168
  {
@@ -1097,8 +1177,8 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1097
1177
  }
1098
1178
  ]
1099
1179
  });
1100
- if (p2.isCancel(scopeChoice)) {
1101
- p2.cancel("Setup cancelled.");
1180
+ if (p3.isCancel(scopeChoice)) {
1181
+ p3.cancel("Setup cancelled.");
1102
1182
  process.exit(0);
1103
1183
  }
1104
1184
  scope = scopeChoice;
@@ -1108,12 +1188,12 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1108
1188
  const nonProjectAgents = selectedAgents.filter((a) => !supportsProjectScope(a));
1109
1189
  if (projectAgents.length === 0) {
1110
1190
  const supported = allAgents.filter((a) => supportsProjectScope(a)).map((a) => a.name).join(", ");
1111
- p2.note(
1191
+ p3.note(
1112
1192
  `${selectedAgents.map((a) => a.name).join(", ")} does not support project-level MCP.
1113
1193
  Project scope is supported by: ${supported}`,
1114
1194
  "Not Supported"
1115
1195
  );
1116
- p2.cancel("Run again and choose global scope or a different agent.");
1196
+ p3.cancel("Run again and choose global scope or a different agent.");
1117
1197
  process.exit(1);
1118
1198
  }
1119
1199
  for (const agent of projectAgents) {
@@ -1121,15 +1201,15 @@ Project scope is supported by: ${supported}`,
1121
1201
  installMcpConfig(agent, apiKey, "project");
1122
1202
  const configPath = getAgentConfigPath(agent, "project");
1123
1203
  const status = wasInstalled ? "updated" : "created";
1124
- p2.log.success(`${agent.name}: ${configPath} ${status}`);
1204
+ p3.log.success(`${agent.name}: ${configPath} ${status}`);
1125
1205
  }
1126
1206
  if (nonProjectAgents.length > 0) {
1127
- p2.log.info(`Installing globally for agents without project scope support:`);
1207
+ p3.log.info(`Installing globally for agents without project scope support:`);
1128
1208
  for (const agent of nonProjectAgents) {
1129
1209
  const wasInstalled = isMcpInstalled(agent, "global");
1130
1210
  installMcpConfig(agent, apiKey, "global");
1131
1211
  const status = wasInstalled ? "updated" : "installed";
1132
- p2.log.success(`${agent.name}: MCP ${status} (global)`);
1212
+ p3.log.success(`${agent.name}: MCP ${status} (global)`);
1133
1213
  }
1134
1214
  }
1135
1215
  const allInstalled = [...projectAgents, ...nonProjectAgents];
@@ -1152,9 +1232,9 @@ ${globalPaths}
1152
1232
  `;
1153
1233
  }
1154
1234
  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.");
1155
- p2.note(summary, "Setup Complete");
1235
+ p3.note(summary, "Setup Complete");
1156
1236
  await promptConnectIntegrations(apiKey);
1157
- p2.outro("Your AI agents now have access to Pica integrations!");
1237
+ p3.outro("Your AI agents now have access to Pica integrations!");
1158
1238
  return;
1159
1239
  }
1160
1240
  const installedAgentIds = [];
@@ -1163,19 +1243,19 @@ ${globalPaths}
1163
1243
  installMcpConfig(agent, apiKey, "global");
1164
1244
  installedAgentIds.push(agent.id);
1165
1245
  const status = wasInstalled ? "updated" : "installed";
1166
- p2.log.success(`${agent.name}: MCP ${status}`);
1246
+ p3.log.success(`${agent.name}: MCP ${status}`);
1167
1247
  }
1168
1248
  writeConfig({
1169
1249
  apiKey,
1170
1250
  installedAgents: installedAgentIds,
1171
1251
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1172
1252
  });
1173
- p2.note(
1253
+ p3.note(
1174
1254
  `Config saved to: ${pc3.dim(getConfigPath())}`,
1175
1255
  "Setup Complete"
1176
1256
  );
1177
1257
  await promptConnectIntegrations(apiKey);
1178
- p2.outro("Your AI agents now have access to Pica integrations!");
1258
+ p3.outro("Your AI agents now have access to Pica integrations!");
1179
1259
  }
1180
1260
  function printBanner() {
1181
1261
  console.log();
@@ -1224,48 +1304,48 @@ async function promptConnectIntegrations(apiKey) {
1224
1304
  { value: "skip", label: "Skip for now", hint: "you can always run pica add later" }
1225
1305
  ];
1226
1306
  const message = first ? "Connect your first integration?" : "Connect another?";
1227
- const choice = await p2.select({ message, options });
1228
- if (p2.isCancel(choice) || choice === "skip") {
1307
+ const choice = await p3.select({ message, options });
1308
+ if (p3.isCancel(choice) || choice === "skip") {
1229
1309
  break;
1230
1310
  }
1231
1311
  if (choice === "more") {
1232
1312
  try {
1233
1313
  await open2("https://app.picaos.com/connections");
1234
- p2.log.info("Opened Pica dashboard in browser.");
1314
+ p3.log.info("Opened Pica dashboard in browser.");
1235
1315
  } catch {
1236
- p2.note("https://app.picaos.com/connections", "Open in browser");
1316
+ p3.note("https://app.picaos.com/connections", "Open in browser");
1237
1317
  }
1238
- p2.log.info(`Connect from the dashboard, or use ${pc3.cyan("pica add <platform>")}`);
1318
+ p3.log.info(`Connect from the dashboard, or use ${pc3.cyan("pica add <platform>")}`);
1239
1319
  break;
1240
1320
  }
1241
1321
  const platform = choice;
1242
1322
  const integration = TOP_INTEGRATIONS.find((i) => i.value === platform);
1243
1323
  const label = integration?.label ?? platform;
1244
- p2.log.info(`Opening browser to connect ${pc3.cyan(label)}...`);
1324
+ p3.log.info(`Opening browser to connect ${pc3.cyan(label)}...`);
1245
1325
  try {
1246
1326
  await openConnectionPage(platform);
1247
1327
  } catch {
1248
1328
  const url = getConnectionUrl(platform);
1249
- p2.log.warn("Could not open browser automatically.");
1250
- p2.note(url, "Open manually");
1329
+ p3.log.warn("Could not open browser automatically.");
1330
+ p3.note(url, "Open manually");
1251
1331
  }
1252
- const spinner6 = p2.spinner();
1253
- spinner6.start("Waiting for connection... (complete auth in browser)");
1332
+ const spinner5 = p3.spinner();
1333
+ spinner5.start("Waiting for connection... (complete auth in browser)");
1254
1334
  try {
1255
1335
  await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
1256
- spinner6.stop(`${label} connected!`);
1257
- p2.log.success(`${pc3.green("\u2713")} ${label} is now available to your AI agents`);
1336
+ spinner5.stop(`${label} connected!`);
1337
+ p3.log.success(`${pc3.green("\u2713")} ${label} is now available to your AI agents`);
1258
1338
  connected.push(platform);
1259
1339
  first = false;
1260
- } catch (error) {
1261
- spinner6.stop("Connection timed out");
1262
- if (error instanceof TimeoutError) {
1263
- p2.log.warn(`No worries. Connect later with: ${pc3.cyan(`pica add ${platform}`)}`);
1340
+ } catch (error2) {
1341
+ spinner5.stop("Connection timed out");
1342
+ if (error2 instanceof TimeoutError) {
1343
+ p3.log.warn(`No worries. Connect later with: ${pc3.cyan(`pica add ${platform}`)}`);
1264
1344
  }
1265
1345
  first = false;
1266
1346
  }
1267
1347
  if (TOP_INTEGRATIONS.every((i) => connected.includes(i.value))) {
1268
- p2.log.success("All top integrations connected!");
1348
+ p3.log.success("All top integrations connected!");
1269
1349
  break;
1270
1350
  }
1271
1351
  }
@@ -1284,23 +1364,23 @@ function updateConfigAgents(agentId) {
1284
1364
  }
1285
1365
 
1286
1366
  // src/commands/connection.ts
1287
- import * as p3 from "@clack/prompts";
1367
+ import * as p4 from "@clack/prompts";
1288
1368
  import pc4 from "picocolors";
1289
1369
 
1290
1370
  // src/lib/platforms.ts
1291
1371
  function findPlatform(platforms, query) {
1292
1372
  const normalizedQuery = query.toLowerCase().trim();
1293
1373
  const exact = platforms.find(
1294
- (p6) => p6.platform.toLowerCase() === normalizedQuery || p6.name.toLowerCase() === normalizedQuery
1374
+ (p7) => p7.platform.toLowerCase() === normalizedQuery || p7.name.toLowerCase() === normalizedQuery
1295
1375
  );
1296
1376
  if (exact) return exact;
1297
1377
  return null;
1298
1378
  }
1299
1379
  function findSimilarPlatforms(platforms, query, limit = 3) {
1300
1380
  const normalizedQuery = query.toLowerCase().trim();
1301
- const scored = platforms.map((p6) => {
1302
- const name = p6.name.toLowerCase();
1303
- const slug = p6.platform.toLowerCase();
1381
+ const scored = platforms.map((p7) => {
1382
+ const name = p7.name.toLowerCase();
1383
+ const slug = p7.platform.toLowerCase();
1304
1384
  let score = 0;
1305
1385
  if (name.includes(normalizedQuery) || slug.includes(normalizedQuery)) {
1306
1386
  score = 10;
@@ -1309,7 +1389,7 @@ function findSimilarPlatforms(platforms, query, limit = 3) {
1309
1389
  } else {
1310
1390
  score = countMatchingChars(normalizedQuery, slug);
1311
1391
  }
1312
- return { platform: p6, score };
1392
+ return { platform: p7, score };
1313
1393
  }).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
1314
1394
  return scored.map((item) => item.platform);
1315
1395
  }
@@ -1324,22 +1404,25 @@ function countMatchingChars(a, b) {
1324
1404
 
1325
1405
  // src/commands/connection.ts
1326
1406
  async function connectionAddCommand(platformArg) {
1327
- p3.intro(pc4.bgCyan(pc4.black(" Pica ")));
1407
+ if (isAgentMode()) {
1408
+ error("This command requires interactive input. Run without --agent.");
1409
+ }
1410
+ p4.intro(pc4.bgCyan(pc4.black(" Pica ")));
1328
1411
  const apiKey = getApiKey();
1329
1412
  if (!apiKey) {
1330
- p3.cancel("Not configured. Run `pica init` first.");
1413
+ p4.cancel("Not configured. Run `pica init` first.");
1331
1414
  process.exit(1);
1332
1415
  }
1333
1416
  const api = new PicaApi(apiKey);
1334
- const spinner6 = p3.spinner();
1335
- spinner6.start("Loading platforms...");
1417
+ const spinner5 = p4.spinner();
1418
+ spinner5.start("Loading platforms...");
1336
1419
  let platforms;
1337
1420
  try {
1338
1421
  platforms = await api.listPlatforms();
1339
- spinner6.stop(`${platforms.length} platforms available`);
1340
- } catch (error) {
1341
- spinner6.stop("Failed to load platforms");
1342
- p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1422
+ spinner5.stop(`${platforms.length} platforms available`);
1423
+ } catch (error2) {
1424
+ spinner5.stop("Failed to load platforms");
1425
+ p4.cancel(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1343
1426
  process.exit(1);
1344
1427
  }
1345
1428
  let platform;
@@ -1350,29 +1433,29 @@ async function connectionAddCommand(platformArg) {
1350
1433
  } else {
1351
1434
  const similar = findSimilarPlatforms(platforms, platformArg);
1352
1435
  if (similar.length > 0) {
1353
- p3.log.warn(`Unknown platform: ${platformArg}`);
1354
- const suggestion = await p3.select({
1436
+ p4.log.warn(`Unknown platform: ${platformArg}`);
1437
+ const suggestion = await p4.select({
1355
1438
  message: "Did you mean:",
1356
1439
  options: [
1357
1440
  ...similar.map((s) => ({ value: s.platform, label: `${s.name} (${s.platform})` })),
1358
1441
  { value: "__other__", label: "None of these" }
1359
1442
  ]
1360
1443
  });
1361
- if (p3.isCancel(suggestion) || suggestion === "__other__") {
1362
- p3.note(`Run ${pc4.cyan("pica platforms")} to see all available platforms.`);
1363
- p3.cancel("Connection cancelled.");
1444
+ if (p4.isCancel(suggestion) || suggestion === "__other__") {
1445
+ p4.note(`Run ${pc4.cyan("pica platforms")} to see all available platforms.`);
1446
+ p4.cancel("Connection cancelled.");
1364
1447
  process.exit(0);
1365
1448
  }
1366
1449
  platform = suggestion;
1367
1450
  } else {
1368
- p3.cancel(`Unknown platform: ${platformArg}
1451
+ p4.cancel(`Unknown platform: ${platformArg}
1369
1452
 
1370
1453
  Run ${pc4.cyan("pica platforms")} to see available platforms.`);
1371
1454
  process.exit(1);
1372
1455
  }
1373
1456
  }
1374
1457
  } else {
1375
- const platformInput = await p3.text({
1458
+ const platformInput = await p4.text({
1376
1459
  message: "Which platform do you want to connect?",
1377
1460
  placeholder: "gmail, slack, hubspot...",
1378
1461
  validate: (value) => {
@@ -1380,41 +1463,41 @@ Run ${pc4.cyan("pica platforms")} to see available platforms.`);
1380
1463
  return void 0;
1381
1464
  }
1382
1465
  });
1383
- if (p3.isCancel(platformInput)) {
1384
- p3.cancel("Connection cancelled.");
1466
+ if (p4.isCancel(platformInput)) {
1467
+ p4.cancel("Connection cancelled.");
1385
1468
  process.exit(0);
1386
1469
  }
1387
1470
  const found = findPlatform(platforms, platformInput);
1388
1471
  if (found) {
1389
1472
  platform = found.platform;
1390
1473
  } else {
1391
- p3.cancel(`Unknown platform: ${platformInput}
1474
+ p4.cancel(`Unknown platform: ${platformInput}
1392
1475
 
1393
1476
  Run ${pc4.cyan("pica platforms")} to see available platforms.`);
1394
1477
  process.exit(1);
1395
1478
  }
1396
1479
  }
1397
1480
  const url = getConnectionUrl(platform);
1398
- p3.log.info(`Opening browser to connect ${pc4.cyan(platform)}...`);
1399
- p3.note(pc4.dim(url), "URL");
1481
+ p4.log.info(`Opening browser to connect ${pc4.cyan(platform)}...`);
1482
+ p4.note(pc4.dim(url), "URL");
1400
1483
  try {
1401
1484
  await openConnectionPage(platform);
1402
1485
  } catch {
1403
- p3.log.warn("Could not open browser automatically.");
1404
- p3.note(`Open this URL manually:
1486
+ p4.log.warn("Could not open browser automatically.");
1487
+ p4.note(`Open this URL manually:
1405
1488
  ${url}`);
1406
1489
  }
1407
- const pollSpinner = p3.spinner();
1490
+ const pollSpinner = p4.spinner();
1408
1491
  pollSpinner.start("Waiting for connection... (complete auth in browser)");
1409
1492
  try {
1410
1493
  const connection2 = await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
1411
1494
  pollSpinner.stop(`${platform} connected!`);
1412
- p3.log.success(`${pc4.green("\u2713")} ${connection2.platform} is now available to your AI agents.`);
1413
- p3.outro("Connection complete!");
1414
- } catch (error) {
1495
+ p4.log.success(`${pc4.green("\u2713")} ${connection2.platform} is now available to your AI agents.`);
1496
+ p4.outro("Connection complete!");
1497
+ } catch (error2) {
1415
1498
  pollSpinner.stop("Connection timed out");
1416
- if (error instanceof TimeoutError) {
1417
- p3.note(
1499
+ if (error2 instanceof TimeoutError) {
1500
+ p4.note(
1418
1501
  `Possible issues:
1419
1502
  - OAuth flow was not completed in the browser
1420
1503
  - Browser popup was blocked
@@ -1424,7 +1507,7 @@ Try again with: ${pc4.cyan(`pica connection add ${platform}`)}`,
1424
1507
  "Timed Out"
1425
1508
  );
1426
1509
  } else {
1427
- p3.log.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1510
+ p4.log.error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1428
1511
  }
1429
1512
  process.exit(1);
1430
1513
  }
@@ -1432,17 +1515,26 @@ Try again with: ${pc4.cyan(`pica connection add ${platform}`)}`,
1432
1515
  async function connectionListCommand() {
1433
1516
  const apiKey = getApiKey();
1434
1517
  if (!apiKey) {
1435
- p3.cancel("Not configured. Run `pica init` first.");
1436
- process.exit(1);
1518
+ error("Not configured. Run `pica init` first.");
1437
1519
  }
1438
1520
  const api = new PicaApi(apiKey);
1439
- const spinner6 = p3.spinner();
1440
- spinner6.start("Loading connections...");
1521
+ const spinner5 = createSpinner();
1522
+ spinner5.start("Loading connections...");
1441
1523
  try {
1442
1524
  const connections = await api.listConnections();
1443
- spinner6.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
1525
+ if (isAgentMode()) {
1526
+ json({
1527
+ connections: connections.map((conn) => ({
1528
+ platform: conn.platform,
1529
+ state: conn.state,
1530
+ key: conn.key
1531
+ }))
1532
+ });
1533
+ return;
1534
+ }
1535
+ spinner5.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
1444
1536
  if (connections.length === 0) {
1445
- p3.note(
1537
+ p4.note(
1446
1538
  `No connections yet.
1447
1539
 
1448
1540
  Add one with: ${pc4.cyan("pica connection add gmail")}`,
@@ -1467,11 +1559,10 @@ Add one with: ${pc4.cyan("pica connection add gmail")}`,
1467
1559
  rows
1468
1560
  );
1469
1561
  console.log();
1470
- p3.note(`Add more with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
1471
- } catch (error) {
1472
- spinner6.stop("Failed to load connections");
1473
- p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1474
- process.exit(1);
1562
+ p4.note(`Add more with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
1563
+ } catch (error2) {
1564
+ spinner5.stop("Failed to load connections");
1565
+ error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1475
1566
  }
1476
1567
  }
1477
1568
  function getStatusIndicator(state) {
@@ -1488,22 +1579,28 @@ function getStatusIndicator(state) {
1488
1579
  }
1489
1580
 
1490
1581
  // src/commands/platforms.ts
1491
- import * as p4 from "@clack/prompts";
1582
+ import * as p5 from "@clack/prompts";
1492
1583
  import pc5 from "picocolors";
1493
1584
  async function platformsCommand(options) {
1494
1585
  const apiKey = getApiKey();
1495
1586
  if (!apiKey) {
1496
- p4.cancel("Not configured. Run `pica init` first.");
1497
- process.exit(1);
1587
+ error("Not configured. Run `pica init` first.");
1588
+ }
1589
+ if (isAgentMode()) {
1590
+ options.json = true;
1498
1591
  }
1499
1592
  const api = new PicaApi(apiKey);
1500
- const spinner6 = p4.spinner();
1501
- spinner6.start("Loading platforms...");
1593
+ const spinner5 = createSpinner();
1594
+ spinner5.start("Loading platforms...");
1502
1595
  try {
1503
1596
  const platforms = await api.listPlatforms();
1504
- spinner6.stop(`${platforms.length} platforms available`);
1597
+ spinner5.stop(`${platforms.length} platforms available`);
1505
1598
  if (options.json) {
1506
- console.log(JSON.stringify(platforms, null, 2));
1599
+ if (isAgentMode()) {
1600
+ json({ platforms });
1601
+ } else {
1602
+ console.log(JSON.stringify(platforms, null, 2));
1603
+ }
1507
1604
  return;
1508
1605
  }
1509
1606
  const byCategory = /* @__PURE__ */ new Map();
@@ -1519,7 +1616,7 @@ async function platformsCommand(options) {
1519
1616
  const categoryPlatforms = byCategory.get(options.category);
1520
1617
  if (!categoryPlatforms) {
1521
1618
  const categories = [...byCategory.keys()].sort();
1522
- p4.note(`Available categories:
1619
+ p5.note(`Available categories:
1523
1620
  ${categories.join(", ")}`, "Unknown Category");
1524
1621
  process.exit(1);
1525
1622
  }
@@ -1551,36 +1648,41 @@ async function platformsCommand(options) {
1551
1648
  );
1552
1649
  }
1553
1650
  console.log();
1554
- p4.note(`Connect with: ${pc5.cyan("pica connection add <platform>")}`, "Tip");
1555
- } catch (error) {
1556
- spinner6.stop("Failed to load platforms");
1557
- p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1558
- process.exit(1);
1651
+ p5.note(`Connect with: ${pc5.cyan("pica connection add <platform>")}`, "Tip");
1652
+ } catch (error2) {
1653
+ spinner5.stop("Failed to load platforms");
1654
+ error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1559
1655
  }
1560
1656
  }
1561
1657
 
1562
1658
  // src/commands/actions.ts
1563
- import * as p5 from "@clack/prompts";
1659
+ import * as p6 from "@clack/prompts";
1564
1660
  import pc6 from "picocolors";
1565
1661
  function getConfig() {
1566
1662
  const apiKey = getApiKey();
1567
1663
  if (!apiKey) {
1568
- p5.cancel("Not configured. Run `pica init` first.");
1569
- process.exit(1);
1664
+ error("Not configured. Run `pica init` first.");
1570
1665
  }
1571
- const ac = getAccessControl();
1666
+ const ac = getAccessControlFromAllSources();
1572
1667
  const permissions = ac.permissions || "admin";
1573
1668
  const connectionKeys = ac.connectionKeys || ["*"];
1574
1669
  const actionIds = ac.actionIds || ["*"];
1575
1670
  const knowledgeAgent = ac.knowledgeAgent || false;
1576
1671
  return { apiKey, permissions, connectionKeys, actionIds, knowledgeAgent };
1577
1672
  }
1673
+ function parseJsonArg(value, argName) {
1674
+ try {
1675
+ return JSON.parse(value);
1676
+ } catch {
1677
+ error(`Invalid JSON for ${argName}: ${value}`);
1678
+ }
1679
+ }
1578
1680
  async function actionsSearchCommand(platform, query, options) {
1579
- p5.intro(pc6.bgCyan(pc6.black(" Pica ")));
1681
+ intro2(pc6.bgCyan(pc6.black(" Pica ")));
1580
1682
  const { apiKey, permissions, actionIds, knowledgeAgent } = getConfig();
1581
1683
  const api = new PicaApi(apiKey);
1582
- const spinner6 = p5.spinner();
1583
- spinner6.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
1684
+ const spinner5 = createSpinner();
1685
+ spinner5.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
1584
1686
  try {
1585
1687
  const agentType = knowledgeAgent ? "knowledge" : options.type;
1586
1688
  let actions2 = await api.searchActions(platform, query, agentType);
@@ -1592,9 +1694,13 @@ async function actionsSearchCommand(platform, query, options) {
1592
1694
  method: action.method,
1593
1695
  path: action.path
1594
1696
  }));
1697
+ if (isAgentMode()) {
1698
+ json({ actions: cleanedActions });
1699
+ return;
1700
+ }
1595
1701
  if (cleanedActions.length === 0) {
1596
- spinner6.stop("No actions found");
1597
- p5.note(
1702
+ spinner5.stop("No actions found");
1703
+ p6.note(
1598
1704
  `No actions found for platform '${platform}' matching query '${query}'.
1599
1705
 
1600
1706
  Suggestions:
@@ -1611,7 +1717,7 @@ Examples of good queries:
1611
1717
  );
1612
1718
  return;
1613
1719
  }
1614
- spinner6.stop(
1720
+ spinner5.stop(
1615
1721
  `Found ${cleanedActions.length} action(s) for '${platform}' matching '${query}'`
1616
1722
  );
1617
1723
  console.log();
@@ -1631,49 +1737,45 @@ Examples of good queries:
1631
1737
  rows
1632
1738
  );
1633
1739
  console.log();
1634
- p5.note(
1740
+ p6.note(
1635
1741
  `Get details: ${pc6.cyan(`pica actions knowledge ${platform} <actionId>`)}
1636
1742
  Execute: ${pc6.cyan(`pica actions execute ${platform} <actionId> <connectionKey>`)}`,
1637
1743
  "Next Steps"
1638
1744
  );
1639
- } catch (error) {
1640
- spinner6.stop("Search failed");
1641
- p5.cancel(
1642
- `Error: ${error instanceof Error ? error.message : "Unknown error"}`
1745
+ } catch (error2) {
1746
+ spinner5.stop("Search failed");
1747
+ error(
1748
+ `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
1643
1749
  );
1644
- process.exit(1);
1645
1750
  }
1646
1751
  }
1647
1752
  async function actionsKnowledgeCommand(platform, actionId) {
1648
- p5.intro(pc6.bgCyan(pc6.black(" Pica ")));
1753
+ intro2(pc6.bgCyan(pc6.black(" Pica ")));
1649
1754
  const { apiKey, actionIds, connectionKeys } = getConfig();
1650
1755
  const api = new PicaApi(apiKey);
1651
1756
  if (!isActionAllowed(actionId, actionIds)) {
1652
- p5.cancel(`Action "${actionId}" is not in the allowed action list.`);
1653
- process.exit(1);
1757
+ error(`Action "${actionId}" is not in the allowed action list.`);
1654
1758
  }
1655
1759
  if (!connectionKeys.includes("*")) {
1656
- const spinner7 = p5.spinner();
1657
- spinner7.start("Checking connections...");
1760
+ const spinner6 = createSpinner();
1761
+ spinner6.start("Checking connections...");
1658
1762
  try {
1659
1763
  const connections = await api.listConnections();
1660
1764
  const connectedPlatforms = connections.map((c) => c.platform);
1661
1765
  if (!connectedPlatforms.includes(platform)) {
1662
- spinner7.stop("Platform not connected");
1663
- p5.cancel(`Platform "${platform}" has no allowed connections.`);
1664
- process.exit(1);
1766
+ spinner6.stop("Platform not connected");
1767
+ error(`Platform "${platform}" has no allowed connections.`);
1665
1768
  }
1666
- spinner7.stop("Connection verified");
1667
- } catch (error) {
1668
- spinner7.stop("Failed to check connections");
1669
- p5.cancel(
1670
- `Error: ${error instanceof Error ? error.message : "Unknown error"}`
1769
+ spinner6.stop("Connection verified");
1770
+ } catch (error2) {
1771
+ spinner6.stop("Failed to check connections");
1772
+ error(
1773
+ `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
1671
1774
  );
1672
- process.exit(1);
1673
1775
  }
1674
1776
  }
1675
- const spinner6 = p5.spinner();
1676
- spinner6.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
1777
+ const spinner5 = createSpinner();
1778
+ spinner5.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
1677
1779
  try {
1678
1780
  const { knowledge, method } = await api.getActionKnowledge(actionId);
1679
1781
  const knowledgeWithGuidance = buildActionKnowledgeWithGuidance(
@@ -1682,58 +1784,56 @@ async function actionsKnowledgeCommand(platform, actionId) {
1682
1784
  platform,
1683
1785
  actionId
1684
1786
  );
1685
- spinner6.stop("Knowledge loaded");
1787
+ if (isAgentMode()) {
1788
+ json({ knowledge: knowledgeWithGuidance, method });
1789
+ return;
1790
+ }
1791
+ spinner5.stop("Knowledge loaded");
1686
1792
  console.log();
1687
1793
  console.log(knowledgeWithGuidance);
1688
1794
  console.log();
1689
- p5.note(
1795
+ p6.note(
1690
1796
  `Execute: ${pc6.cyan(`pica actions execute ${platform} ${actionId} <connectionKey>`)}`,
1691
1797
  "Next Step"
1692
1798
  );
1693
- } catch (error) {
1694
- spinner6.stop("Failed to load knowledge");
1695
- p5.cancel(
1696
- `Error: ${error instanceof Error ? error.message : "Unknown error"}`
1799
+ } catch (error2) {
1800
+ spinner5.stop("Failed to load knowledge");
1801
+ error(
1802
+ `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
1697
1803
  );
1698
- process.exit(1);
1699
1804
  }
1700
1805
  }
1701
1806
  async function actionsExecuteCommand(platform, actionId, connectionKey, options) {
1702
- p5.intro(pc6.bgCyan(pc6.black(" Pica ")));
1807
+ intro2(pc6.bgCyan(pc6.black(" Pica ")));
1703
1808
  const { apiKey, permissions, actionIds, connectionKeys, knowledgeAgent } = getConfig();
1704
1809
  if (knowledgeAgent) {
1705
- p5.cancel(
1706
- `Action execution is disabled (knowledge-only mode).
1707
- Configure with: ${pc6.cyan("pica config")}`
1810
+ error(
1811
+ "Action execution is disabled (knowledge-only mode)."
1708
1812
  );
1709
- process.exit(1);
1710
1813
  }
1711
1814
  if (!isActionAllowed(actionId, actionIds)) {
1712
- p5.cancel(`Action "${actionId}" is not in the allowed action list.`);
1713
- process.exit(1);
1815
+ error(`Action "${actionId}" is not in the allowed action list.`);
1714
1816
  }
1715
1817
  if (!connectionKeys.includes("*") && !connectionKeys.includes(connectionKey)) {
1716
- p5.cancel(`Connection key "${connectionKey}" is not allowed.`);
1717
- process.exit(1);
1818
+ error(`Connection key "${connectionKey}" is not allowed.`);
1718
1819
  }
1719
1820
  const api = new PicaApi(apiKey);
1720
- const spinner6 = p5.spinner();
1721
- spinner6.start("Loading action details...");
1821
+ const spinner5 = createSpinner();
1822
+ spinner5.start("Loading action details...");
1722
1823
  try {
1723
1824
  const actionDetails = await api.getActionDetails(actionId);
1724
1825
  if (!isMethodAllowed(actionDetails.method, permissions)) {
1725
- spinner6.stop("Permission denied");
1726
- p5.cancel(
1826
+ spinner5.stop("Permission denied");
1827
+ error(
1727
1828
  `Method "${actionDetails.method}" is not allowed under "${permissions}" permission level.`
1728
1829
  );
1729
- process.exit(1);
1730
1830
  }
1731
- spinner6.stop(`Action: ${actionDetails.title} [${actionDetails.method}]`);
1831
+ spinner5.stop(`Action: ${actionDetails.title} [${actionDetails.method}]`);
1732
1832
  const data = options.data ? parseJsonArg(options.data, "--data") : void 0;
1733
1833
  const pathVariables = options.pathVars ? parseJsonArg(options.pathVars, "--path-vars") : void 0;
1734
1834
  const queryParams = options.queryParams ? parseJsonArg(options.queryParams, "--query-params") : void 0;
1735
1835
  const headers = options.headers ? parseJsonArg(options.headers, "--headers") : void 0;
1736
- const execSpinner = p5.spinner();
1836
+ const execSpinner = createSpinner();
1737
1837
  execSpinner.start("Executing action...");
1738
1838
  const result = await api.executePassthroughRequest(
1739
1839
  {
@@ -1750,6 +1850,16 @@ Configure with: ${pc6.cyan("pica config")}`
1750
1850
  actionDetails
1751
1851
  );
1752
1852
  execSpinner.stop("Action executed successfully");
1853
+ if (isAgentMode()) {
1854
+ json({
1855
+ request: {
1856
+ method: result.requestConfig.method,
1857
+ url: result.requestConfig.url
1858
+ },
1859
+ response: result.responseData
1860
+ });
1861
+ return;
1862
+ }
1753
1863
  console.log();
1754
1864
  console.log(pc6.dim("Request:"));
1755
1865
  console.log(
@@ -1760,20 +1870,11 @@ Configure with: ${pc6.cyan("pica config")}`
1760
1870
  console.log();
1761
1871
  console.log(pc6.bold("Response:"));
1762
1872
  console.log(JSON.stringify(result.responseData, null, 2));
1763
- } catch (error) {
1764
- spinner6.stop("Execution failed");
1765
- p5.cancel(
1766
- `Error: ${error instanceof Error ? error.message : "Unknown error"}`
1873
+ } catch (error2) {
1874
+ spinner5.stop("Execution failed");
1875
+ error(
1876
+ `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
1767
1877
  );
1768
- process.exit(1);
1769
- }
1770
- }
1771
- function parseJsonArg(value, argName) {
1772
- try {
1773
- return JSON.parse(value);
1774
- } catch {
1775
- p5.cancel(`Invalid JSON for ${argName}: ${value}`);
1776
- process.exit(1);
1777
1878
  }
1778
1879
  }
1779
1880
  function colorMethod(method) {
@@ -1797,7 +1898,7 @@ function colorMethod(method) {
1797
1898
  var require2 = createRequire(import.meta.url);
1798
1899
  var { version } = require2("../package.json");
1799
1900
  var program = new Command();
1800
- program.name("pica").description(`Pica CLI \u2014 Connect AI agents to 200+ platforms through one interface.
1901
+ program.name("pica").option("--agent", "Machine-readable JSON output (no colors, spinners, or prompts)").description(`Pica CLI \u2014 Connect AI agents to 200+ platforms through one interface.
1801
1902
 
1802
1903
  Setup:
1803
1904
  pica init Set up API key and install MCP server
@@ -1825,6 +1926,12 @@ program.name("pica").description(`Pica CLI \u2014 Connect AI agents to 200+ plat
1825
1926
 
1826
1927
  Platform names are always kebab-case (e.g. hub-spot, ship-station, google-calendar).
1827
1928
  Run 'pica platforms' to browse all 200+ available platforms.`).version(version);
1929
+ program.hook("preAction", (thisCommand) => {
1930
+ const opts = program.opts();
1931
+ if (opts.agent) {
1932
+ setAgentMode(true);
1933
+ }
1934
+ });
1828
1935
  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) => {
1829
1936
  await initCommand(options);
1830
1937
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picahq/cli",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "description": "CLI for managing Pica",
5
5
  "type": "module",
6
6
  "files": [