@picahq/cli 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -47
- package/dist/index.js +35 -432
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,45 +96,6 @@ pica platforms
|
|
|
96
96
|
pica platforms -c "CRM"
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
### Search actions
|
|
100
|
-
|
|
101
|
-
Find available API actions on any connected platform:
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
pica search gmail "send email"
|
|
105
|
-
pica search slack "post message"
|
|
106
|
-
pica search stripe "list payments"
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
POST /gmail/v1/users/{{userId}}/messages/send
|
|
111
|
-
Send Message
|
|
112
|
-
conn_mod_def::ABC123::XYZ789
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Read API docs
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
pica actions knowledge <actionId>
|
|
119
|
-
pica actions k <actionId> --full # no truncation
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Shows method, path, path variables, parameter schemas, and request/response examples.
|
|
123
|
-
|
|
124
|
-
### Execute an action
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
pica exec <actionId> \
|
|
128
|
-
-c live::gmail::default::abc123 \
|
|
129
|
-
-d '{"to": "test@example.com", "subject": "Hello", "body": "Hi there"}' \
|
|
130
|
-
-p userId=me
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
If you omit flags, the CLI prompts interactively:
|
|
134
|
-
- Auto-selects the connection if only one exists for the platform
|
|
135
|
-
- Prompts for each `{{path variable}}` not provided via `-p`
|
|
136
|
-
- Prompts for the request body on POST/PUT/PATCH if `-d` is missing
|
|
137
|
-
|
|
138
99
|
## Commands
|
|
139
100
|
|
|
140
101
|
| Command | Description |
|
|
@@ -143,9 +104,6 @@ If you omit flags, the CLI prompts interactively:
|
|
|
143
104
|
| `pica add <platform>` | Connect a platform via OAuth |
|
|
144
105
|
| `pica list` | List connections with keys |
|
|
145
106
|
| `pica platforms` | Browse available platforms |
|
|
146
|
-
| `pica search <platform> [query]` | Search for actions |
|
|
147
|
-
| `pica actions knowledge <id>` | Get API docs for an action |
|
|
148
|
-
| `pica exec <id>` | Execute an action |
|
|
149
107
|
|
|
150
108
|
Every command supports `--json` for machine-readable output.
|
|
151
109
|
|
|
@@ -155,9 +113,6 @@ Every command supports `--json` for machine-readable output.
|
|
|
155
113
|
|-------|------|
|
|
156
114
|
| `pica ls` | `pica list` |
|
|
157
115
|
| `pica p` | `pica platforms` |
|
|
158
|
-
| `pica a search` | `pica actions search` |
|
|
159
|
-
| `pica a k` | `pica actions knowledge` |
|
|
160
|
-
| `pica a x` | `pica actions execute` |
|
|
161
116
|
|
|
162
117
|
## How it works
|
|
163
118
|
|
|
@@ -193,11 +148,9 @@ src/
|
|
|
193
148
|
init.ts # pica init (setup, status display, targeted actions)
|
|
194
149
|
connection.ts # pica add, pica list
|
|
195
150
|
platforms.ts # pica platforms
|
|
196
|
-
actions.ts # pica search, actions knowledge, exec
|
|
197
151
|
lib/
|
|
198
152
|
api.ts # HTTP client for Pica API
|
|
199
153
|
types.ts # TypeScript interfaces
|
|
200
|
-
actions.ts # Action ID normalization, path variable helpers
|
|
201
154
|
config.ts # ~/.pica/config.json read/write
|
|
202
155
|
agents.ts # Agent detection, MCP config, status reporting
|
|
203
156
|
platforms.ts # Platform search and fuzzy matching
|
package/dist/index.js
CHANGED
|
@@ -46,11 +46,11 @@ function getApiKey() {
|
|
|
46
46
|
import fs2 from "fs";
|
|
47
47
|
import path2 from "path";
|
|
48
48
|
import os2 from "os";
|
|
49
|
-
function expandPath(
|
|
50
|
-
if (
|
|
51
|
-
return path2.join(os2.homedir(),
|
|
49
|
+
function expandPath(p4) {
|
|
50
|
+
if (p4.startsWith("~/")) {
|
|
51
|
+
return path2.join(os2.homedir(), p4.slice(2));
|
|
52
52
|
}
|
|
53
|
-
return
|
|
53
|
+
return p4;
|
|
54
54
|
}
|
|
55
55
|
function getClaudeDesktopConfigPath() {
|
|
56
56
|
switch (process.platform) {
|
|
@@ -186,40 +186,6 @@ function getAgentStatuses() {
|
|
|
186
186
|
});
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
// src/lib/actions.ts
|
|
190
|
-
var ACTION_ID_PREFIX = "conn_mod_def::";
|
|
191
|
-
function normalizeActionId(id) {
|
|
192
|
-
if (id.startsWith(ACTION_ID_PREFIX)) return id;
|
|
193
|
-
return `${ACTION_ID_PREFIX}${id}`;
|
|
194
|
-
}
|
|
195
|
-
function extractPathVariables(path3) {
|
|
196
|
-
const matches = path3.match(/\{\{(\w+)\}\}/g);
|
|
197
|
-
if (!matches) return [];
|
|
198
|
-
return matches.map((m) => m.replace(/\{\{|\}\}/g, ""));
|
|
199
|
-
}
|
|
200
|
-
function replacePathVariables(path3, vars) {
|
|
201
|
-
let result = path3;
|
|
202
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
203
|
-
result = result.replace(`{{${key}}}`, encodeURIComponent(value));
|
|
204
|
-
}
|
|
205
|
-
return result;
|
|
206
|
-
}
|
|
207
|
-
function resolveTemplateVariables(path3, data, pathVars) {
|
|
208
|
-
const variables = extractPathVariables(path3);
|
|
209
|
-
const merged = { ...pathVars };
|
|
210
|
-
const remaining = { ...data };
|
|
211
|
-
for (const v of variables) {
|
|
212
|
-
if (!merged[v] && data[v] != null) {
|
|
213
|
-
merged[v] = String(data[v]);
|
|
214
|
-
delete remaining[v];
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
resolvedPath: replacePathVariables(path3, merged),
|
|
219
|
-
remainingData: remaining
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
189
|
// src/lib/api.ts
|
|
224
190
|
var API_BASE = "https://api.picaos.com/v1";
|
|
225
191
|
var ApiError = class extends Error {
|
|
@@ -256,8 +222,8 @@ var PicaApi = class {
|
|
|
256
222
|
}
|
|
257
223
|
const response = await fetch(url, fetchOpts);
|
|
258
224
|
if (!response.ok) {
|
|
259
|
-
const
|
|
260
|
-
throw new ApiError(response.status,
|
|
225
|
+
const text3 = await response.text();
|
|
226
|
+
throw new ApiError(response.status, text3 || `HTTP ${response.status}`);
|
|
261
227
|
}
|
|
262
228
|
return response.json();
|
|
263
229
|
}
|
|
@@ -288,45 +254,6 @@ var PicaApi = class {
|
|
|
288
254
|
} while (page <= totalPages);
|
|
289
255
|
return allPlatforms;
|
|
290
256
|
}
|
|
291
|
-
async searchActions(platform, query, limit = 10) {
|
|
292
|
-
const queryParams = {
|
|
293
|
-
limit: String(limit),
|
|
294
|
-
executeAgent: "true"
|
|
295
|
-
};
|
|
296
|
-
if (query) queryParams.query = query;
|
|
297
|
-
const response = await this.requestFull({
|
|
298
|
-
path: `/available-actions/search/${encodeURIComponent(platform)}`,
|
|
299
|
-
queryParams
|
|
300
|
-
});
|
|
301
|
-
return response.rows || [];
|
|
302
|
-
}
|
|
303
|
-
async getActionKnowledge(actionId) {
|
|
304
|
-
const normalized = normalizeActionId(actionId);
|
|
305
|
-
const response = await this.requestFull({
|
|
306
|
-
path: "/knowledge",
|
|
307
|
-
queryParams: { _id: normalized }
|
|
308
|
-
});
|
|
309
|
-
return response.rows?.[0] ?? null;
|
|
310
|
-
}
|
|
311
|
-
async executeAction(opts) {
|
|
312
|
-
const headers = {
|
|
313
|
-
"x-pica-connection-key": opts.connectionKey,
|
|
314
|
-
"x-pica-action-id": normalizeActionId(opts.actionId),
|
|
315
|
-
...opts.headers
|
|
316
|
-
};
|
|
317
|
-
if (opts.isFormData) {
|
|
318
|
-
headers["Content-Type"] = "multipart/form-data";
|
|
319
|
-
} else if (opts.isFormUrlEncoded) {
|
|
320
|
-
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
321
|
-
}
|
|
322
|
-
return this.requestFull({
|
|
323
|
-
path: `/passthrough${opts.path}`,
|
|
324
|
-
method: opts.method.toUpperCase(),
|
|
325
|
-
body: opts.data,
|
|
326
|
-
headers,
|
|
327
|
-
queryParams: opts.queryParams
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
257
|
async waitForConnection(platform, timeoutMs = 5 * 60 * 1e3, pollIntervalMs = 5e3, onPoll) {
|
|
331
258
|
const startTime = Date.now();
|
|
332
259
|
const existingConnections = await this.listConnections();
|
|
@@ -525,16 +452,16 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
525
452
|
p.cancel("Cancelled.");
|
|
526
453
|
process.exit(0);
|
|
527
454
|
}
|
|
528
|
-
const
|
|
529
|
-
|
|
455
|
+
const spinner4 = p.spinner();
|
|
456
|
+
spinner4.start("Validating API key...");
|
|
530
457
|
const api = new PicaApi(newKey);
|
|
531
458
|
const isValid = await api.validateApiKey();
|
|
532
459
|
if (!isValid) {
|
|
533
|
-
|
|
460
|
+
spinner4.stop("Invalid API key");
|
|
534
461
|
p.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
|
|
535
462
|
process.exit(1);
|
|
536
463
|
}
|
|
537
|
-
|
|
464
|
+
spinner4.stop("API key validated");
|
|
538
465
|
const reinstalled = [];
|
|
539
466
|
for (const s of statuses) {
|
|
540
467
|
if (s.globalMcp) {
|
|
@@ -666,16 +593,16 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
666
593
|
p.cancel("Setup cancelled.");
|
|
667
594
|
process.exit(0);
|
|
668
595
|
}
|
|
669
|
-
const
|
|
670
|
-
|
|
596
|
+
const spinner4 = p.spinner();
|
|
597
|
+
spinner4.start("Validating API key...");
|
|
671
598
|
const api = new PicaApi(apiKey);
|
|
672
599
|
const isValid = await api.validateApiKey();
|
|
673
600
|
if (!isValid) {
|
|
674
|
-
|
|
601
|
+
spinner4.stop("Invalid API key");
|
|
675
602
|
p.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
|
|
676
603
|
process.exit(1);
|
|
677
604
|
}
|
|
678
|
-
|
|
605
|
+
spinner4.stop("API key validated");
|
|
679
606
|
writeConfig({
|
|
680
607
|
apiKey,
|
|
681
608
|
installedAgents: [],
|
|
@@ -875,16 +802,16 @@ async function promptConnectIntegrations(apiKey) {
|
|
|
875
802
|
p.log.warn("Could not open browser automatically.");
|
|
876
803
|
p.note(url, "Open manually");
|
|
877
804
|
}
|
|
878
|
-
const
|
|
879
|
-
|
|
805
|
+
const spinner4 = p.spinner();
|
|
806
|
+
spinner4.start("Waiting for connection... (complete auth in browser)");
|
|
880
807
|
try {
|
|
881
808
|
await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
|
|
882
|
-
|
|
809
|
+
spinner4.stop(`${label} connected!`);
|
|
883
810
|
p.log.success(`${pc2.green("\u2713")} ${label} is now available to your AI agents`);
|
|
884
811
|
connected.push(platform);
|
|
885
812
|
first = false;
|
|
886
813
|
} catch (error) {
|
|
887
|
-
|
|
814
|
+
spinner4.stop("Connection timed out");
|
|
888
815
|
if (error instanceof TimeoutError) {
|
|
889
816
|
p.log.warn(`No worries. Connect later with: ${pc2.cyan(`pica add ${platform}`)}`);
|
|
890
817
|
}
|
|
@@ -917,16 +844,16 @@ import pc3 from "picocolors";
|
|
|
917
844
|
function findPlatform(platforms, query) {
|
|
918
845
|
const normalizedQuery = query.toLowerCase().trim();
|
|
919
846
|
const exact = platforms.find(
|
|
920
|
-
(
|
|
847
|
+
(p4) => p4.platform.toLowerCase() === normalizedQuery || p4.name.toLowerCase() === normalizedQuery
|
|
921
848
|
);
|
|
922
849
|
if (exact) return exact;
|
|
923
850
|
return null;
|
|
924
851
|
}
|
|
925
852
|
function findSimilarPlatforms(platforms, query, limit = 3) {
|
|
926
853
|
const normalizedQuery = query.toLowerCase().trim();
|
|
927
|
-
const scored = platforms.map((
|
|
928
|
-
const name =
|
|
929
|
-
const slug =
|
|
854
|
+
const scored = platforms.map((p4) => {
|
|
855
|
+
const name = p4.name.toLowerCase();
|
|
856
|
+
const slug = p4.platform.toLowerCase();
|
|
930
857
|
let score = 0;
|
|
931
858
|
if (name.includes(normalizedQuery) || slug.includes(normalizedQuery)) {
|
|
932
859
|
score = 10;
|
|
@@ -935,7 +862,7 @@ function findSimilarPlatforms(platforms, query, limit = 3) {
|
|
|
935
862
|
} else {
|
|
936
863
|
score = countMatchingChars(normalizedQuery, slug);
|
|
937
864
|
}
|
|
938
|
-
return { platform:
|
|
865
|
+
return { platform: p4, score };
|
|
939
866
|
}).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
940
867
|
return scored.map((item) => item.platform);
|
|
941
868
|
}
|
|
@@ -957,14 +884,14 @@ async function connectionAddCommand(platformArg) {
|
|
|
957
884
|
process.exit(1);
|
|
958
885
|
}
|
|
959
886
|
const api = new PicaApi(apiKey);
|
|
960
|
-
const
|
|
961
|
-
|
|
887
|
+
const spinner4 = p2.spinner();
|
|
888
|
+
spinner4.start("Loading platforms...");
|
|
962
889
|
let platforms;
|
|
963
890
|
try {
|
|
964
891
|
platforms = await api.listPlatforms();
|
|
965
|
-
|
|
892
|
+
spinner4.stop(`${platforms.length} platforms available`);
|
|
966
893
|
} catch (error) {
|
|
967
|
-
|
|
894
|
+
spinner4.stop("Failed to load platforms");
|
|
968
895
|
p2.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
969
896
|
process.exit(1);
|
|
970
897
|
}
|
|
@@ -1062,11 +989,11 @@ async function connectionListCommand() {
|
|
|
1062
989
|
process.exit(1);
|
|
1063
990
|
}
|
|
1064
991
|
const api = new PicaApi(apiKey);
|
|
1065
|
-
const
|
|
1066
|
-
|
|
992
|
+
const spinner4 = p2.spinner();
|
|
993
|
+
spinner4.start("Loading connections...");
|
|
1067
994
|
try {
|
|
1068
995
|
const connections = await api.listConnections();
|
|
1069
|
-
|
|
996
|
+
spinner4.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
|
|
1070
997
|
if (connections.length === 0) {
|
|
1071
998
|
p2.note(
|
|
1072
999
|
`No connections yet.
|
|
@@ -1095,7 +1022,7 @@ Add one with: ${pc3.cyan("pica connection add gmail")}`,
|
|
|
1095
1022
|
console.log();
|
|
1096
1023
|
p2.note(`Add more with: ${pc3.cyan("pica connection add <platform>")}`, "Tip");
|
|
1097
1024
|
} catch (error) {
|
|
1098
|
-
|
|
1025
|
+
spinner4.stop("Failed to load connections");
|
|
1099
1026
|
p2.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1100
1027
|
process.exit(1);
|
|
1101
1028
|
}
|
|
@@ -1123,11 +1050,11 @@ async function platformsCommand(options) {
|
|
|
1123
1050
|
process.exit(1);
|
|
1124
1051
|
}
|
|
1125
1052
|
const api = new PicaApi(apiKey);
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1053
|
+
const spinner4 = p3.spinner();
|
|
1054
|
+
spinner4.start("Loading platforms...");
|
|
1128
1055
|
try {
|
|
1129
1056
|
const platforms = await api.listPlatforms();
|
|
1130
|
-
|
|
1057
|
+
spinner4.stop(`${platforms.length} platforms available`);
|
|
1131
1058
|
if (options.json) {
|
|
1132
1059
|
console.log(JSON.stringify(platforms, null, 2));
|
|
1133
1060
|
return;
|
|
@@ -1179,317 +1106,12 @@ async function platformsCommand(options) {
|
|
|
1179
1106
|
console.log();
|
|
1180
1107
|
p3.note(`Connect with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
|
|
1181
1108
|
} catch (error) {
|
|
1182
|
-
|
|
1109
|
+
spinner4.stop("Failed to load platforms");
|
|
1183
1110
|
p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1184
1111
|
process.exit(1);
|
|
1185
1112
|
}
|
|
1186
1113
|
}
|
|
1187
1114
|
|
|
1188
|
-
// src/commands/actions.ts
|
|
1189
|
-
import * as p4 from "@clack/prompts";
|
|
1190
|
-
import pc5 from "picocolors";
|
|
1191
|
-
function getApi() {
|
|
1192
|
-
const apiKey = getApiKey();
|
|
1193
|
-
if (!apiKey) {
|
|
1194
|
-
p4.cancel("Not configured. Run `pica init` first.");
|
|
1195
|
-
process.exit(1);
|
|
1196
|
-
}
|
|
1197
|
-
return new PicaApi(apiKey);
|
|
1198
|
-
}
|
|
1199
|
-
function colorMethod(method) {
|
|
1200
|
-
const m = method.toUpperCase();
|
|
1201
|
-
switch (m) {
|
|
1202
|
-
case "GET":
|
|
1203
|
-
return pc5.green(m);
|
|
1204
|
-
case "POST":
|
|
1205
|
-
return pc5.yellow(m);
|
|
1206
|
-
case "PUT":
|
|
1207
|
-
return pc5.blue(m);
|
|
1208
|
-
case "PATCH":
|
|
1209
|
-
return pc5.cyan(m);
|
|
1210
|
-
case "DELETE":
|
|
1211
|
-
return pc5.red(m);
|
|
1212
|
-
default:
|
|
1213
|
-
return pc5.dim(m);
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
function padMethod(method) {
|
|
1217
|
-
return method.toUpperCase().padEnd(7);
|
|
1218
|
-
}
|
|
1219
|
-
async function actionsSearchCommand(platform, query, options = {}) {
|
|
1220
|
-
const api = getApi();
|
|
1221
|
-
if (!query) {
|
|
1222
|
-
const input = await p4.text({
|
|
1223
|
-
message: `Search actions on ${pc5.cyan(platform)}:`,
|
|
1224
|
-
placeholder: "send email, create contact, list orders..."
|
|
1225
|
-
});
|
|
1226
|
-
if (p4.isCancel(input)) {
|
|
1227
|
-
p4.cancel("Cancelled.");
|
|
1228
|
-
process.exit(0);
|
|
1229
|
-
}
|
|
1230
|
-
query = input;
|
|
1231
|
-
}
|
|
1232
|
-
const spinner5 = p4.spinner();
|
|
1233
|
-
spinner5.start(`Searching ${platform} actions...`);
|
|
1234
|
-
try {
|
|
1235
|
-
const limit = options.limit ? parseInt(options.limit, 10) : 10;
|
|
1236
|
-
const actions2 = await api.searchActions(platform, query, limit);
|
|
1237
|
-
spinner5.stop(`${actions2.length} action${actions2.length === 1 ? "" : "s"} found`);
|
|
1238
|
-
if (options.json) {
|
|
1239
|
-
console.log(JSON.stringify(actions2, null, 2));
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
if (actions2.length === 0) {
|
|
1243
|
-
p4.note(`No actions found for "${query}" on ${platform}.`, "No Results");
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
console.log();
|
|
1247
|
-
const rows = actions2.map((action) => ({
|
|
1248
|
-
method: colorMethod(padMethod(action.method)),
|
|
1249
|
-
path: action.path,
|
|
1250
|
-
title: action.title,
|
|
1251
|
-
id: action._id
|
|
1252
|
-
}));
|
|
1253
|
-
printTable(
|
|
1254
|
-
[
|
|
1255
|
-
{ key: "method", label: "Method" },
|
|
1256
|
-
{ key: "title", label: "Title" },
|
|
1257
|
-
{ key: "path", label: "Path", color: pc5.dim },
|
|
1258
|
-
{ key: "id", label: "Action ID", color: pc5.dim }
|
|
1259
|
-
],
|
|
1260
|
-
rows
|
|
1261
|
-
);
|
|
1262
|
-
console.log();
|
|
1263
|
-
p4.note(
|
|
1264
|
-
`Get docs: ${pc5.cyan("pica actions knowledge <actionId>")}
|
|
1265
|
-
Execute: ${pc5.cyan("pica actions execute <actionId>")}`,
|
|
1266
|
-
"Next Steps"
|
|
1267
|
-
);
|
|
1268
|
-
} catch (error) {
|
|
1269
|
-
spinner5.stop("Search failed");
|
|
1270
|
-
p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1271
|
-
process.exit(1);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
async function actionsKnowledgeCommand(actionId, options = {}) {
|
|
1275
|
-
const api = getApi();
|
|
1276
|
-
const spinner5 = p4.spinner();
|
|
1277
|
-
spinner5.start("Loading action knowledge...");
|
|
1278
|
-
try {
|
|
1279
|
-
const knowledge = await api.getActionKnowledge(actionId);
|
|
1280
|
-
spinner5.stop("Action knowledge loaded");
|
|
1281
|
-
if (!knowledge) {
|
|
1282
|
-
p4.cancel(`No knowledge found for action: ${actionId}`);
|
|
1283
|
-
process.exit(1);
|
|
1284
|
-
}
|
|
1285
|
-
if (options.json) {
|
|
1286
|
-
console.log(JSON.stringify(knowledge, null, 2));
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
printKnowledge(knowledge, options.full);
|
|
1290
|
-
} catch (error) {
|
|
1291
|
-
spinner5.stop("Failed to load knowledge");
|
|
1292
|
-
p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1293
|
-
process.exit(1);
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
function printKnowledge(k, full) {
|
|
1297
|
-
console.log();
|
|
1298
|
-
console.log(pc5.bold(` ${k.title}`));
|
|
1299
|
-
console.log();
|
|
1300
|
-
console.log(` Platform: ${pc5.cyan(k.connectionPlatform)}`);
|
|
1301
|
-
console.log(` Method: ${colorMethod(k.method)}`);
|
|
1302
|
-
console.log(` Path: ${k.path}`);
|
|
1303
|
-
console.log(` Base URL: ${pc5.dim(k.baseUrl)}`);
|
|
1304
|
-
if (k.tags?.length) {
|
|
1305
|
-
console.log(` Tags: ${k.tags.map((t) => pc5.dim(t)).join(", ")}`);
|
|
1306
|
-
}
|
|
1307
|
-
const pathVars = extractPathVariables(k.path);
|
|
1308
|
-
if (pathVars.length > 0) {
|
|
1309
|
-
console.log(` Path Vars: ${pathVars.map((v) => pc5.yellow(`{{${v}}}`)).join(", ")}`);
|
|
1310
|
-
}
|
|
1311
|
-
console.log(` Active: ${k.active ? pc5.green("yes") : pc5.red("no")}`);
|
|
1312
|
-
console.log(` ID: ${pc5.dim(k._id)}`);
|
|
1313
|
-
if (k.knowledge) {
|
|
1314
|
-
console.log();
|
|
1315
|
-
console.log(pc5.bold(" API Documentation"));
|
|
1316
|
-
console.log(pc5.dim(" " + "\u2500".repeat(40)));
|
|
1317
|
-
console.log();
|
|
1318
|
-
const lines = k.knowledge.split("\n");
|
|
1319
|
-
const displayLines = full ? lines : lines.slice(0, 50);
|
|
1320
|
-
for (const line of displayLines) {
|
|
1321
|
-
console.log(` ${line}`);
|
|
1322
|
-
}
|
|
1323
|
-
if (!full && lines.length > 50) {
|
|
1324
|
-
console.log();
|
|
1325
|
-
console.log(pc5.dim(` ... ${lines.length - 50} more lines. Use --full to see all.`));
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
console.log();
|
|
1329
|
-
}
|
|
1330
|
-
async function actionsExecuteCommand(actionId, options = {}) {
|
|
1331
|
-
const api = getApi();
|
|
1332
|
-
const spinner5 = p4.spinner();
|
|
1333
|
-
spinner5.start("Loading action details...");
|
|
1334
|
-
let knowledge;
|
|
1335
|
-
try {
|
|
1336
|
-
const k = await api.getActionKnowledge(actionId);
|
|
1337
|
-
if (!k) {
|
|
1338
|
-
spinner5.stop("Action not found");
|
|
1339
|
-
p4.cancel(`No action found for: ${actionId}`);
|
|
1340
|
-
process.exit(1);
|
|
1341
|
-
}
|
|
1342
|
-
knowledge = k;
|
|
1343
|
-
spinner5.stop(`${colorMethod(knowledge.method)} ${knowledge.path}`);
|
|
1344
|
-
} catch (error) {
|
|
1345
|
-
spinner5.stop("Failed to load action");
|
|
1346
|
-
p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1347
|
-
process.exit(1);
|
|
1348
|
-
}
|
|
1349
|
-
let connectionKey = options.connection;
|
|
1350
|
-
if (!connectionKey) {
|
|
1351
|
-
connectionKey = await resolveConnection(api, knowledge.connectionPlatform);
|
|
1352
|
-
}
|
|
1353
|
-
const pathVarMap = parseKeyValuePairs(options.pathVar || []);
|
|
1354
|
-
const pathVars = extractPathVariables(knowledge.path);
|
|
1355
|
-
for (const v of pathVars) {
|
|
1356
|
-
if (!pathVarMap[v]) {
|
|
1357
|
-
const input = await p4.text({
|
|
1358
|
-
message: `Value for path variable ${pc5.yellow(`{{${v}}}`)}:`,
|
|
1359
|
-
validate: (val) => val.trim() ? void 0 : "Value is required"
|
|
1360
|
-
});
|
|
1361
|
-
if (p4.isCancel(input)) {
|
|
1362
|
-
p4.cancel("Cancelled.");
|
|
1363
|
-
process.exit(0);
|
|
1364
|
-
}
|
|
1365
|
-
pathVarMap[v] = input;
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
let bodyData = {};
|
|
1369
|
-
if (options.data) {
|
|
1370
|
-
try {
|
|
1371
|
-
bodyData = JSON.parse(options.data);
|
|
1372
|
-
} catch {
|
|
1373
|
-
p4.cancel("Invalid JSON in --data flag.");
|
|
1374
|
-
process.exit(1);
|
|
1375
|
-
}
|
|
1376
|
-
} else if (!["GET", "DELETE", "HEAD"].includes(knowledge.method.toUpperCase())) {
|
|
1377
|
-
const input = await p4.text({
|
|
1378
|
-
message: "Request body (JSON):",
|
|
1379
|
-
placeholder: '{"key": "value"} or leave empty'
|
|
1380
|
-
});
|
|
1381
|
-
if (p4.isCancel(input)) {
|
|
1382
|
-
p4.cancel("Cancelled.");
|
|
1383
|
-
process.exit(0);
|
|
1384
|
-
}
|
|
1385
|
-
if (input.trim()) {
|
|
1386
|
-
try {
|
|
1387
|
-
bodyData = JSON.parse(input);
|
|
1388
|
-
} catch {
|
|
1389
|
-
p4.cancel("Invalid JSON.");
|
|
1390
|
-
process.exit(1);
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
const queryParams = parseKeyValuePairs(options.query || []);
|
|
1395
|
-
const { resolvedPath, remainingData } = resolveTemplateVariables(
|
|
1396
|
-
knowledge.path,
|
|
1397
|
-
bodyData,
|
|
1398
|
-
pathVarMap
|
|
1399
|
-
);
|
|
1400
|
-
console.log();
|
|
1401
|
-
console.log(pc5.bold(" Request Summary"));
|
|
1402
|
-
console.log(` ${colorMethod(knowledge.method)} ${knowledge.baseUrl}${resolvedPath}`);
|
|
1403
|
-
console.log(` Connection: ${pc5.dim(connectionKey)}`);
|
|
1404
|
-
if (Object.keys(remainingData).length > 0) {
|
|
1405
|
-
console.log(` Body: ${pc5.dim(JSON.stringify(remainingData))}`);
|
|
1406
|
-
}
|
|
1407
|
-
if (Object.keys(queryParams).length > 0) {
|
|
1408
|
-
console.log(` Query: ${pc5.dim(JSON.stringify(queryParams))}`);
|
|
1409
|
-
}
|
|
1410
|
-
console.log();
|
|
1411
|
-
const execSpinner = p4.spinner();
|
|
1412
|
-
execSpinner.start("Executing...");
|
|
1413
|
-
try {
|
|
1414
|
-
const result = await api.executeAction({
|
|
1415
|
-
method: knowledge.method,
|
|
1416
|
-
path: resolvedPath,
|
|
1417
|
-
actionId,
|
|
1418
|
-
connectionKey,
|
|
1419
|
-
data: Object.keys(remainingData).length > 0 ? remainingData : void 0,
|
|
1420
|
-
queryParams: Object.keys(queryParams).length > 0 ? queryParams : void 0,
|
|
1421
|
-
isFormData: options.formData,
|
|
1422
|
-
isFormUrlEncoded: options.formUrlencoded
|
|
1423
|
-
});
|
|
1424
|
-
execSpinner.stop(pc5.green("Success"));
|
|
1425
|
-
if (options.json) {
|
|
1426
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1427
|
-
} else {
|
|
1428
|
-
console.log();
|
|
1429
|
-
console.log(pc5.bold(" Response"));
|
|
1430
|
-
console.log(pc5.dim(" " + "\u2500".repeat(40)));
|
|
1431
|
-
console.log();
|
|
1432
|
-
console.log(formatResponse(result));
|
|
1433
|
-
console.log();
|
|
1434
|
-
}
|
|
1435
|
-
} catch (error) {
|
|
1436
|
-
execSpinner.stop(pc5.red("Failed"));
|
|
1437
|
-
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
1438
|
-
p4.cancel(`Execution failed: ${msg}`);
|
|
1439
|
-
process.exit(1);
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
async function resolveConnection(api, platform) {
|
|
1443
|
-
const spinner5 = p4.spinner();
|
|
1444
|
-
spinner5.start("Loading connections...");
|
|
1445
|
-
const connections = await api.listConnections();
|
|
1446
|
-
const matching = connections.filter(
|
|
1447
|
-
(c) => c.platform.toLowerCase() === platform.toLowerCase()
|
|
1448
|
-
);
|
|
1449
|
-
spinner5.stop(`${matching.length} ${platform} connection${matching.length === 1 ? "" : "s"} found`);
|
|
1450
|
-
if (matching.length === 0) {
|
|
1451
|
-
p4.cancel(
|
|
1452
|
-
`No ${platform} connections found.
|
|
1453
|
-
|
|
1454
|
-
Add one with: ${pc5.cyan(`pica connection add ${platform}`)}`
|
|
1455
|
-
);
|
|
1456
|
-
process.exit(1);
|
|
1457
|
-
}
|
|
1458
|
-
if (matching.length === 1) {
|
|
1459
|
-
p4.log.info(`Using connection: ${pc5.dim(matching[0].key)}`);
|
|
1460
|
-
return matching[0].key;
|
|
1461
|
-
}
|
|
1462
|
-
const selected = await p4.select({
|
|
1463
|
-
message: `Multiple ${platform} connections found. Which one?`,
|
|
1464
|
-
options: matching.map((c) => ({
|
|
1465
|
-
value: c.key,
|
|
1466
|
-
label: `${c.key}`,
|
|
1467
|
-
hint: c.state
|
|
1468
|
-
}))
|
|
1469
|
-
});
|
|
1470
|
-
if (p4.isCancel(selected)) {
|
|
1471
|
-
p4.cancel("Cancelled.");
|
|
1472
|
-
process.exit(0);
|
|
1473
|
-
}
|
|
1474
|
-
return selected;
|
|
1475
|
-
}
|
|
1476
|
-
function parseKeyValuePairs(pairs) {
|
|
1477
|
-
const result = {};
|
|
1478
|
-
for (const pair of pairs) {
|
|
1479
|
-
const eqIdx = pair.indexOf("=");
|
|
1480
|
-
if (eqIdx === -1) continue;
|
|
1481
|
-
const key = pair.slice(0, eqIdx);
|
|
1482
|
-
const value = pair.slice(eqIdx + 1);
|
|
1483
|
-
result[key] = value;
|
|
1484
|
-
}
|
|
1485
|
-
return result;
|
|
1486
|
-
}
|
|
1487
|
-
function formatResponse(data, indent = 2) {
|
|
1488
|
-
const prefix = " ".repeat(indent);
|
|
1489
|
-
const json = JSON.stringify(data, null, 2);
|
|
1490
|
-
return json.split("\n").map((line) => `${prefix}${line}`).join("\n");
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
1115
|
// src/index.ts
|
|
1494
1116
|
var require2 = createRequire(import.meta.url);
|
|
1495
1117
|
var { version } = require2("../package.json");
|
|
@@ -1514,23 +1136,4 @@ program.command("add [platform]").description("Shortcut for: connection add").ac
|
|
|
1514
1136
|
program.command("list").alias("ls").description("Shortcut for: connection list").action(async () => {
|
|
1515
1137
|
await connectionListCommand();
|
|
1516
1138
|
});
|
|
1517
|
-
var actions = program.command("actions").alias("a").description("Discover and execute platform actions");
|
|
1518
|
-
actions.command("search <platform> [query]").description("Search actions on a platform").option("--json", "Output as JSON").option("-l, --limit <limit>", "Max results", "10").action(async (platform, query, options) => {
|
|
1519
|
-
await actionsSearchCommand(platform, query, options);
|
|
1520
|
-
});
|
|
1521
|
-
actions.command("knowledge <actionId>").alias("k").description("Get API docs for an action").option("--json", "Output as JSON").option("--full", "Show full knowledge (no truncation)").action(async (actionId, options) => {
|
|
1522
|
-
await actionsKnowledgeCommand(actionId, options);
|
|
1523
|
-
});
|
|
1524
|
-
actions.command("execute <actionId>").alias("x").description("Execute an action").option("-c, --connection <key>", "Connection key to use").option("-d, --data <json>", "Request body as JSON").option("-p, --path-var <key=value...>", "Path variable", collectValues).option("-q, --query <key=value...>", "Query parameter", collectValues).option("--form-data", "Send as multipart/form-data").option("--form-urlencoded", "Send as application/x-www-form-urlencoded").option("--json", "Output as JSON").action(async (actionId, options) => {
|
|
1525
|
-
await actionsExecuteCommand(actionId, options);
|
|
1526
|
-
});
|
|
1527
|
-
program.command("search <platform> [query]").description("Shortcut for: actions search").option("--json", "Output as JSON").option("-l, --limit <limit>", "Max results", "10").action(async (platform, query, options) => {
|
|
1528
|
-
await actionsSearchCommand(platform, query, options);
|
|
1529
|
-
});
|
|
1530
|
-
program.command("exec <actionId>").description("Shortcut for: actions execute").option("-c, --connection <key>", "Connection key to use").option("-d, --data <json>", "Request body as JSON").option("-p, --path-var <key=value...>", "Path variable", collectValues).option("-q, --query <key=value...>", "Query parameter", collectValues).option("--form-data", "Send as multipart/form-data").option("--form-urlencoded", "Send as application/x-www-form-urlencoded").option("--json", "Output as JSON").action(async (actionId, options) => {
|
|
1531
|
-
await actionsExecuteCommand(actionId, options);
|
|
1532
|
-
});
|
|
1533
|
-
function collectValues(value, previous) {
|
|
1534
|
-
return (previous || []).concat([value]);
|
|
1535
|
-
}
|
|
1536
1139
|
program.parse();
|