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