@picahq/cli 1.3.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.
Files changed (3) hide show
  1. package/README.md +0 -47
  2. package/dist/index.js +134 -439
  3. 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(p5) {
50
- if (p5.startsWith("~/")) {
51
- return path2.join(os2.homedir(), p5.slice(2));
49
+ function expandPath(p4) {
50
+ if (p4.startsWith("~/")) {
51
+ return path2.join(os2.homedir(), p4.slice(2));
52
52
  }
53
- return p5;
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 text4 = await response.text();
260
- throw new ApiError(response.status, text4 || `HTTP ${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();
@@ -372,6 +299,9 @@ async function openApiKeyPage() {
372
299
  await open(getApiKeyUrl());
373
300
  }
374
301
 
302
+ // src/commands/init.ts
303
+ import open2 from "open";
304
+
375
305
  // src/lib/table.ts
376
306
  import pc from "picocolors";
377
307
  function printTable(columns, rows) {
@@ -410,12 +340,13 @@ function stripAnsi(str) {
410
340
 
411
341
  // src/commands/init.ts
412
342
  async function initCommand(options) {
413
- p.intro(pc2.bgCyan(pc2.black(" Pica ")));
414
343
  const existingConfig = readConfig();
415
344
  if (existingConfig) {
345
+ p.intro(pc2.bgCyan(pc2.black(" Pica ")));
416
346
  await handleExistingConfig(existingConfig.apiKey, options);
417
347
  return;
418
348
  }
349
+ printBanner();
419
350
  await freshSetup(options);
420
351
  }
421
352
  async function handleExistingConfig(apiKey, options) {
@@ -521,16 +452,16 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
521
452
  p.cancel("Cancelled.");
522
453
  process.exit(0);
523
454
  }
524
- const spinner5 = p.spinner();
525
- spinner5.start("Validating API key...");
455
+ const spinner4 = p.spinner();
456
+ spinner4.start("Validating API key...");
526
457
  const api = new PicaApi(newKey);
527
458
  const isValid = await api.validateApiKey();
528
459
  if (!isValid) {
529
- spinner5.stop("Invalid API key");
460
+ spinner4.stop("Invalid API key");
530
461
  p.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
531
462
  process.exit(1);
532
463
  }
533
- spinner5.stop("API key validated");
464
+ spinner4.stop("API key validated");
534
465
  const reinstalled = [];
535
466
  for (const s of statuses) {
536
467
  if (s.globalMcp) {
@@ -662,16 +593,16 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
662
593
  p.cancel("Setup cancelled.");
663
594
  process.exit(0);
664
595
  }
665
- const spinner5 = p.spinner();
666
- spinner5.start("Validating API key...");
596
+ const spinner4 = p.spinner();
597
+ spinner4.start("Validating API key...");
667
598
  const api = new PicaApi(apiKey);
668
599
  const isValid = await api.validateApiKey();
669
600
  if (!isValid) {
670
- spinner5.stop("Invalid API key");
601
+ spinner4.stop("Invalid API key");
671
602
  p.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
672
603
  process.exit(1);
673
604
  }
674
- spinner5.stop("API key validated");
605
+ spinner4.stop("API key validated");
675
606
  writeConfig({
676
607
  apiKey,
677
608
  installedAgents: [],
@@ -773,11 +704,10 @@ ${globalPaths}
773
704
 
774
705
  `;
775
706
  }
776
- summary += pc2.yellow("Note: Project config files can be committed to share with your team.\n") + pc2.yellow("Team members will need their own API key.\n\n") + `Next steps:
777
- ${pc2.cyan("pica add gmail")} - Connect Gmail
778
- ${pc2.cyan("pica platforms")} - See all 200+ integrations`;
707
+ summary += pc2.yellow("Note: Project config files can be committed to share with your team.\n") + pc2.yellow("Team members will need their own API key.");
779
708
  p.note(summary, "Setup Complete");
780
- p.outro("Pica MCP installed!");
709
+ await promptConnectIntegrations(apiKey);
710
+ p.outro("Your AI agents now have access to Pica integrations!");
781
711
  return;
782
712
  }
783
713
  const installedAgentIds = [];
@@ -794,16 +724,105 @@ ${globalPaths}
794
724
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
795
725
  });
796
726
  p.note(
797
- `Config saved to: ${pc2.dim(getConfigPath())}
798
-
799
- Next steps:
800
- ${pc2.cyan("pica add gmail")} - Connect Gmail
801
- ${pc2.cyan("pica platforms")} - See all 200+ integrations
802
- ${pc2.cyan("pica connection list")} - View your connections`,
727
+ `Config saved to: ${pc2.dim(getConfigPath())}`,
803
728
  "Setup Complete"
804
729
  );
730
+ await promptConnectIntegrations(apiKey);
805
731
  p.outro("Your AI agents now have access to Pica integrations!");
806
732
  }
733
+ function printBanner() {
734
+ console.log();
735
+ console.log(pc2.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
736
+ console.log(pc2.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
737
+ console.log(pc2.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
738
+ console.log(pc2.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
739
+ console.log(pc2.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
740
+ console.log(pc2.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
741
+ console.log(pc2.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
742
+ console.log(pc2.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
743
+ console.log(pc2.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
744
+ console.log(pc2.cyan(" \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588"));
745
+ console.log();
746
+ console.log(pc2.dim(" U N I V E R S A L I N T E G R A T I O N S F O R A I"));
747
+ console.log();
748
+ }
749
+ var TOP_INTEGRATIONS = [
750
+ { value: "gmail", label: "Gmail", hint: "Read and send emails" },
751
+ { value: "google-calendar", label: "Google Calendar", hint: "Manage events and schedules" },
752
+ { value: "notion", label: "Notion", hint: "Access pages, databases, and docs" }
753
+ ];
754
+ async function promptConnectIntegrations(apiKey) {
755
+ const api = new PicaApi(apiKey);
756
+ const connected = [];
757
+ try {
758
+ const existing = await api.listConnections();
759
+ for (const conn of existing) {
760
+ const match = TOP_INTEGRATIONS.find(
761
+ (i) => i.value === conn.platform.toLowerCase()
762
+ );
763
+ if (match) connected.push(match.value);
764
+ }
765
+ } catch {
766
+ }
767
+ let first = true;
768
+ while (true) {
769
+ const available = TOP_INTEGRATIONS.filter((i) => !connected.includes(i.value));
770
+ const options = [
771
+ ...available.map((i) => ({
772
+ value: i.value,
773
+ label: i.label,
774
+ hint: i.hint
775
+ })),
776
+ { value: "more", label: "Browse all 200+ platforms" },
777
+ { value: "skip", label: "Skip for now", hint: "you can always run pica add later" }
778
+ ];
779
+ const message = first ? "Connect your first integration?" : "Connect another?";
780
+ const choice = await p.select({ message, options });
781
+ if (p.isCancel(choice) || choice === "skip") {
782
+ break;
783
+ }
784
+ if (choice === "more") {
785
+ try {
786
+ await open2("https://app.picaos.com/connections");
787
+ p.log.info("Opened Pica dashboard in browser.");
788
+ } catch {
789
+ p.note("https://app.picaos.com/connections", "Open in browser");
790
+ }
791
+ p.log.info(`Connect from the dashboard, or use ${pc2.cyan("pica add <platform>")}`);
792
+ break;
793
+ }
794
+ const platform = choice;
795
+ const integration = TOP_INTEGRATIONS.find((i) => i.value === platform);
796
+ const label = integration?.label ?? platform;
797
+ p.log.info(`Opening browser to connect ${pc2.cyan(label)}...`);
798
+ try {
799
+ await openConnectionPage(platform);
800
+ } catch {
801
+ const url = getConnectionUrl(platform);
802
+ p.log.warn("Could not open browser automatically.");
803
+ p.note(url, "Open manually");
804
+ }
805
+ const spinner4 = p.spinner();
806
+ spinner4.start("Waiting for connection... (complete auth in browser)");
807
+ try {
808
+ await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
809
+ spinner4.stop(`${label} connected!`);
810
+ p.log.success(`${pc2.green("\u2713")} ${label} is now available to your AI agents`);
811
+ connected.push(platform);
812
+ first = false;
813
+ } catch (error) {
814
+ spinner4.stop("Connection timed out");
815
+ if (error instanceof TimeoutError) {
816
+ p.log.warn(`No worries. Connect later with: ${pc2.cyan(`pica add ${platform}`)}`);
817
+ }
818
+ first = false;
819
+ }
820
+ if (TOP_INTEGRATIONS.every((i) => connected.includes(i.value))) {
821
+ p.log.success("All top integrations connected!");
822
+ break;
823
+ }
824
+ }
825
+ }
807
826
  function maskApiKey(key) {
808
827
  if (key.length <= 12) return key.slice(0, 8) + "...";
809
828
  return key.slice(0, 8) + "..." + key.slice(-4);
@@ -825,16 +844,16 @@ import pc3 from "picocolors";
825
844
  function findPlatform(platforms, query) {
826
845
  const normalizedQuery = query.toLowerCase().trim();
827
846
  const exact = platforms.find(
828
- (p5) => p5.platform.toLowerCase() === normalizedQuery || p5.name.toLowerCase() === normalizedQuery
847
+ (p4) => p4.platform.toLowerCase() === normalizedQuery || p4.name.toLowerCase() === normalizedQuery
829
848
  );
830
849
  if (exact) return exact;
831
850
  return null;
832
851
  }
833
852
  function findSimilarPlatforms(platforms, query, limit = 3) {
834
853
  const normalizedQuery = query.toLowerCase().trim();
835
- const scored = platforms.map((p5) => {
836
- const name = p5.name.toLowerCase();
837
- const slug = p5.platform.toLowerCase();
854
+ const scored = platforms.map((p4) => {
855
+ const name = p4.name.toLowerCase();
856
+ const slug = p4.platform.toLowerCase();
838
857
  let score = 0;
839
858
  if (name.includes(normalizedQuery) || slug.includes(normalizedQuery)) {
840
859
  score = 10;
@@ -843,7 +862,7 @@ function findSimilarPlatforms(platforms, query, limit = 3) {
843
862
  } else {
844
863
  score = countMatchingChars(normalizedQuery, slug);
845
864
  }
846
- return { platform: p5, score };
865
+ return { platform: p4, score };
847
866
  }).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
848
867
  return scored.map((item) => item.platform);
849
868
  }
@@ -865,14 +884,14 @@ async function connectionAddCommand(platformArg) {
865
884
  process.exit(1);
866
885
  }
867
886
  const api = new PicaApi(apiKey);
868
- const spinner5 = p2.spinner();
869
- spinner5.start("Loading platforms...");
887
+ const spinner4 = p2.spinner();
888
+ spinner4.start("Loading platforms...");
870
889
  let platforms;
871
890
  try {
872
891
  platforms = await api.listPlatforms();
873
- spinner5.stop(`${platforms.length} platforms available`);
892
+ spinner4.stop(`${platforms.length} platforms available`);
874
893
  } catch (error) {
875
- spinner5.stop("Failed to load platforms");
894
+ spinner4.stop("Failed to load platforms");
876
895
  p2.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
877
896
  process.exit(1);
878
897
  }
@@ -970,11 +989,11 @@ async function connectionListCommand() {
970
989
  process.exit(1);
971
990
  }
972
991
  const api = new PicaApi(apiKey);
973
- const spinner5 = p2.spinner();
974
- spinner5.start("Loading connections...");
992
+ const spinner4 = p2.spinner();
993
+ spinner4.start("Loading connections...");
975
994
  try {
976
995
  const connections = await api.listConnections();
977
- spinner5.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
996
+ spinner4.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
978
997
  if (connections.length === 0) {
979
998
  p2.note(
980
999
  `No connections yet.
@@ -1003,7 +1022,7 @@ Add one with: ${pc3.cyan("pica connection add gmail")}`,
1003
1022
  console.log();
1004
1023
  p2.note(`Add more with: ${pc3.cyan("pica connection add <platform>")}`, "Tip");
1005
1024
  } catch (error) {
1006
- spinner5.stop("Failed to load connections");
1025
+ spinner4.stop("Failed to load connections");
1007
1026
  p2.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1008
1027
  process.exit(1);
1009
1028
  }
@@ -1031,11 +1050,11 @@ async function platformsCommand(options) {
1031
1050
  process.exit(1);
1032
1051
  }
1033
1052
  const api = new PicaApi(apiKey);
1034
- const spinner5 = p3.spinner();
1035
- spinner5.start("Loading platforms...");
1053
+ const spinner4 = p3.spinner();
1054
+ spinner4.start("Loading platforms...");
1036
1055
  try {
1037
1056
  const platforms = await api.listPlatforms();
1038
- spinner5.stop(`${platforms.length} platforms available`);
1057
+ spinner4.stop(`${platforms.length} platforms available`);
1039
1058
  if (options.json) {
1040
1059
  console.log(JSON.stringify(platforms, null, 2));
1041
1060
  return;
@@ -1087,317 +1106,12 @@ async function platformsCommand(options) {
1087
1106
  console.log();
1088
1107
  p3.note(`Connect with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
1089
1108
  } catch (error) {
1090
- spinner5.stop("Failed to load platforms");
1109
+ spinner4.stop("Failed to load platforms");
1091
1110
  p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1092
1111
  process.exit(1);
1093
1112
  }
1094
1113
  }
1095
1114
 
1096
- // src/commands/actions.ts
1097
- import * as p4 from "@clack/prompts";
1098
- import pc5 from "picocolors";
1099
- function getApi() {
1100
- const apiKey = getApiKey();
1101
- if (!apiKey) {
1102
- p4.cancel("Not configured. Run `pica init` first.");
1103
- process.exit(1);
1104
- }
1105
- return new PicaApi(apiKey);
1106
- }
1107
- function colorMethod(method) {
1108
- const m = method.toUpperCase();
1109
- switch (m) {
1110
- case "GET":
1111
- return pc5.green(m);
1112
- case "POST":
1113
- return pc5.yellow(m);
1114
- case "PUT":
1115
- return pc5.blue(m);
1116
- case "PATCH":
1117
- return pc5.cyan(m);
1118
- case "DELETE":
1119
- return pc5.red(m);
1120
- default:
1121
- return pc5.dim(m);
1122
- }
1123
- }
1124
- function padMethod(method) {
1125
- return method.toUpperCase().padEnd(7);
1126
- }
1127
- async function actionsSearchCommand(platform, query, options = {}) {
1128
- const api = getApi();
1129
- if (!query) {
1130
- const input = await p4.text({
1131
- message: `Search actions on ${pc5.cyan(platform)}:`,
1132
- placeholder: "send email, create contact, list orders..."
1133
- });
1134
- if (p4.isCancel(input)) {
1135
- p4.cancel("Cancelled.");
1136
- process.exit(0);
1137
- }
1138
- query = input;
1139
- }
1140
- const spinner5 = p4.spinner();
1141
- spinner5.start(`Searching ${platform} actions...`);
1142
- try {
1143
- const limit = options.limit ? parseInt(options.limit, 10) : 10;
1144
- const actions2 = await api.searchActions(platform, query, limit);
1145
- spinner5.stop(`${actions2.length} action${actions2.length === 1 ? "" : "s"} found`);
1146
- if (options.json) {
1147
- console.log(JSON.stringify(actions2, null, 2));
1148
- return;
1149
- }
1150
- if (actions2.length === 0) {
1151
- p4.note(`No actions found for "${query}" on ${platform}.`, "No Results");
1152
- return;
1153
- }
1154
- console.log();
1155
- const rows = actions2.map((action) => ({
1156
- method: colorMethod(padMethod(action.method)),
1157
- path: action.path,
1158
- title: action.title,
1159
- id: action._id
1160
- }));
1161
- printTable(
1162
- [
1163
- { key: "method", label: "Method" },
1164
- { key: "title", label: "Title" },
1165
- { key: "path", label: "Path", color: pc5.dim },
1166
- { key: "id", label: "Action ID", color: pc5.dim }
1167
- ],
1168
- rows
1169
- );
1170
- console.log();
1171
- p4.note(
1172
- `Get docs: ${pc5.cyan("pica actions knowledge <actionId>")}
1173
- Execute: ${pc5.cyan("pica actions execute <actionId>")}`,
1174
- "Next Steps"
1175
- );
1176
- } catch (error) {
1177
- spinner5.stop("Search failed");
1178
- p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1179
- process.exit(1);
1180
- }
1181
- }
1182
- async function actionsKnowledgeCommand(actionId, options = {}) {
1183
- const api = getApi();
1184
- const spinner5 = p4.spinner();
1185
- spinner5.start("Loading action knowledge...");
1186
- try {
1187
- const knowledge = await api.getActionKnowledge(actionId);
1188
- spinner5.stop("Action knowledge loaded");
1189
- if (!knowledge) {
1190
- p4.cancel(`No knowledge found for action: ${actionId}`);
1191
- process.exit(1);
1192
- }
1193
- if (options.json) {
1194
- console.log(JSON.stringify(knowledge, null, 2));
1195
- return;
1196
- }
1197
- printKnowledge(knowledge, options.full);
1198
- } catch (error) {
1199
- spinner5.stop("Failed to load knowledge");
1200
- p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1201
- process.exit(1);
1202
- }
1203
- }
1204
- function printKnowledge(k, full) {
1205
- console.log();
1206
- console.log(pc5.bold(` ${k.title}`));
1207
- console.log();
1208
- console.log(` Platform: ${pc5.cyan(k.connectionPlatform)}`);
1209
- console.log(` Method: ${colorMethod(k.method)}`);
1210
- console.log(` Path: ${k.path}`);
1211
- console.log(` Base URL: ${pc5.dim(k.baseUrl)}`);
1212
- if (k.tags?.length) {
1213
- console.log(` Tags: ${k.tags.map((t) => pc5.dim(t)).join(", ")}`);
1214
- }
1215
- const pathVars = extractPathVariables(k.path);
1216
- if (pathVars.length > 0) {
1217
- console.log(` Path Vars: ${pathVars.map((v) => pc5.yellow(`{{${v}}}`)).join(", ")}`);
1218
- }
1219
- console.log(` Active: ${k.active ? pc5.green("yes") : pc5.red("no")}`);
1220
- console.log(` ID: ${pc5.dim(k._id)}`);
1221
- if (k.knowledge) {
1222
- console.log();
1223
- console.log(pc5.bold(" API Documentation"));
1224
- console.log(pc5.dim(" " + "\u2500".repeat(40)));
1225
- console.log();
1226
- const lines = k.knowledge.split("\n");
1227
- const displayLines = full ? lines : lines.slice(0, 50);
1228
- for (const line of displayLines) {
1229
- console.log(` ${line}`);
1230
- }
1231
- if (!full && lines.length > 50) {
1232
- console.log();
1233
- console.log(pc5.dim(` ... ${lines.length - 50} more lines. Use --full to see all.`));
1234
- }
1235
- }
1236
- console.log();
1237
- }
1238
- async function actionsExecuteCommand(actionId, options = {}) {
1239
- const api = getApi();
1240
- const spinner5 = p4.spinner();
1241
- spinner5.start("Loading action details...");
1242
- let knowledge;
1243
- try {
1244
- const k = await api.getActionKnowledge(actionId);
1245
- if (!k) {
1246
- spinner5.stop("Action not found");
1247
- p4.cancel(`No action found for: ${actionId}`);
1248
- process.exit(1);
1249
- }
1250
- knowledge = k;
1251
- spinner5.stop(`${colorMethod(knowledge.method)} ${knowledge.path}`);
1252
- } catch (error) {
1253
- spinner5.stop("Failed to load action");
1254
- p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
1255
- process.exit(1);
1256
- }
1257
- let connectionKey = options.connection;
1258
- if (!connectionKey) {
1259
- connectionKey = await resolveConnection(api, knowledge.connectionPlatform);
1260
- }
1261
- const pathVarMap = parseKeyValuePairs(options.pathVar || []);
1262
- const pathVars = extractPathVariables(knowledge.path);
1263
- for (const v of pathVars) {
1264
- if (!pathVarMap[v]) {
1265
- const input = await p4.text({
1266
- message: `Value for path variable ${pc5.yellow(`{{${v}}}`)}:`,
1267
- validate: (val) => val.trim() ? void 0 : "Value is required"
1268
- });
1269
- if (p4.isCancel(input)) {
1270
- p4.cancel("Cancelled.");
1271
- process.exit(0);
1272
- }
1273
- pathVarMap[v] = input;
1274
- }
1275
- }
1276
- let bodyData = {};
1277
- if (options.data) {
1278
- try {
1279
- bodyData = JSON.parse(options.data);
1280
- } catch {
1281
- p4.cancel("Invalid JSON in --data flag.");
1282
- process.exit(1);
1283
- }
1284
- } else if (!["GET", "DELETE", "HEAD"].includes(knowledge.method.toUpperCase())) {
1285
- const input = await p4.text({
1286
- message: "Request body (JSON):",
1287
- placeholder: '{"key": "value"} or leave empty'
1288
- });
1289
- if (p4.isCancel(input)) {
1290
- p4.cancel("Cancelled.");
1291
- process.exit(0);
1292
- }
1293
- if (input.trim()) {
1294
- try {
1295
- bodyData = JSON.parse(input);
1296
- } catch {
1297
- p4.cancel("Invalid JSON.");
1298
- process.exit(1);
1299
- }
1300
- }
1301
- }
1302
- const queryParams = parseKeyValuePairs(options.query || []);
1303
- const { resolvedPath, remainingData } = resolveTemplateVariables(
1304
- knowledge.path,
1305
- bodyData,
1306
- pathVarMap
1307
- );
1308
- console.log();
1309
- console.log(pc5.bold(" Request Summary"));
1310
- console.log(` ${colorMethod(knowledge.method)} ${knowledge.baseUrl}${resolvedPath}`);
1311
- console.log(` Connection: ${pc5.dim(connectionKey)}`);
1312
- if (Object.keys(remainingData).length > 0) {
1313
- console.log(` Body: ${pc5.dim(JSON.stringify(remainingData))}`);
1314
- }
1315
- if (Object.keys(queryParams).length > 0) {
1316
- console.log(` Query: ${pc5.dim(JSON.stringify(queryParams))}`);
1317
- }
1318
- console.log();
1319
- const execSpinner = p4.spinner();
1320
- execSpinner.start("Executing...");
1321
- try {
1322
- const result = await api.executeAction({
1323
- method: knowledge.method,
1324
- path: resolvedPath,
1325
- actionId,
1326
- connectionKey,
1327
- data: Object.keys(remainingData).length > 0 ? remainingData : void 0,
1328
- queryParams: Object.keys(queryParams).length > 0 ? queryParams : void 0,
1329
- isFormData: options.formData,
1330
- isFormUrlEncoded: options.formUrlencoded
1331
- });
1332
- execSpinner.stop(pc5.green("Success"));
1333
- if (options.json) {
1334
- console.log(JSON.stringify(result, null, 2));
1335
- } else {
1336
- console.log();
1337
- console.log(pc5.bold(" Response"));
1338
- console.log(pc5.dim(" " + "\u2500".repeat(40)));
1339
- console.log();
1340
- console.log(formatResponse(result));
1341
- console.log();
1342
- }
1343
- } catch (error) {
1344
- execSpinner.stop(pc5.red("Failed"));
1345
- const msg = error instanceof Error ? error.message : "Unknown error";
1346
- p4.cancel(`Execution failed: ${msg}`);
1347
- process.exit(1);
1348
- }
1349
- }
1350
- async function resolveConnection(api, platform) {
1351
- const spinner5 = p4.spinner();
1352
- spinner5.start("Loading connections...");
1353
- const connections = await api.listConnections();
1354
- const matching = connections.filter(
1355
- (c) => c.platform.toLowerCase() === platform.toLowerCase()
1356
- );
1357
- spinner5.stop(`${matching.length} ${platform} connection${matching.length === 1 ? "" : "s"} found`);
1358
- if (matching.length === 0) {
1359
- p4.cancel(
1360
- `No ${platform} connections found.
1361
-
1362
- Add one with: ${pc5.cyan(`pica connection add ${platform}`)}`
1363
- );
1364
- process.exit(1);
1365
- }
1366
- if (matching.length === 1) {
1367
- p4.log.info(`Using connection: ${pc5.dim(matching[0].key)}`);
1368
- return matching[0].key;
1369
- }
1370
- const selected = await p4.select({
1371
- message: `Multiple ${platform} connections found. Which one?`,
1372
- options: matching.map((c) => ({
1373
- value: c.key,
1374
- label: `${c.key}`,
1375
- hint: c.state
1376
- }))
1377
- });
1378
- if (p4.isCancel(selected)) {
1379
- p4.cancel("Cancelled.");
1380
- process.exit(0);
1381
- }
1382
- return selected;
1383
- }
1384
- function parseKeyValuePairs(pairs) {
1385
- const result = {};
1386
- for (const pair of pairs) {
1387
- const eqIdx = pair.indexOf("=");
1388
- if (eqIdx === -1) continue;
1389
- const key = pair.slice(0, eqIdx);
1390
- const value = pair.slice(eqIdx + 1);
1391
- result[key] = value;
1392
- }
1393
- return result;
1394
- }
1395
- function formatResponse(data, indent = 2) {
1396
- const prefix = " ".repeat(indent);
1397
- const json = JSON.stringify(data, null, 2);
1398
- return json.split("\n").map((line) => `${prefix}${line}`).join("\n");
1399
- }
1400
-
1401
1115
  // src/index.ts
1402
1116
  var require2 = createRequire(import.meta.url);
1403
1117
  var { version } = require2("../package.json");
@@ -1422,23 +1136,4 @@ program.command("add [platform]").description("Shortcut for: connection add").ac
1422
1136
  program.command("list").alias("ls").description("Shortcut for: connection list").action(async () => {
1423
1137
  await connectionListCommand();
1424
1138
  });
1425
- var actions = program.command("actions").alias("a").description("Discover and execute platform actions");
1426
- 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) => {
1427
- await actionsSearchCommand(platform, query, options);
1428
- });
1429
- 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) => {
1430
- await actionsKnowledgeCommand(actionId, options);
1431
- });
1432
- 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) => {
1433
- await actionsExecuteCommand(actionId, options);
1434
- });
1435
- 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) => {
1436
- await actionsSearchCommand(platform, query, options);
1437
- });
1438
- 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) => {
1439
- await actionsExecuteCommand(actionId, options);
1440
- });
1441
- function collectValues(value, previous) {
1442
- return (previous || []).concat([value]);
1443
- }
1444
1139
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picahq/cli",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "CLI for managing Pica",
5
5
  "type": "module",
6
6
  "files": [