@picahq/cli 1.9.2 → 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 +317 -250
  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
@@ -113,11 +113,11 @@ import fs2 from "fs";
113
113
  import path2 from "path";
114
114
  import os2 from "os";
115
115
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
116
- function expandPath(p6) {
117
- if (p6.startsWith("~/")) {
118
- 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));
119
119
  }
120
- return p6;
120
+ return p7;
121
121
  }
122
122
  function getClaudeDesktopConfigPath() {
123
123
  switch (process.platform) {
@@ -337,11 +337,11 @@ var PicaApi = class {
337
337
  try {
338
338
  await this.listConnections();
339
339
  return true;
340
- } catch (error) {
341
- if (error instanceof ApiError && error.status === 401) {
340
+ } catch (error2) {
341
+ if (error2 instanceof ApiError && error2.status === 401) {
342
342
  return false;
343
343
  }
344
- throw error;
344
+ throw error2;
345
345
  }
346
346
  }
347
347
  async listConnections() {
@@ -606,15 +606,52 @@ async function openApiKeyPage() {
606
606
  }
607
607
 
608
608
  // src/commands/config.ts
609
- import * as p from "@clack/prompts";
609
+ import * as p2 from "@clack/prompts";
610
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
611
645
  async function configCommand() {
646
+ if (isAgentMode()) {
647
+ error("This command requires interactive input. Run without --agent.");
648
+ }
612
649
  const config = readConfig();
613
650
  if (!config) {
614
- 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.`);
615
652
  return;
616
653
  }
617
- p.intro(pc.bgCyan(pc.black(" Pica Access Control ")));
654
+ p2.intro(pc.bgCyan(pc.black(" Pica Access Control ")));
618
655
  const current = getAccessControl();
619
656
  console.log();
620
657
  console.log(` ${pc.bold("Current Access Control")}`);
@@ -624,7 +661,7 @@ async function configCommand() {
624
661
  console.log(` ${pc.dim("Action IDs:")} ${formatList(current.actionIds)}`);
625
662
  console.log(` ${pc.dim("Knowledge only:")} ${current.knowledgeAgent ? "yes" : "no"}`);
626
663
  console.log();
627
- const permissions = await p.select({
664
+ const permissions = await p2.select({
628
665
  message: "Permission level",
629
666
  options: [
630
667
  { value: "admin", label: "Admin", hint: "Full access (GET, POST, PUT, PATCH, DELETE)" },
@@ -633,11 +670,11 @@ async function configCommand() {
633
670
  ],
634
671
  initialValue: current.permissions ?? "admin"
635
672
  });
636
- if (p.isCancel(permissions)) {
637
- p.outro("No changes made.");
673
+ if (p2.isCancel(permissions)) {
674
+ p2.outro("No changes made.");
638
675
  return;
639
676
  }
640
- const connectionMode = await p.select({
677
+ const connectionMode = await p2.select({
641
678
  message: "Connection scope",
642
679
  options: [
643
680
  { value: "all", label: "All connections" },
@@ -645,23 +682,23 @@ async function configCommand() {
645
682
  ],
646
683
  initialValue: current.connectionKeys ? "specific" : "all"
647
684
  });
648
- if (p.isCancel(connectionMode)) {
649
- p.outro("No changes made.");
685
+ if (p2.isCancel(connectionMode)) {
686
+ p2.outro("No changes made.");
650
687
  return;
651
688
  }
652
689
  let connectionKeys;
653
690
  if (connectionMode === "specific") {
654
691
  connectionKeys = await selectConnections(config.apiKey);
655
692
  if (connectionKeys === void 0) {
656
- p.outro("No changes made.");
693
+ p2.outro("No changes made.");
657
694
  return;
658
695
  }
659
696
  if (connectionKeys.length === 0) {
660
- 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.`);
661
698
  connectionKeys = void 0;
662
699
  }
663
700
  }
664
- const actionMode = await p.select({
701
+ const actionMode = await p2.select({
665
702
  message: "Action scope",
666
703
  options: [
667
704
  { value: "all", label: "All actions" },
@@ -669,13 +706,13 @@ async function configCommand() {
669
706
  ],
670
707
  initialValue: current.actionIds ? "specific" : "all"
671
708
  });
672
- if (p.isCancel(actionMode)) {
673
- p.outro("No changes made.");
709
+ if (p2.isCancel(actionMode)) {
710
+ p2.outro("No changes made.");
674
711
  return;
675
712
  }
676
713
  let actionIds;
677
714
  if (actionMode === "specific") {
678
- const actionInput = await p.text({
715
+ const actionInput = await p2.text({
679
716
  message: "Enter action IDs (comma-separated):",
680
717
  placeholder: "action-id-1, action-id-2",
681
718
  initialValue: current.actionIds?.join(", ") ?? "",
@@ -684,18 +721,18 @@ async function configCommand() {
684
721
  return void 0;
685
722
  }
686
723
  });
687
- if (p.isCancel(actionInput)) {
688
- p.outro("No changes made.");
724
+ if (p2.isCancel(actionInput)) {
725
+ p2.outro("No changes made.");
689
726
  return;
690
727
  }
691
728
  actionIds = actionInput.split(",").map((s) => s.trim()).filter(Boolean);
692
729
  }
693
- const knowledgeAgent = await p.confirm({
730
+ const knowledgeAgent = await p2.confirm({
694
731
  message: "Enable knowledge-only mode? (disables action execution)",
695
732
  initialValue: current.knowledgeAgent ?? false
696
733
  });
697
- if (p.isCancel(knowledgeAgent)) {
698
- p.outro("No changes made.");
734
+ if (p2.isCancel(knowledgeAgent)) {
735
+ p2.outro("No changes made.");
699
736
  return;
700
737
  }
701
738
  const settings = {
@@ -719,22 +756,22 @@ async function configCommand() {
719
756
  }
720
757
  }
721
758
  if (reinstalled.length > 0) {
722
- p.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
759
+ p2.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
723
760
  }
724
- p.outro("Access control updated.");
761
+ p2.outro("Access control updated.");
725
762
  }
726
763
  async function selectConnections(apiKey) {
727
- const spinner6 = p.spinner();
728
- spinner6.start("Fetching connections...");
764
+ const spinner5 = p2.spinner();
765
+ spinner5.start("Fetching connections...");
729
766
  let connections;
730
767
  try {
731
768
  const api = new PicaApi(apiKey);
732
769
  const rawConnections = await api.listConnections();
733
770
  connections = rawConnections.map((c) => ({ platform: c.platform, key: c.key }));
734
- spinner6.stop(`Found ${connections.length} connection(s)`);
771
+ spinner5.stop(`Found ${connections.length} connection(s)`);
735
772
  } catch {
736
- spinner6.stop("Could not fetch connections");
737
- const manual = await p.text({
773
+ spinner5.stop("Could not fetch connections");
774
+ const manual = await p2.text({
738
775
  message: "Enter connection keys manually (comma-separated):",
739
776
  placeholder: "conn_key_1, conn_key_2",
740
777
  validate: (value) => {
@@ -742,13 +779,13 @@ async function selectConnections(apiKey) {
742
779
  return void 0;
743
780
  }
744
781
  });
745
- if (p.isCancel(manual)) return void 0;
782
+ if (p2.isCancel(manual)) return void 0;
746
783
  return manual.split(",").map((s) => s.trim()).filter(Boolean);
747
784
  }
748
785
  if (connections.length === 0) {
749
786
  return [];
750
787
  }
751
- const selected = await p.multiselect({
788
+ const selected = await p2.multiselect({
752
789
  message: "Select connections:",
753
790
  options: connections.map((c) => ({
754
791
  value: c.key,
@@ -756,7 +793,7 @@ async function selectConnections(apiKey) {
756
793
  hint: c.key
757
794
  }))
758
795
  });
759
- if (p.isCancel(selected)) return void 0;
796
+ if (p2.isCancel(selected)) return void 0;
760
797
  return selected;
761
798
  }
762
799
  function formatList(list) {
@@ -806,9 +843,12 @@ function stripAnsi(str) {
806
843
 
807
844
  // src/commands/init.ts
808
845
  async function initCommand(options) {
846
+ if (isAgentMode()) {
847
+ error("This command requires interactive input. Run without --agent.");
848
+ }
809
849
  const existingConfig = readConfig();
810
850
  if (existingConfig) {
811
- p2.intro(pc3.bgCyan(pc3.black(" Pica ")));
851
+ p3.intro(pc3.bgCyan(pc3.black(" Pica ")));
812
852
  await handleExistingConfig(existingConfig.apiKey, options);
813
853
  return;
814
854
  }
@@ -880,12 +920,12 @@ async function handleExistingConfig(apiKey, options) {
880
920
  value: "start-fresh",
881
921
  label: "Start fresh (reconfigure everything)"
882
922
  });
883
- const action = await p2.select({
923
+ const action = await p3.select({
884
924
  message: "What would you like to do?",
885
925
  options: actionOptions
886
926
  });
887
- if (p2.isCancel(action)) {
888
- p2.outro("No changes made.");
927
+ if (p3.isCancel(action)) {
928
+ p3.outro("No changes made.");
889
929
  return;
890
930
  }
891
931
  switch (action) {
@@ -907,20 +947,20 @@ async function handleExistingConfig(apiKey, options) {
907
947
  }
908
948
  }
909
949
  async function handleUpdateKey(statuses) {
910
- p2.note(`Get your API key at:
950
+ p3.note(`Get your API key at:
911
951
  ${pc3.cyan(getApiKeyUrl())}`, "API Key");
912
- const openBrowser = await p2.confirm({
952
+ const openBrowser = await p3.confirm({
913
953
  message: "Open browser to get API key?",
914
954
  initialValue: true
915
955
  });
916
- if (p2.isCancel(openBrowser)) {
917
- p2.cancel("Cancelled.");
956
+ if (p3.isCancel(openBrowser)) {
957
+ p3.cancel("Cancelled.");
918
958
  process.exit(0);
919
959
  }
920
960
  if (openBrowser) {
921
961
  await openApiKeyPage();
922
962
  }
923
- const newKey = await p2.text({
963
+ const newKey = await p3.text({
924
964
  message: "Enter your new Pica API key:",
925
965
  placeholder: "sk_live_...",
926
966
  validate: (value) => {
@@ -931,20 +971,20 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
931
971
  return void 0;
932
972
  }
933
973
  });
934
- if (p2.isCancel(newKey)) {
935
- p2.cancel("Cancelled.");
974
+ if (p3.isCancel(newKey)) {
975
+ p3.cancel("Cancelled.");
936
976
  process.exit(0);
937
977
  }
938
- const spinner6 = p2.spinner();
939
- spinner6.start("Validating API key...");
978
+ const spinner5 = p3.spinner();
979
+ spinner5.start("Validating API key...");
940
980
  const api = new PicaApi(newKey);
941
981
  const isValid = await api.validateApiKey();
942
982
  if (!isValid) {
943
- spinner6.stop("Invalid API key");
944
- 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()}`);
945
985
  process.exit(1);
946
986
  }
947
- spinner6.stop("API key validated");
987
+ spinner5.stop("API key validated");
948
988
  const ac = getAccessControl();
949
989
  const reinstalled = [];
950
990
  for (const s of statuses) {
@@ -965,107 +1005,107 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
965
1005
  accessControl: config?.accessControl
966
1006
  });
967
1007
  if (reinstalled.length > 0) {
968
- p2.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
1008
+ p3.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
969
1009
  }
970
- p2.outro("API key updated.");
1010
+ p3.outro("API key updated.");
971
1011
  }
972
1012
  async function handleInstallMore(apiKey, missing) {
973
1013
  const ac = getAccessControl();
974
1014
  if (missing.length === 1) {
975
1015
  const agent = missing[0].agent;
976
- const confirm3 = await p2.confirm({
1016
+ const confirm3 = await p3.confirm({
977
1017
  message: `Install Pica MCP to ${agent.name}?`,
978
1018
  initialValue: true
979
1019
  });
980
- if (p2.isCancel(confirm3) || !confirm3) {
981
- p2.outro("No changes made.");
1020
+ if (p3.isCancel(confirm3) || !confirm3) {
1021
+ p3.outro("No changes made.");
982
1022
  return;
983
1023
  }
984
1024
  installMcpConfig(agent, apiKey, "global", ac);
985
1025
  updateConfigAgents(agent.id);
986
- p2.log.success(`${agent.name}: MCP installed`);
987
- p2.outro("Done.");
1026
+ p3.log.success(`${agent.name}: MCP installed`);
1027
+ p3.outro("Done.");
988
1028
  return;
989
1029
  }
990
- const selected = await p2.multiselect({
1030
+ const selected = await p3.multiselect({
991
1031
  message: "Select agents to install MCP:",
992
1032
  options: missing.map((s) => ({
993
1033
  value: s.agent.id,
994
1034
  label: s.agent.name
995
1035
  }))
996
1036
  });
997
- if (p2.isCancel(selected)) {
998
- p2.outro("No changes made.");
1037
+ if (p3.isCancel(selected)) {
1038
+ p3.outro("No changes made.");
999
1039
  return;
1000
1040
  }
1001
1041
  const agents = missing.filter((s) => selected.includes(s.agent.id));
1002
1042
  for (const s of agents) {
1003
1043
  installMcpConfig(s.agent, apiKey, "global", ac);
1004
1044
  updateConfigAgents(s.agent.id);
1005
- p2.log.success(`${s.agent.name}: MCP installed`);
1045
+ p3.log.success(`${s.agent.name}: MCP installed`);
1006
1046
  }
1007
- p2.outro("Done.");
1047
+ p3.outro("Done.");
1008
1048
  }
1009
1049
  async function handleInstallProject(apiKey, missing) {
1010
1050
  const ac = getAccessControl();
1011
1051
  if (missing.length === 1) {
1012
1052
  const agent = missing[0].agent;
1013
- const confirm3 = await p2.confirm({
1053
+ const confirm3 = await p3.confirm({
1014
1054
  message: `Install project-level MCP for ${agent.name}?`,
1015
1055
  initialValue: true
1016
1056
  });
1017
- if (p2.isCancel(confirm3) || !confirm3) {
1018
- p2.outro("No changes made.");
1057
+ if (p3.isCancel(confirm3) || !confirm3) {
1058
+ p3.outro("No changes made.");
1019
1059
  return;
1020
1060
  }
1021
1061
  installMcpConfig(agent, apiKey, "project", ac);
1022
1062
  const configPath = getAgentConfigPath(agent, "project");
1023
- p2.log.success(`${agent.name}: ${configPath} created`);
1024
- p2.note(
1063
+ p3.log.success(`${agent.name}: ${configPath} created`);
1064
+ p3.note(
1025
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."),
1026
1066
  "Tip"
1027
1067
  );
1028
- p2.outro("Done.");
1068
+ p3.outro("Done.");
1029
1069
  return;
1030
1070
  }
1031
- const selected = await p2.multiselect({
1071
+ const selected = await p3.multiselect({
1032
1072
  message: "Select agents for project-level MCP:",
1033
1073
  options: missing.map((s) => ({
1034
1074
  value: s.agent.id,
1035
1075
  label: s.agent.name
1036
1076
  }))
1037
1077
  });
1038
- if (p2.isCancel(selected)) {
1039
- p2.outro("No changes made.");
1078
+ if (p3.isCancel(selected)) {
1079
+ p3.outro("No changes made.");
1040
1080
  return;
1041
1081
  }
1042
1082
  const agents = missing.filter((s) => selected.includes(s.agent.id));
1043
1083
  for (const s of agents) {
1044
1084
  installMcpConfig(s.agent, apiKey, "project", ac);
1045
1085
  const configPath = getAgentConfigPath(s.agent, "project");
1046
- p2.log.success(`${s.agent.name}: ${configPath} created`);
1086
+ p3.log.success(`${s.agent.name}: ${configPath} created`);
1047
1087
  }
1048
- p2.note(
1088
+ p3.note(
1049
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."),
1050
1090
  "Tip"
1051
1091
  );
1052
- p2.outro("Done.");
1092
+ p3.outro("Done.");
1053
1093
  }
1054
1094
  async function freshSetup(options) {
1055
- p2.note(`Get your API key at:
1095
+ p3.note(`Get your API key at:
1056
1096
  ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1057
- const openBrowser = await p2.confirm({
1097
+ const openBrowser = await p3.confirm({
1058
1098
  message: "Open browser to get API key?",
1059
1099
  initialValue: true
1060
1100
  });
1061
- if (p2.isCancel(openBrowser)) {
1062
- p2.cancel("Setup cancelled.");
1101
+ if (p3.isCancel(openBrowser)) {
1102
+ p3.cancel("Setup cancelled.");
1063
1103
  process.exit(0);
1064
1104
  }
1065
1105
  if (openBrowser) {
1066
1106
  await openApiKeyPage();
1067
1107
  }
1068
- const apiKey = await p2.text({
1108
+ const apiKey = await p3.text({
1069
1109
  message: "Enter your Pica API key:",
1070
1110
  placeholder: "sk_live_...",
1071
1111
  validate: (value) => {
@@ -1076,27 +1116,27 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1076
1116
  return void 0;
1077
1117
  }
1078
1118
  });
1079
- if (p2.isCancel(apiKey)) {
1080
- p2.cancel("Setup cancelled.");
1119
+ if (p3.isCancel(apiKey)) {
1120
+ p3.cancel("Setup cancelled.");
1081
1121
  process.exit(0);
1082
1122
  }
1083
- const spinner6 = p2.spinner();
1084
- spinner6.start("Validating API key...");
1123
+ const spinner5 = p3.spinner();
1124
+ spinner5.start("Validating API key...");
1085
1125
  const api = new PicaApi(apiKey);
1086
1126
  const isValid = await api.validateApiKey();
1087
1127
  if (!isValid) {
1088
- spinner6.stop("Invalid API key");
1089
- 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()}`);
1090
1130
  process.exit(1);
1091
1131
  }
1092
- spinner6.stop("API key validated");
1132
+ spinner5.stop("API key validated");
1093
1133
  writeConfig({
1094
1134
  apiKey,
1095
1135
  installedAgents: [],
1096
1136
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1097
1137
  });
1098
1138
  const allAgents = getAllAgents();
1099
- const agentChoice = await p2.select({
1139
+ const agentChoice = await p3.select({
1100
1140
  message: "Where do you want to install the MCP?",
1101
1141
  options: [
1102
1142
  {
@@ -1110,8 +1150,8 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1110
1150
  }))
1111
1151
  ]
1112
1152
  });
1113
- if (p2.isCancel(agentChoice)) {
1114
- p2.cancel("Setup cancelled.");
1153
+ if (p3.isCancel(agentChoice)) {
1154
+ p3.cancel("Setup cancelled.");
1115
1155
  process.exit(0);
1116
1156
  }
1117
1157
  const selectedAgents = agentChoice === "all" ? allAgents : allAgents.filter((a) => a.id === agentChoice);
@@ -1122,7 +1162,7 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1122
1162
  } else if (options.project) {
1123
1163
  scope = "project";
1124
1164
  } else if (hasProjectScopeAgent) {
1125
- const scopeChoice = await p2.select({
1165
+ const scopeChoice = await p3.select({
1126
1166
  message: "How do you want to install it?",
1127
1167
  options: [
1128
1168
  {
@@ -1137,8 +1177,8 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1137
1177
  }
1138
1178
  ]
1139
1179
  });
1140
- if (p2.isCancel(scopeChoice)) {
1141
- p2.cancel("Setup cancelled.");
1180
+ if (p3.isCancel(scopeChoice)) {
1181
+ p3.cancel("Setup cancelled.");
1142
1182
  process.exit(0);
1143
1183
  }
1144
1184
  scope = scopeChoice;
@@ -1148,12 +1188,12 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
1148
1188
  const nonProjectAgents = selectedAgents.filter((a) => !supportsProjectScope(a));
1149
1189
  if (projectAgents.length === 0) {
1150
1190
  const supported = allAgents.filter((a) => supportsProjectScope(a)).map((a) => a.name).join(", ");
1151
- p2.note(
1191
+ p3.note(
1152
1192
  `${selectedAgents.map((a) => a.name).join(", ")} does not support project-level MCP.
1153
1193
  Project scope is supported by: ${supported}`,
1154
1194
  "Not Supported"
1155
1195
  );
1156
- 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.");
1157
1197
  process.exit(1);
1158
1198
  }
1159
1199
  for (const agent of projectAgents) {
@@ -1161,15 +1201,15 @@ Project scope is supported by: ${supported}`,
1161
1201
  installMcpConfig(agent, apiKey, "project");
1162
1202
  const configPath = getAgentConfigPath(agent, "project");
1163
1203
  const status = wasInstalled ? "updated" : "created";
1164
- p2.log.success(`${agent.name}: ${configPath} ${status}`);
1204
+ p3.log.success(`${agent.name}: ${configPath} ${status}`);
1165
1205
  }
1166
1206
  if (nonProjectAgents.length > 0) {
1167
- p2.log.info(`Installing globally for agents without project scope support:`);
1207
+ p3.log.info(`Installing globally for agents without project scope support:`);
1168
1208
  for (const agent of nonProjectAgents) {
1169
1209
  const wasInstalled = isMcpInstalled(agent, "global");
1170
1210
  installMcpConfig(agent, apiKey, "global");
1171
1211
  const status = wasInstalled ? "updated" : "installed";
1172
- p2.log.success(`${agent.name}: MCP ${status} (global)`);
1212
+ p3.log.success(`${agent.name}: MCP ${status} (global)`);
1173
1213
  }
1174
1214
  }
1175
1215
  const allInstalled = [...projectAgents, ...nonProjectAgents];
@@ -1192,9 +1232,9 @@ ${globalPaths}
1192
1232
  `;
1193
1233
  }
1194
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.");
1195
- p2.note(summary, "Setup Complete");
1235
+ p3.note(summary, "Setup Complete");
1196
1236
  await promptConnectIntegrations(apiKey);
1197
- p2.outro("Your AI agents now have access to Pica integrations!");
1237
+ p3.outro("Your AI agents now have access to Pica integrations!");
1198
1238
  return;
1199
1239
  }
1200
1240
  const installedAgentIds = [];
@@ -1203,19 +1243,19 @@ ${globalPaths}
1203
1243
  installMcpConfig(agent, apiKey, "global");
1204
1244
  installedAgentIds.push(agent.id);
1205
1245
  const status = wasInstalled ? "updated" : "installed";
1206
- p2.log.success(`${agent.name}: MCP ${status}`);
1246
+ p3.log.success(`${agent.name}: MCP ${status}`);
1207
1247
  }
1208
1248
  writeConfig({
1209
1249
  apiKey,
1210
1250
  installedAgents: installedAgentIds,
1211
1251
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1212
1252
  });
1213
- p2.note(
1253
+ p3.note(
1214
1254
  `Config saved to: ${pc3.dim(getConfigPath())}`,
1215
1255
  "Setup Complete"
1216
1256
  );
1217
1257
  await promptConnectIntegrations(apiKey);
1218
- p2.outro("Your AI agents now have access to Pica integrations!");
1258
+ p3.outro("Your AI agents now have access to Pica integrations!");
1219
1259
  }
1220
1260
  function printBanner() {
1221
1261
  console.log();
@@ -1264,48 +1304,48 @@ async function promptConnectIntegrations(apiKey) {
1264
1304
  { value: "skip", label: "Skip for now", hint: "you can always run pica add later" }
1265
1305
  ];
1266
1306
  const message = first ? "Connect your first integration?" : "Connect another?";
1267
- const choice = await p2.select({ message, options });
1268
- if (p2.isCancel(choice) || choice === "skip") {
1307
+ const choice = await p3.select({ message, options });
1308
+ if (p3.isCancel(choice) || choice === "skip") {
1269
1309
  break;
1270
1310
  }
1271
1311
  if (choice === "more") {
1272
1312
  try {
1273
1313
  await open2("https://app.picaos.com/connections");
1274
- p2.log.info("Opened Pica dashboard in browser.");
1314
+ p3.log.info("Opened Pica dashboard in browser.");
1275
1315
  } catch {
1276
- p2.note("https://app.picaos.com/connections", "Open in browser");
1316
+ p3.note("https://app.picaos.com/connections", "Open in browser");
1277
1317
  }
1278
- 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>")}`);
1279
1319
  break;
1280
1320
  }
1281
1321
  const platform = choice;
1282
1322
  const integration = TOP_INTEGRATIONS.find((i) => i.value === platform);
1283
1323
  const label = integration?.label ?? platform;
1284
- p2.log.info(`Opening browser to connect ${pc3.cyan(label)}...`);
1324
+ p3.log.info(`Opening browser to connect ${pc3.cyan(label)}...`);
1285
1325
  try {
1286
1326
  await openConnectionPage(platform);
1287
1327
  } catch {
1288
1328
  const url = getConnectionUrl(platform);
1289
- p2.log.warn("Could not open browser automatically.");
1290
- p2.note(url, "Open manually");
1329
+ p3.log.warn("Could not open browser automatically.");
1330
+ p3.note(url, "Open manually");
1291
1331
  }
1292
- const spinner6 = p2.spinner();
1293
- spinner6.start("Waiting for connection... (complete auth in browser)");
1332
+ const spinner5 = p3.spinner();
1333
+ spinner5.start("Waiting for connection... (complete auth in browser)");
1294
1334
  try {
1295
1335
  await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
1296
- spinner6.stop(`${label} connected!`);
1297
- 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`);
1298
1338
  connected.push(platform);
1299
1339
  first = false;
1300
- } catch (error) {
1301
- spinner6.stop("Connection timed out");
1302
- if (error instanceof TimeoutError) {
1303
- 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}`)}`);
1304
1344
  }
1305
1345
  first = false;
1306
1346
  }
1307
1347
  if (TOP_INTEGRATIONS.every((i) => connected.includes(i.value))) {
1308
- p2.log.success("All top integrations connected!");
1348
+ p3.log.success("All top integrations connected!");
1309
1349
  break;
1310
1350
  }
1311
1351
  }
@@ -1324,23 +1364,23 @@ function updateConfigAgents(agentId) {
1324
1364
  }
1325
1365
 
1326
1366
  // src/commands/connection.ts
1327
- import * as p3 from "@clack/prompts";
1367
+ import * as p4 from "@clack/prompts";
1328
1368
  import pc4 from "picocolors";
1329
1369
 
1330
1370
  // src/lib/platforms.ts
1331
1371
  function findPlatform(platforms, query) {
1332
1372
  const normalizedQuery = query.toLowerCase().trim();
1333
1373
  const exact = platforms.find(
1334
- (p6) => p6.platform.toLowerCase() === normalizedQuery || p6.name.toLowerCase() === normalizedQuery
1374
+ (p7) => p7.platform.toLowerCase() === normalizedQuery || p7.name.toLowerCase() === normalizedQuery
1335
1375
  );
1336
1376
  if (exact) return exact;
1337
1377
  return null;
1338
1378
  }
1339
1379
  function findSimilarPlatforms(platforms, query, limit = 3) {
1340
1380
  const normalizedQuery = query.toLowerCase().trim();
1341
- const scored = platforms.map((p6) => {
1342
- const name = p6.name.toLowerCase();
1343
- const slug = p6.platform.toLowerCase();
1381
+ const scored = platforms.map((p7) => {
1382
+ const name = p7.name.toLowerCase();
1383
+ const slug = p7.platform.toLowerCase();
1344
1384
  let score = 0;
1345
1385
  if (name.includes(normalizedQuery) || slug.includes(normalizedQuery)) {
1346
1386
  score = 10;
@@ -1349,7 +1389,7 @@ function findSimilarPlatforms(platforms, query, limit = 3) {
1349
1389
  } else {
1350
1390
  score = countMatchingChars(normalizedQuery, slug);
1351
1391
  }
1352
- return { platform: p6, score };
1392
+ return { platform: p7, score };
1353
1393
  }).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
1354
1394
  return scored.map((item) => item.platform);
1355
1395
  }
@@ -1364,22 +1404,25 @@ function countMatchingChars(a, b) {
1364
1404
 
1365
1405
  // src/commands/connection.ts
1366
1406
  async function connectionAddCommand(platformArg) {
1367
- 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 ")));
1368
1411
  const apiKey = getApiKey();
1369
1412
  if (!apiKey) {
1370
- p3.cancel("Not configured. Run `pica init` first.");
1413
+ p4.cancel("Not configured. Run `pica init` first.");
1371
1414
  process.exit(1);
1372
1415
  }
1373
1416
  const api = new PicaApi(apiKey);
1374
- const spinner6 = p3.spinner();
1375
- spinner6.start("Loading platforms...");
1417
+ const spinner5 = p4.spinner();
1418
+ spinner5.start("Loading platforms...");
1376
1419
  let platforms;
1377
1420
  try {
1378
1421
  platforms = await api.listPlatforms();
1379
- spinner6.stop(`${platforms.length} platforms available`);
1380
- } catch (error) {
1381
- spinner6.stop("Failed to load platforms");
1382
- 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"}`);
1383
1426
  process.exit(1);
1384
1427
  }
1385
1428
  let platform;
@@ -1390,29 +1433,29 @@ async function connectionAddCommand(platformArg) {
1390
1433
  } else {
1391
1434
  const similar = findSimilarPlatforms(platforms, platformArg);
1392
1435
  if (similar.length > 0) {
1393
- p3.log.warn(`Unknown platform: ${platformArg}`);
1394
- const suggestion = await p3.select({
1436
+ p4.log.warn(`Unknown platform: ${platformArg}`);
1437
+ const suggestion = await p4.select({
1395
1438
  message: "Did you mean:",
1396
1439
  options: [
1397
1440
  ...similar.map((s) => ({ value: s.platform, label: `${s.name} (${s.platform})` })),
1398
1441
  { value: "__other__", label: "None of these" }
1399
1442
  ]
1400
1443
  });
1401
- if (p3.isCancel(suggestion) || suggestion === "__other__") {
1402
- p3.note(`Run ${pc4.cyan("pica platforms")} to see all available platforms.`);
1403
- 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.");
1404
1447
  process.exit(0);
1405
1448
  }
1406
1449
  platform = suggestion;
1407
1450
  } else {
1408
- p3.cancel(`Unknown platform: ${platformArg}
1451
+ p4.cancel(`Unknown platform: ${platformArg}
1409
1452
 
1410
1453
  Run ${pc4.cyan("pica platforms")} to see available platforms.`);
1411
1454
  process.exit(1);
1412
1455
  }
1413
1456
  }
1414
1457
  } else {
1415
- const platformInput = await p3.text({
1458
+ const platformInput = await p4.text({
1416
1459
  message: "Which platform do you want to connect?",
1417
1460
  placeholder: "gmail, slack, hubspot...",
1418
1461
  validate: (value) => {
@@ -1420,41 +1463,41 @@ Run ${pc4.cyan("pica platforms")} to see available platforms.`);
1420
1463
  return void 0;
1421
1464
  }
1422
1465
  });
1423
- if (p3.isCancel(platformInput)) {
1424
- p3.cancel("Connection cancelled.");
1466
+ if (p4.isCancel(platformInput)) {
1467
+ p4.cancel("Connection cancelled.");
1425
1468
  process.exit(0);
1426
1469
  }
1427
1470
  const found = findPlatform(platforms, platformInput);
1428
1471
  if (found) {
1429
1472
  platform = found.platform;
1430
1473
  } else {
1431
- p3.cancel(`Unknown platform: ${platformInput}
1474
+ p4.cancel(`Unknown platform: ${platformInput}
1432
1475
 
1433
1476
  Run ${pc4.cyan("pica platforms")} to see available platforms.`);
1434
1477
  process.exit(1);
1435
1478
  }
1436
1479
  }
1437
1480
  const url = getConnectionUrl(platform);
1438
- p3.log.info(`Opening browser to connect ${pc4.cyan(platform)}...`);
1439
- p3.note(pc4.dim(url), "URL");
1481
+ p4.log.info(`Opening browser to connect ${pc4.cyan(platform)}...`);
1482
+ p4.note(pc4.dim(url), "URL");
1440
1483
  try {
1441
1484
  await openConnectionPage(platform);
1442
1485
  } catch {
1443
- p3.log.warn("Could not open browser automatically.");
1444
- p3.note(`Open this URL manually:
1486
+ p4.log.warn("Could not open browser automatically.");
1487
+ p4.note(`Open this URL manually:
1445
1488
  ${url}`);
1446
1489
  }
1447
- const pollSpinner = p3.spinner();
1490
+ const pollSpinner = p4.spinner();
1448
1491
  pollSpinner.start("Waiting for connection... (complete auth in browser)");
1449
1492
  try {
1450
1493
  const connection2 = await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
1451
1494
  pollSpinner.stop(`${platform} connected!`);
1452
- p3.log.success(`${pc4.green("\u2713")} ${connection2.platform} is now available to your AI agents.`);
1453
- p3.outro("Connection complete!");
1454
- } 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) {
1455
1498
  pollSpinner.stop("Connection timed out");
1456
- if (error instanceof TimeoutError) {
1457
- p3.note(
1499
+ if (error2 instanceof TimeoutError) {
1500
+ p4.note(
1458
1501
  `Possible issues:
1459
1502
  - OAuth flow was not completed in the browser
1460
1503
  - Browser popup was blocked
@@ -1464,7 +1507,7 @@ Try again with: ${pc4.cyan(`pica connection add ${platform}`)}`,
1464
1507
  "Timed Out"
1465
1508
  );
1466
1509
  } else {
1467
- p3.log.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1510
+ p4.log.error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1468
1511
  }
1469
1512
  process.exit(1);
1470
1513
  }
@@ -1472,17 +1515,26 @@ Try again with: ${pc4.cyan(`pica connection add ${platform}`)}`,
1472
1515
  async function connectionListCommand() {
1473
1516
  const apiKey = getApiKey();
1474
1517
  if (!apiKey) {
1475
- p3.cancel("Not configured. Run `pica init` first.");
1476
- process.exit(1);
1518
+ error("Not configured. Run `pica init` first.");
1477
1519
  }
1478
1520
  const api = new PicaApi(apiKey);
1479
- const spinner6 = p3.spinner();
1480
- spinner6.start("Loading connections...");
1521
+ const spinner5 = createSpinner();
1522
+ spinner5.start("Loading connections...");
1481
1523
  try {
1482
1524
  const connections = await api.listConnections();
1483
- 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`);
1484
1536
  if (connections.length === 0) {
1485
- p3.note(
1537
+ p4.note(
1486
1538
  `No connections yet.
1487
1539
 
1488
1540
  Add one with: ${pc4.cyan("pica connection add gmail")}`,
@@ -1507,11 +1559,10 @@ Add one with: ${pc4.cyan("pica connection add gmail")}`,
1507
1559
  rows
1508
1560
  );
1509
1561
  console.log();
1510
- p3.note(`Add more with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
1511
- } catch (error) {
1512
- spinner6.stop("Failed to load connections");
1513
- p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1514
- 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"}`);
1515
1566
  }
1516
1567
  }
1517
1568
  function getStatusIndicator(state) {
@@ -1528,22 +1579,28 @@ function getStatusIndicator(state) {
1528
1579
  }
1529
1580
 
1530
1581
  // src/commands/platforms.ts
1531
- import * as p4 from "@clack/prompts";
1582
+ import * as p5 from "@clack/prompts";
1532
1583
  import pc5 from "picocolors";
1533
1584
  async function platformsCommand(options) {
1534
1585
  const apiKey = getApiKey();
1535
1586
  if (!apiKey) {
1536
- p4.cancel("Not configured. Run `pica init` first.");
1537
- process.exit(1);
1587
+ error("Not configured. Run `pica init` first.");
1588
+ }
1589
+ if (isAgentMode()) {
1590
+ options.json = true;
1538
1591
  }
1539
1592
  const api = new PicaApi(apiKey);
1540
- const spinner6 = p4.spinner();
1541
- spinner6.start("Loading platforms...");
1593
+ const spinner5 = createSpinner();
1594
+ spinner5.start("Loading platforms...");
1542
1595
  try {
1543
1596
  const platforms = await api.listPlatforms();
1544
- spinner6.stop(`${platforms.length} platforms available`);
1597
+ spinner5.stop(`${platforms.length} platforms available`);
1545
1598
  if (options.json) {
1546
- console.log(JSON.stringify(platforms, null, 2));
1599
+ if (isAgentMode()) {
1600
+ json({ platforms });
1601
+ } else {
1602
+ console.log(JSON.stringify(platforms, null, 2));
1603
+ }
1547
1604
  return;
1548
1605
  }
1549
1606
  const byCategory = /* @__PURE__ */ new Map();
@@ -1559,7 +1616,7 @@ async function platformsCommand(options) {
1559
1616
  const categoryPlatforms = byCategory.get(options.category);
1560
1617
  if (!categoryPlatforms) {
1561
1618
  const categories = [...byCategory.keys()].sort();
1562
- p4.note(`Available categories:
1619
+ p5.note(`Available categories:
1563
1620
  ${categories.join(", ")}`, "Unknown Category");
1564
1621
  process.exit(1);
1565
1622
  }
@@ -1591,22 +1648,20 @@ async function platformsCommand(options) {
1591
1648
  );
1592
1649
  }
1593
1650
  console.log();
1594
- p4.note(`Connect with: ${pc5.cyan("pica connection add <platform>")}`, "Tip");
1595
- } catch (error) {
1596
- spinner6.stop("Failed to load platforms");
1597
- p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1598
- 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"}`);
1599
1655
  }
1600
1656
  }
1601
1657
 
1602
1658
  // src/commands/actions.ts
1603
- import * as p5 from "@clack/prompts";
1659
+ import * as p6 from "@clack/prompts";
1604
1660
  import pc6 from "picocolors";
1605
1661
  function getConfig() {
1606
1662
  const apiKey = getApiKey();
1607
1663
  if (!apiKey) {
1608
- p5.cancel("Not configured. Run `pica init` first.");
1609
- process.exit(1);
1664
+ error("Not configured. Run `pica init` first.");
1610
1665
  }
1611
1666
  const ac = getAccessControlFromAllSources();
1612
1667
  const permissions = ac.permissions || "admin";
@@ -1615,12 +1670,19 @@ function getConfig() {
1615
1670
  const knowledgeAgent = ac.knowledgeAgent || false;
1616
1671
  return { apiKey, permissions, connectionKeys, actionIds, knowledgeAgent };
1617
1672
  }
1673
+ function parseJsonArg(value, argName) {
1674
+ try {
1675
+ return JSON.parse(value);
1676
+ } catch {
1677
+ error(`Invalid JSON for ${argName}: ${value}`);
1678
+ }
1679
+ }
1618
1680
  async function actionsSearchCommand(platform, query, options) {
1619
- p5.intro(pc6.bgCyan(pc6.black(" Pica ")));
1681
+ intro2(pc6.bgCyan(pc6.black(" Pica ")));
1620
1682
  const { apiKey, permissions, actionIds, knowledgeAgent } = getConfig();
1621
1683
  const api = new PicaApi(apiKey);
1622
- const spinner6 = p5.spinner();
1623
- 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}"...`);
1624
1686
  try {
1625
1687
  const agentType = knowledgeAgent ? "knowledge" : options.type;
1626
1688
  let actions2 = await api.searchActions(platform, query, agentType);
@@ -1632,9 +1694,13 @@ async function actionsSearchCommand(platform, query, options) {
1632
1694
  method: action.method,
1633
1695
  path: action.path
1634
1696
  }));
1697
+ if (isAgentMode()) {
1698
+ json({ actions: cleanedActions });
1699
+ return;
1700
+ }
1635
1701
  if (cleanedActions.length === 0) {
1636
- spinner6.stop("No actions found");
1637
- p5.note(
1702
+ spinner5.stop("No actions found");
1703
+ p6.note(
1638
1704
  `No actions found for platform '${platform}' matching query '${query}'.
1639
1705
 
1640
1706
  Suggestions:
@@ -1651,7 +1717,7 @@ Examples of good queries:
1651
1717
  );
1652
1718
  return;
1653
1719
  }
1654
- spinner6.stop(
1720
+ spinner5.stop(
1655
1721
  `Found ${cleanedActions.length} action(s) for '${platform}' matching '${query}'`
1656
1722
  );
1657
1723
  console.log();
@@ -1671,49 +1737,45 @@ Examples of good queries:
1671
1737
  rows
1672
1738
  );
1673
1739
  console.log();
1674
- p5.note(
1740
+ p6.note(
1675
1741
  `Get details: ${pc6.cyan(`pica actions knowledge ${platform} <actionId>`)}
1676
1742
  Execute: ${pc6.cyan(`pica actions execute ${platform} <actionId> <connectionKey>`)}`,
1677
1743
  "Next Steps"
1678
1744
  );
1679
- } catch (error) {
1680
- spinner6.stop("Search failed");
1681
- p5.cancel(
1682
- `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"}`
1683
1749
  );
1684
- process.exit(1);
1685
1750
  }
1686
1751
  }
1687
1752
  async function actionsKnowledgeCommand(platform, actionId) {
1688
- p5.intro(pc6.bgCyan(pc6.black(" Pica ")));
1753
+ intro2(pc6.bgCyan(pc6.black(" Pica ")));
1689
1754
  const { apiKey, actionIds, connectionKeys } = getConfig();
1690
1755
  const api = new PicaApi(apiKey);
1691
1756
  if (!isActionAllowed(actionId, actionIds)) {
1692
- p5.cancel(`Action "${actionId}" is not in the allowed action list.`);
1693
- process.exit(1);
1757
+ error(`Action "${actionId}" is not in the allowed action list.`);
1694
1758
  }
1695
1759
  if (!connectionKeys.includes("*")) {
1696
- const spinner7 = p5.spinner();
1697
- spinner7.start("Checking connections...");
1760
+ const spinner6 = createSpinner();
1761
+ spinner6.start("Checking connections...");
1698
1762
  try {
1699
1763
  const connections = await api.listConnections();
1700
1764
  const connectedPlatforms = connections.map((c) => c.platform);
1701
1765
  if (!connectedPlatforms.includes(platform)) {
1702
- spinner7.stop("Platform not connected");
1703
- p5.cancel(`Platform "${platform}" has no allowed connections.`);
1704
- process.exit(1);
1766
+ spinner6.stop("Platform not connected");
1767
+ error(`Platform "${platform}" has no allowed connections.`);
1705
1768
  }
1706
- spinner7.stop("Connection verified");
1707
- } catch (error) {
1708
- spinner7.stop("Failed to check connections");
1709
- p5.cancel(
1710
- `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"}`
1711
1774
  );
1712
- process.exit(1);
1713
1775
  }
1714
1776
  }
1715
- const spinner6 = p5.spinner();
1716
- spinner6.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
1777
+ const spinner5 = createSpinner();
1778
+ spinner5.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
1717
1779
  try {
1718
1780
  const { knowledge, method } = await api.getActionKnowledge(actionId);
1719
1781
  const knowledgeWithGuidance = buildActionKnowledgeWithGuidance(
@@ -1722,58 +1784,56 @@ async function actionsKnowledgeCommand(platform, actionId) {
1722
1784
  platform,
1723
1785
  actionId
1724
1786
  );
1725
- spinner6.stop("Knowledge loaded");
1787
+ if (isAgentMode()) {
1788
+ json({ knowledge: knowledgeWithGuidance, method });
1789
+ return;
1790
+ }
1791
+ spinner5.stop("Knowledge loaded");
1726
1792
  console.log();
1727
1793
  console.log(knowledgeWithGuidance);
1728
1794
  console.log();
1729
- p5.note(
1795
+ p6.note(
1730
1796
  `Execute: ${pc6.cyan(`pica actions execute ${platform} ${actionId} <connectionKey>`)}`,
1731
1797
  "Next Step"
1732
1798
  );
1733
- } catch (error) {
1734
- spinner6.stop("Failed to load knowledge");
1735
- p5.cancel(
1736
- `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"}`
1737
1803
  );
1738
- process.exit(1);
1739
1804
  }
1740
1805
  }
1741
1806
  async function actionsExecuteCommand(platform, actionId, connectionKey, options) {
1742
- p5.intro(pc6.bgCyan(pc6.black(" Pica ")));
1807
+ intro2(pc6.bgCyan(pc6.black(" Pica ")));
1743
1808
  const { apiKey, permissions, actionIds, connectionKeys, knowledgeAgent } = getConfig();
1744
1809
  if (knowledgeAgent) {
1745
- p5.cancel(
1746
- `Action execution is disabled (knowledge-only mode).
1747
- Configure with: ${pc6.cyan("pica config")}`
1810
+ error(
1811
+ "Action execution is disabled (knowledge-only mode)."
1748
1812
  );
1749
- process.exit(1);
1750
1813
  }
1751
1814
  if (!isActionAllowed(actionId, actionIds)) {
1752
- p5.cancel(`Action "${actionId}" is not in the allowed action list.`);
1753
- process.exit(1);
1815
+ error(`Action "${actionId}" is not in the allowed action list.`);
1754
1816
  }
1755
1817
  if (!connectionKeys.includes("*") && !connectionKeys.includes(connectionKey)) {
1756
- p5.cancel(`Connection key "${connectionKey}" is not allowed.`);
1757
- process.exit(1);
1818
+ error(`Connection key "${connectionKey}" is not allowed.`);
1758
1819
  }
1759
1820
  const api = new PicaApi(apiKey);
1760
- const spinner6 = p5.spinner();
1761
- spinner6.start("Loading action details...");
1821
+ const spinner5 = createSpinner();
1822
+ spinner5.start("Loading action details...");
1762
1823
  try {
1763
1824
  const actionDetails = await api.getActionDetails(actionId);
1764
1825
  if (!isMethodAllowed(actionDetails.method, permissions)) {
1765
- spinner6.stop("Permission denied");
1766
- p5.cancel(
1826
+ spinner5.stop("Permission denied");
1827
+ error(
1767
1828
  `Method "${actionDetails.method}" is not allowed under "${permissions}" permission level.`
1768
1829
  );
1769
- process.exit(1);
1770
1830
  }
1771
- spinner6.stop(`Action: ${actionDetails.title} [${actionDetails.method}]`);
1831
+ spinner5.stop(`Action: ${actionDetails.title} [${actionDetails.method}]`);
1772
1832
  const data = options.data ? parseJsonArg(options.data, "--data") : void 0;
1773
1833
  const pathVariables = options.pathVars ? parseJsonArg(options.pathVars, "--path-vars") : void 0;
1774
1834
  const queryParams = options.queryParams ? parseJsonArg(options.queryParams, "--query-params") : void 0;
1775
1835
  const headers = options.headers ? parseJsonArg(options.headers, "--headers") : void 0;
1776
- const execSpinner = p5.spinner();
1836
+ const execSpinner = createSpinner();
1777
1837
  execSpinner.start("Executing action...");
1778
1838
  const result = await api.executePassthroughRequest(
1779
1839
  {
@@ -1790,6 +1850,16 @@ Configure with: ${pc6.cyan("pica config")}`
1790
1850
  actionDetails
1791
1851
  );
1792
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
+ }
1793
1863
  console.log();
1794
1864
  console.log(pc6.dim("Request:"));
1795
1865
  console.log(
@@ -1800,20 +1870,11 @@ Configure with: ${pc6.cyan("pica config")}`
1800
1870
  console.log();
1801
1871
  console.log(pc6.bold("Response:"));
1802
1872
  console.log(JSON.stringify(result.responseData, null, 2));
1803
- } catch (error) {
1804
- spinner6.stop("Execution failed");
1805
- p5.cancel(
1806
- `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"}`
1807
1877
  );
1808
- process.exit(1);
1809
- }
1810
- }
1811
- function parseJsonArg(value, argName) {
1812
- try {
1813
- return JSON.parse(value);
1814
- } catch {
1815
- p5.cancel(`Invalid JSON for ${argName}: ${value}`);
1816
- process.exit(1);
1817
1878
  }
1818
1879
  }
1819
1880
  function colorMethod(method) {
@@ -1837,7 +1898,7 @@ function colorMethod(method) {
1837
1898
  var require2 = createRequire(import.meta.url);
1838
1899
  var { version } = require2("../package.json");
1839
1900
  var program = new Command();
1840
- 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.
1841
1902
 
1842
1903
  Setup:
1843
1904
  pica init Set up API key and install MCP server
@@ -1865,6 +1926,12 @@ program.name("pica").description(`Pica CLI \u2014 Connect AI agents to 200+ plat
1865
1926
 
1866
1927
  Platform names are always kebab-case (e.g. hub-spot, ship-station, google-calendar).
1867
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
+ });
1868
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) => {
1869
1936
  await initCommand(options);
1870
1937
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picahq/cli",
3
- "version": "1.9.2",
3
+ "version": "1.9.3",
4
4
  "description": "CLI for managing Pica",
5
5
  "type": "module",
6
6
  "files": [