@picahq/cli 1.9.2 → 1.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +317 -250
- 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
|
|
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(
|
|
117
|
-
if (
|
|
118
|
-
return path2.join(os2.homedir(),
|
|
116
|
+
function expandPath(p7) {
|
|
117
|
+
if (p7.startsWith("~/")) {
|
|
118
|
+
return path2.join(os2.homedir(), p7.slice(2));
|
|
119
119
|
}
|
|
120
|
-
return
|
|
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 (
|
|
341
|
-
if (
|
|
340
|
+
} catch (error2) {
|
|
341
|
+
if (error2 instanceof ApiError && error2.status === 401) {
|
|
342
342
|
return false;
|
|
343
343
|
}
|
|
344
|
-
throw
|
|
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
|
|
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
|
-
|
|
651
|
+
p2.log.error(`No Pica config found. Run ${pc.cyan("pica init")} first.`);
|
|
615
652
|
return;
|
|
616
653
|
}
|
|
617
|
-
|
|
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
|
|
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 (
|
|
637
|
-
|
|
673
|
+
if (p2.isCancel(permissions)) {
|
|
674
|
+
p2.outro("No changes made.");
|
|
638
675
|
return;
|
|
639
676
|
}
|
|
640
|
-
const connectionMode = await
|
|
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 (
|
|
649
|
-
|
|
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
|
-
|
|
693
|
+
p2.outro("No changes made.");
|
|
657
694
|
return;
|
|
658
695
|
}
|
|
659
696
|
if (connectionKeys.length === 0) {
|
|
660
|
-
|
|
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
|
|
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 (
|
|
673
|
-
|
|
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
|
|
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 (
|
|
688
|
-
|
|
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
|
|
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 (
|
|
698
|
-
|
|
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
|
-
|
|
759
|
+
p2.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
|
|
723
760
|
}
|
|
724
|
-
|
|
761
|
+
p2.outro("Access control updated.");
|
|
725
762
|
}
|
|
726
763
|
async function selectConnections(apiKey) {
|
|
727
|
-
const
|
|
728
|
-
|
|
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
|
-
|
|
771
|
+
spinner5.stop(`Found ${connections.length} connection(s)`);
|
|
735
772
|
} catch {
|
|
736
|
-
|
|
737
|
-
const manual = await
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
923
|
+
const action = await p3.select({
|
|
884
924
|
message: "What would you like to do?",
|
|
885
925
|
options: actionOptions
|
|
886
926
|
});
|
|
887
|
-
if (
|
|
888
|
-
|
|
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
|
-
|
|
950
|
+
p3.note(`Get your API key at:
|
|
911
951
|
${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
912
|
-
const openBrowser = await
|
|
952
|
+
const openBrowser = await p3.confirm({
|
|
913
953
|
message: "Open browser to get API key?",
|
|
914
954
|
initialValue: true
|
|
915
955
|
});
|
|
916
|
-
if (
|
|
917
|
-
|
|
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
|
|
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 (
|
|
935
|
-
|
|
974
|
+
if (p3.isCancel(newKey)) {
|
|
975
|
+
p3.cancel("Cancelled.");
|
|
936
976
|
process.exit(0);
|
|
937
977
|
}
|
|
938
|
-
const
|
|
939
|
-
|
|
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
|
-
|
|
944
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1008
|
+
p3.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
|
|
969
1009
|
}
|
|
970
|
-
|
|
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
|
|
1016
|
+
const confirm3 = await p3.confirm({
|
|
977
1017
|
message: `Install Pica MCP to ${agent.name}?`,
|
|
978
1018
|
initialValue: true
|
|
979
1019
|
});
|
|
980
|
-
if (
|
|
981
|
-
|
|
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
|
-
|
|
987
|
-
|
|
1026
|
+
p3.log.success(`${agent.name}: MCP installed`);
|
|
1027
|
+
p3.outro("Done.");
|
|
988
1028
|
return;
|
|
989
1029
|
}
|
|
990
|
-
const selected = await
|
|
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 (
|
|
998
|
-
|
|
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
|
-
|
|
1045
|
+
p3.log.success(`${s.agent.name}: MCP installed`);
|
|
1006
1046
|
}
|
|
1007
|
-
|
|
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
|
|
1053
|
+
const confirm3 = await p3.confirm({
|
|
1014
1054
|
message: `Install project-level MCP for ${agent.name}?`,
|
|
1015
1055
|
initialValue: true
|
|
1016
1056
|
});
|
|
1017
|
-
if (
|
|
1018
|
-
|
|
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
|
-
|
|
1024
|
-
|
|
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
|
-
|
|
1068
|
+
p3.outro("Done.");
|
|
1029
1069
|
return;
|
|
1030
1070
|
}
|
|
1031
|
-
const selected = await
|
|
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 (
|
|
1039
|
-
|
|
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
|
-
|
|
1086
|
+
p3.log.success(`${s.agent.name}: ${configPath} created`);
|
|
1047
1087
|
}
|
|
1048
|
-
|
|
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
|
-
|
|
1092
|
+
p3.outro("Done.");
|
|
1053
1093
|
}
|
|
1054
1094
|
async function freshSetup(options) {
|
|
1055
|
-
|
|
1095
|
+
p3.note(`Get your API key at:
|
|
1056
1096
|
${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
1057
|
-
const openBrowser = await
|
|
1097
|
+
const openBrowser = await p3.confirm({
|
|
1058
1098
|
message: "Open browser to get API key?",
|
|
1059
1099
|
initialValue: true
|
|
1060
1100
|
});
|
|
1061
|
-
if (
|
|
1062
|
-
|
|
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
|
|
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 (
|
|
1080
|
-
|
|
1119
|
+
if (p3.isCancel(apiKey)) {
|
|
1120
|
+
p3.cancel("Setup cancelled.");
|
|
1081
1121
|
process.exit(0);
|
|
1082
1122
|
}
|
|
1083
|
-
const
|
|
1084
|
-
|
|
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
|
-
|
|
1089
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
1114
|
-
|
|
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
|
|
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 (
|
|
1141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1204
|
+
p3.log.success(`${agent.name}: ${configPath} ${status}`);
|
|
1165
1205
|
}
|
|
1166
1206
|
if (nonProjectAgents.length > 0) {
|
|
1167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1235
|
+
p3.note(summary, "Setup Complete");
|
|
1196
1236
|
await promptConnectIntegrations(apiKey);
|
|
1197
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1253
|
+
p3.note(
|
|
1214
1254
|
`Config saved to: ${pc3.dim(getConfigPath())}`,
|
|
1215
1255
|
"Setup Complete"
|
|
1216
1256
|
);
|
|
1217
1257
|
await promptConnectIntegrations(apiKey);
|
|
1218
|
-
|
|
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
|
|
1268
|
-
if (
|
|
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
|
-
|
|
1314
|
+
p3.log.info("Opened Pica dashboard in browser.");
|
|
1275
1315
|
} catch {
|
|
1276
|
-
|
|
1316
|
+
p3.note("https://app.picaos.com/connections", "Open in browser");
|
|
1277
1317
|
}
|
|
1278
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1290
|
-
|
|
1329
|
+
p3.log.warn("Could not open browser automatically.");
|
|
1330
|
+
p3.note(url, "Open manually");
|
|
1291
1331
|
}
|
|
1292
|
-
const
|
|
1293
|
-
|
|
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
|
-
|
|
1297
|
-
|
|
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 (
|
|
1301
|
-
|
|
1302
|
-
if (
|
|
1303
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
(
|
|
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((
|
|
1342
|
-
const name =
|
|
1343
|
-
const slug =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1375
|
-
|
|
1417
|
+
const spinner5 = p4.spinner();
|
|
1418
|
+
spinner5.start("Loading platforms...");
|
|
1376
1419
|
let platforms;
|
|
1377
1420
|
try {
|
|
1378
1421
|
platforms = await api.listPlatforms();
|
|
1379
|
-
|
|
1380
|
-
} catch (
|
|
1381
|
-
|
|
1382
|
-
|
|
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
|
-
|
|
1394
|
-
const suggestion = await
|
|
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 (
|
|
1402
|
-
|
|
1403
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
1424
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1439
|
-
|
|
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
|
-
|
|
1444
|
-
|
|
1486
|
+
p4.log.warn("Could not open browser automatically.");
|
|
1487
|
+
p4.note(`Open this URL manually:
|
|
1445
1488
|
${url}`);
|
|
1446
1489
|
}
|
|
1447
|
-
const pollSpinner =
|
|
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
|
-
|
|
1453
|
-
|
|
1454
|
-
} catch (
|
|
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 (
|
|
1457
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1476
|
-
process.exit(1);
|
|
1518
|
+
error("Not configured. Run `pica init` first.");
|
|
1477
1519
|
}
|
|
1478
1520
|
const api = new PicaApi(apiKey);
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1521
|
+
const spinner5 = createSpinner();
|
|
1522
|
+
spinner5.start("Loading connections...");
|
|
1481
1523
|
try {
|
|
1482
1524
|
const connections = await api.listConnections();
|
|
1483
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1511
|
-
} catch (
|
|
1512
|
-
|
|
1513
|
-
|
|
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
|
|
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
|
-
|
|
1537
|
-
|
|
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
|
|
1541
|
-
|
|
1593
|
+
const spinner5 = createSpinner();
|
|
1594
|
+
spinner5.start("Loading platforms...");
|
|
1542
1595
|
try {
|
|
1543
1596
|
const platforms = await api.listPlatforms();
|
|
1544
|
-
|
|
1597
|
+
spinner5.stop(`${platforms.length} platforms available`);
|
|
1545
1598
|
if (options.json) {
|
|
1546
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1595
|
-
} catch (
|
|
1596
|
-
|
|
1597
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1681
|
+
intro2(pc6.bgCyan(pc6.black(" Pica ")));
|
|
1620
1682
|
const { apiKey, permissions, actionIds, knowledgeAgent } = getConfig();
|
|
1621
1683
|
const api = new PicaApi(apiKey);
|
|
1622
|
-
const
|
|
1623
|
-
|
|
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
|
-
|
|
1637
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
`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
|
-
|
|
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
|
-
|
|
1693
|
-
process.exit(1);
|
|
1757
|
+
error(`Action "${actionId}" is not in the allowed action list.`);
|
|
1694
1758
|
}
|
|
1695
1759
|
if (!connectionKeys.includes("*")) {
|
|
1696
|
-
const
|
|
1697
|
-
|
|
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
|
-
|
|
1703
|
-
|
|
1704
|
-
process.exit(1);
|
|
1766
|
+
spinner6.stop("Platform not connected");
|
|
1767
|
+
error(`Platform "${platform}" has no allowed connections.`);
|
|
1705
1768
|
}
|
|
1706
|
-
|
|
1707
|
-
} catch (
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
`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
|
|
1716
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1795
|
+
p6.note(
|
|
1730
1796
|
`Execute: ${pc6.cyan(`pica actions execute ${platform} ${actionId} <connectionKey>`)}`,
|
|
1731
1797
|
"Next Step"
|
|
1732
1798
|
);
|
|
1733
|
-
} catch (
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
`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
|
-
|
|
1807
|
+
intro2(pc6.bgCyan(pc6.black(" Pica ")));
|
|
1743
1808
|
const { apiKey, permissions, actionIds, connectionKeys, knowledgeAgent } = getConfig();
|
|
1744
1809
|
if (knowledgeAgent) {
|
|
1745
|
-
|
|
1746
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1757
|
-
process.exit(1);
|
|
1818
|
+
error(`Connection key "${connectionKey}" is not allowed.`);
|
|
1758
1819
|
}
|
|
1759
1820
|
const api = new PicaApi(apiKey);
|
|
1760
|
-
const
|
|
1761
|
-
|
|
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
|
-
|
|
1766
|
-
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
`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
|
});
|