@picahq/cli 1.8.0 → 1.9.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 +166 -95
- package/dist/index.js +488 -37
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,163 +1,234 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Pica CLI
|
|
2
2
|
|
|
3
|
-
CLI
|
|
3
|
+
One CLI to connect AI agents to every API on the internet.
|
|
4
|
+
|
|
5
|
+
Pica gives your AI agent authenticated access to 200+ platforms — Gmail, Slack, Shopify, HubSpot, Stripe, Notion, and everything else — through a single interface. No API keys to juggle, no OAuth flows to build, no request formats to memorize. Connect a platform once, and your agent can search for actions, read the docs, and execute API calls in seconds.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
|
|
9
|
-
npm run build
|
|
10
|
-
npm link
|
|
10
|
+
npx @picahq/cli@latest init
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Setup
|
|
13
|
+
Or install globally:
|
|
16
14
|
|
|
17
15
|
```bash
|
|
16
|
+
npm install -g @picahq/cli
|
|
18
17
|
pica init
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
1. Entering your Pica API key (validates it)
|
|
23
|
-
2. Choosing which AI agents to install the MCP server into
|
|
24
|
-
3. Choosing global vs project-level installation
|
|
20
|
+
`pica init` walks you through setup: enter your [API key](https://app.picaos.com/settings/api-keys), pick your AI agents, and you're done. The MCP server gets installed automatically.
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
Requires Node.js 18+.
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Connect a platform
|
|
28
|
+
pica add gmail
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
# See what you're connected to
|
|
31
|
+
pica list
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
# Search for actions you can take
|
|
34
|
+
pica actions search gmail "send email" -t execute
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
# Read the docs for an action
|
|
37
|
+
pica actions knowledge gmail <actionId>
|
|
33
38
|
|
|
39
|
+
# Execute it
|
|
40
|
+
pica actions execute gmail <actionId> <connectionKey> \
|
|
41
|
+
-d '{"to": "jane@example.com", "subject": "Hello", "body": "Sent from my AI agent"}'
|
|
34
42
|
```
|
|
35
|
-
Pica
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
──────────────────────────────────────────
|
|
39
|
-
API Key: sk_test_...9j-Y
|
|
40
|
-
Config: ~/.pica/config.json
|
|
44
|
+
That's it. Five commands to go from zero to sending an email through Gmail's API — fully authenticated, correctly formatted, without touching a single OAuth token.
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
────────────── ────── ───────
|
|
44
|
-
Claude Code ● yes ● yes
|
|
45
|
-
Claude Desktop ● yes -
|
|
46
|
-
Cursor ○ no ○ no
|
|
47
|
-
Windsurf - -
|
|
48
|
-
Codex ● yes ○ no
|
|
49
|
-
Kiro ○ no ○ no
|
|
46
|
+
## How it works
|
|
50
47
|
|
|
51
|
-
|
|
48
|
+
```
|
|
49
|
+
Your AI Agent
|
|
50
|
+
↓
|
|
51
|
+
Pica CLI
|
|
52
|
+
↓
|
|
53
|
+
Pica API (api.picaos.com/v1/passthrough)
|
|
54
|
+
↓
|
|
55
|
+
Gmail / Slack / Shopify / HubSpot / Stripe / ...
|
|
52
56
|
```
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
Every API call routes through Pica's passthrough proxy. Pica injects the right credentials, handles rate limiting, and normalizes responses. You never see or manage raw OAuth tokens — your connection key is all you need.
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
- **Install MCP to more agents** -- only shows detected agents missing the MCP
|
|
58
|
-
- **Install MCP for this project** -- creates `.mcp.json` / `.cursor/mcp.json` / `.codex/config.toml` / `.kiro/settings/mcp.json` in cwd for agents that support project scope
|
|
59
|
-
- **Start fresh** -- full setup flow from scratch
|
|
60
|
+
## Commands
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
### `pica init`
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
Set up your API key and install the MCP server into your AI agents.
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
```bash
|
|
67
|
+
pica init
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Supports Claude Code, Claude Desktop, Cursor, Windsurf, Codex, and Kiro. Installs globally by default, or per-project with `-p` so your team can share configs (each person uses their own API key).
|
|
71
|
+
|
|
72
|
+
If you've already set up, `pica init` shows your current status and lets you update your key, install to more agents, or reconfigure.
|
|
73
|
+
|
|
74
|
+
| Flag | What it does |
|
|
75
|
+
|------|-------------|
|
|
76
|
+
| `-y` | Skip confirmations |
|
|
77
|
+
| `-g` | Install globally (default) |
|
|
78
|
+
| `-p` | Install for current project only |
|
|
70
79
|
|
|
71
|
-
|
|
80
|
+
### `pica add <platform>`
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
Connect a new platform via OAuth.
|
|
74
83
|
|
|
75
84
|
```bash
|
|
85
|
+
pica add shopify
|
|
86
|
+
pica add hub-spot
|
|
76
87
|
pica add gmail
|
|
77
88
|
```
|
|
78
89
|
|
|
79
|
-
Opens
|
|
90
|
+
Opens your browser, you authorize, done. The CLI polls until the connection is live. Platform names are kebab-case — run `pica platforms` to see them all.
|
|
80
91
|
|
|
81
|
-
###
|
|
92
|
+
### `pica list`
|
|
93
|
+
|
|
94
|
+
List your active connections with their status and connection keys.
|
|
82
95
|
|
|
83
96
|
```bash
|
|
84
97
|
pica list
|
|
85
98
|
```
|
|
86
99
|
|
|
87
100
|
```
|
|
88
|
-
● gmail
|
|
89
|
-
|
|
90
|
-
●
|
|
91
|
-
live::slack::default::def456
|
|
101
|
+
● gmail operational live::gmail::default::abc123
|
|
102
|
+
● slack operational live::slack::default::def456
|
|
103
|
+
● shopify operational live::shopify::default::ghi789
|
|
92
104
|
```
|
|
93
105
|
|
|
94
|
-
|
|
106
|
+
You need the connection key (rightmost column) when executing actions.
|
|
107
|
+
|
|
108
|
+
### `pica platforms`
|
|
109
|
+
|
|
110
|
+
Browse all 200+ available platforms.
|
|
95
111
|
|
|
96
112
|
```bash
|
|
97
|
-
pica platforms
|
|
98
|
-
pica platforms -c "CRM"
|
|
113
|
+
pica platforms # all platforms
|
|
114
|
+
pica platforms -c "CRM" # filter by category
|
|
115
|
+
pica platforms --json # machine-readable output
|
|
99
116
|
```
|
|
100
117
|
|
|
101
|
-
|
|
118
|
+
### `pica actions search <platform> <query>`
|
|
119
|
+
|
|
120
|
+
Search for API actions on a connected platform using natural language.
|
|
102
121
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
| `pica platforms` | Browse available platforms |
|
|
122
|
+
```bash
|
|
123
|
+
pica actions search shopify "list products"
|
|
124
|
+
pica actions search hub-spot "create contact" -t execute
|
|
125
|
+
pica actions search gmail "send email"
|
|
126
|
+
```
|
|
109
127
|
|
|
110
|
-
|
|
128
|
+
Returns the top 5 matching actions with their action IDs, HTTP methods, and paths. Use `-t execute` when you intend to run the action, or `-t knowledge` (default) when you want to learn about it or write code against it.
|
|
111
129
|
|
|
112
|
-
###
|
|
130
|
+
### `pica actions knowledge <platform> <actionId>`
|
|
113
131
|
|
|
114
|
-
|
|
115
|
-
|-------|------|
|
|
116
|
-
| `pica ls` | `pica list` |
|
|
117
|
-
| `pica p` | `pica platforms` |
|
|
132
|
+
Get the full documentation for an action — parameters, validation rules, request/response structure, examples, and the exact API request format.
|
|
118
133
|
|
|
119
|
-
|
|
134
|
+
```bash
|
|
135
|
+
pica actions knowledge shopify 67890abcdef
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Always read the knowledge before executing. It tells you exactly what parameters are required, what format they need, and any platform-specific quirks.
|
|
139
|
+
|
|
140
|
+
### `pica actions execute <platform> <actionId> <connectionKey>`
|
|
141
|
+
|
|
142
|
+
Execute an API action on a connected platform.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Simple GET
|
|
146
|
+
pica actions execute shopify <actionId> <connectionKey>
|
|
147
|
+
|
|
148
|
+
# POST with data
|
|
149
|
+
pica actions execute hub-spot <actionId> <connectionKey> \
|
|
150
|
+
-d '{"properties": {"email": "jane@example.com", "firstname": "Jane"}}'
|
|
151
|
+
|
|
152
|
+
# With path variables
|
|
153
|
+
pica actions execute shopify <actionId> <connectionKey> \
|
|
154
|
+
--path-vars '{"order_id": "12345"}'
|
|
155
|
+
|
|
156
|
+
# With query params
|
|
157
|
+
pica actions execute stripe <actionId> <connectionKey> \
|
|
158
|
+
--query-params '{"limit": "10"}'
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| Option | What it does |
|
|
162
|
+
|--------|-------------|
|
|
163
|
+
| `-d, --data <json>` | Request body (POST, PUT, PATCH) |
|
|
164
|
+
| `--path-vars <json>` | Replace `{variables}` in the URL path |
|
|
165
|
+
| `--query-params <json>` | Query string parameters |
|
|
166
|
+
| `--headers <json>` | Additional request headers |
|
|
167
|
+
| `--form-data` | Send as multipart/form-data |
|
|
168
|
+
| `--form-url-encoded` | Send as application/x-www-form-urlencoded |
|
|
169
|
+
|
|
170
|
+
### `pica config`
|
|
171
|
+
|
|
172
|
+
Configure access control for the MCP server. Optional — full access is the default.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
pica config
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
| Setting | Options | Default |
|
|
179
|
+
|---------|---------|---------|
|
|
180
|
+
| Permission level | `admin` / `write` / `read` | `admin` |
|
|
181
|
+
| Connection scope | All or specific connections | All |
|
|
182
|
+
| Action scope | All or specific action IDs | All |
|
|
183
|
+
| Knowledge-only mode | Enable/disable execution | Off |
|
|
184
|
+
|
|
185
|
+
Settings propagate automatically to all installed agent configs.
|
|
186
|
+
|
|
187
|
+
## The workflow
|
|
188
|
+
|
|
189
|
+
The power of Pica is in the workflow. Every interaction follows the same pattern:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
pica list → What am I connected to?
|
|
193
|
+
pica actions search → What can I do?
|
|
194
|
+
pica actions knowledge → How do I do it?
|
|
195
|
+
pica actions execute → Do it.
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
This is the same workflow whether you're sending emails, creating CRM contacts, processing payments, managing inventory, or posting to Slack. One pattern, any platform.
|
|
199
|
+
|
|
200
|
+
## For AI agents
|
|
201
|
+
|
|
202
|
+
If you're an AI agent using the Pica MCP server, the tools map directly:
|
|
203
|
+
|
|
204
|
+
| MCP Tool | CLI Command |
|
|
205
|
+
|----------|------------|
|
|
206
|
+
| `list_pica_integrations` | `pica list` + `pica platforms` |
|
|
207
|
+
| `search_pica_platform_actions` | `pica actions search` |
|
|
208
|
+
| `get_pica_action_knowledge` | `pica actions knowledge` |
|
|
209
|
+
| `execute_pica_action` | `pica actions execute` |
|
|
120
210
|
|
|
121
|
-
|
|
211
|
+
The workflow is the same: list → search → knowledge → execute. Never skip the knowledge step — it contains required parameter info and platform-specific details that are critical for building correct requests.
|
|
122
212
|
|
|
123
|
-
## MCP installation
|
|
213
|
+
## MCP server installation
|
|
124
214
|
|
|
125
|
-
`pica init`
|
|
215
|
+
`pica init` handles this automatically. Here's where configs go:
|
|
126
216
|
|
|
127
|
-
| Agent | Global
|
|
128
|
-
|
|
217
|
+
| Agent | Global | Project |
|
|
218
|
+
|-------|--------|---------|
|
|
129
219
|
| Claude Code | `~/.claude.json` | `.mcp.json` |
|
|
130
|
-
| Claude Desktop |
|
|
220
|
+
| Claude Desktop | Platform-specific app support dir | — |
|
|
131
221
|
| Cursor | `~/.cursor/mcp.json` | `.cursor/mcp.json` |
|
|
132
|
-
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
222
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | — |
|
|
133
223
|
| Codex | `~/.codex/config.toml` | `.codex/config.toml` |
|
|
134
224
|
| Kiro | `~/.kiro/settings/mcp.json` | `.kiro/settings/mcp.json` |
|
|
135
225
|
|
|
136
|
-
|
|
226
|
+
Project configs can be committed to your repo. Each team member runs `pica init` with their own API key.
|
|
137
227
|
|
|
138
228
|
## Development
|
|
139
229
|
|
|
140
230
|
```bash
|
|
141
231
|
npm run dev # watch mode
|
|
142
232
|
npm run build # production build
|
|
143
|
-
npm run typecheck # type check
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Project structure
|
|
147
|
-
|
|
148
|
-
```
|
|
149
|
-
src/
|
|
150
|
-
index.ts # Commander setup and command registration
|
|
151
|
-
commands/
|
|
152
|
-
init.ts # pica init (setup, status display, targeted actions)
|
|
153
|
-
connection.ts # pica add, pica list
|
|
154
|
-
platforms.ts # pica platforms
|
|
155
|
-
lib/
|
|
156
|
-
api.ts # HTTP client for Pica API
|
|
157
|
-
types.ts # TypeScript interfaces
|
|
158
|
-
config.ts # ~/.pica/config.json read/write
|
|
159
|
-
agents.ts # Agent detection, MCP config, status reporting
|
|
160
|
-
platforms.ts # Platform search and fuzzy matching
|
|
161
|
-
browser.ts # Open browser for OAuth and API key pages
|
|
162
|
-
table.ts # Formatted table output
|
|
233
|
+
npm run typecheck # type check
|
|
163
234
|
```
|
package/dist/index.js
CHANGED
|
@@ -73,11 +73,11 @@ import fs2 from "fs";
|
|
|
73
73
|
import path2 from "path";
|
|
74
74
|
import os2 from "os";
|
|
75
75
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
76
|
-
function expandPath(
|
|
77
|
-
if (
|
|
78
|
-
return path2.join(os2.homedir(),
|
|
76
|
+
function expandPath(p6) {
|
|
77
|
+
if (p6.startsWith("~/")) {
|
|
78
|
+
return path2.join(os2.homedir(), p6.slice(2));
|
|
79
79
|
}
|
|
80
|
-
return
|
|
80
|
+
return p6;
|
|
81
81
|
}
|
|
82
82
|
function getClaudeDesktopConfigPath() {
|
|
83
83
|
switch (process.platform) {
|
|
@@ -320,6 +320,140 @@ var PicaApi = class {
|
|
|
320
320
|
} while (page <= totalPages);
|
|
321
321
|
return allPlatforms;
|
|
322
322
|
}
|
|
323
|
+
async searchActions(platform, query, agentType) {
|
|
324
|
+
const isKnowledgeAgent = !agentType || agentType === "knowledge";
|
|
325
|
+
const queryParams = {
|
|
326
|
+
query,
|
|
327
|
+
limit: "5"
|
|
328
|
+
};
|
|
329
|
+
if (isKnowledgeAgent) {
|
|
330
|
+
queryParams.knowledgeAgent = "true";
|
|
331
|
+
} else {
|
|
332
|
+
queryParams.executeAgent = "true";
|
|
333
|
+
}
|
|
334
|
+
const response = await this.requestFull({
|
|
335
|
+
path: `/available-actions/search/${platform}`,
|
|
336
|
+
queryParams
|
|
337
|
+
});
|
|
338
|
+
return response || [];
|
|
339
|
+
}
|
|
340
|
+
async getActionDetails(actionId) {
|
|
341
|
+
const response = await this.requestFull({
|
|
342
|
+
path: "/knowledge",
|
|
343
|
+
queryParams: { _id: actionId }
|
|
344
|
+
});
|
|
345
|
+
const actions2 = response?.rows || [];
|
|
346
|
+
if (actions2.length === 0) {
|
|
347
|
+
throw new ApiError(404, `Action with ID ${actionId} not found`);
|
|
348
|
+
}
|
|
349
|
+
return actions2[0];
|
|
350
|
+
}
|
|
351
|
+
async getActionKnowledge(actionId) {
|
|
352
|
+
const action = await this.getActionDetails(actionId);
|
|
353
|
+
if (!action.knowledge || !action.method) {
|
|
354
|
+
return {
|
|
355
|
+
knowledge: "No knowledge was found",
|
|
356
|
+
method: "No method was found"
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
knowledge: action.knowledge,
|
|
361
|
+
method: action.method
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
async executePassthroughRequest(args, preloadedAction) {
|
|
365
|
+
const action = preloadedAction ?? await this.getActionDetails(args.actionId);
|
|
366
|
+
const method = action.method;
|
|
367
|
+
const contentType = args.isFormData ? "multipart/form-data" : args.isFormUrlEncoded ? "application/x-www-form-urlencoded" : "application/json";
|
|
368
|
+
const requestHeaders = {
|
|
369
|
+
"x-pica-secret": this.apiKey,
|
|
370
|
+
"x-pica-connection-key": args.connectionKey,
|
|
371
|
+
"x-pica-action-id": action._id,
|
|
372
|
+
"Content-Type": contentType,
|
|
373
|
+
...args.headers
|
|
374
|
+
};
|
|
375
|
+
const finalActionPath = args.pathVariables ? replacePathVariables(action.path, args.pathVariables) : action.path;
|
|
376
|
+
const normalizedPath = finalActionPath.startsWith("/") ? finalActionPath : `/${finalActionPath}`;
|
|
377
|
+
const url = `${API_BASE.replace("/v1", "")}/v1/passthrough${normalizedPath}`;
|
|
378
|
+
const isCustomAction = action.tags?.includes("custom");
|
|
379
|
+
let requestData = args.data;
|
|
380
|
+
if (isCustomAction && method?.toLowerCase() !== "get") {
|
|
381
|
+
requestData = {
|
|
382
|
+
...args.data,
|
|
383
|
+
connectionKey: args.connectionKey
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
let queryString = "";
|
|
387
|
+
if (args.queryParams && Object.keys(args.queryParams).length > 0) {
|
|
388
|
+
const params = new URLSearchParams(
|
|
389
|
+
Object.entries(args.queryParams).map(([k, v]) => [k, String(v)])
|
|
390
|
+
);
|
|
391
|
+
queryString = `?${params.toString()}`;
|
|
392
|
+
}
|
|
393
|
+
const fullUrl = `${url}${queryString}`;
|
|
394
|
+
const fetchOpts = {
|
|
395
|
+
method,
|
|
396
|
+
headers: requestHeaders
|
|
397
|
+
};
|
|
398
|
+
if (method?.toLowerCase() !== "get" && requestData !== void 0) {
|
|
399
|
+
if (args.isFormUrlEncoded) {
|
|
400
|
+
const params = new URLSearchParams();
|
|
401
|
+
if (requestData && typeof requestData === "object" && !Array.isArray(requestData)) {
|
|
402
|
+
Object.entries(requestData).forEach(([key, value]) => {
|
|
403
|
+
if (typeof value === "object") {
|
|
404
|
+
params.append(key, JSON.stringify(value));
|
|
405
|
+
} else {
|
|
406
|
+
params.append(key, String(value));
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
fetchOpts.body = params.toString();
|
|
411
|
+
} else if (args.isFormData) {
|
|
412
|
+
const boundary = `----FormBoundary${Date.now()}`;
|
|
413
|
+
requestHeaders["Content-Type"] = `multipart/form-data; boundary=${boundary}`;
|
|
414
|
+
let body = "";
|
|
415
|
+
if (requestData && typeof requestData === "object" && !Array.isArray(requestData)) {
|
|
416
|
+
Object.entries(requestData).forEach(([key, value]) => {
|
|
417
|
+
body += `--${boundary}\r
|
|
418
|
+
`;
|
|
419
|
+
body += `Content-Disposition: form-data; name="${key}"\r
|
|
420
|
+
\r
|
|
421
|
+
`;
|
|
422
|
+
body += typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
423
|
+
body += "\r\n";
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
body += `--${boundary}--\r
|
|
427
|
+
`;
|
|
428
|
+
fetchOpts.body = body;
|
|
429
|
+
fetchOpts.headers = requestHeaders;
|
|
430
|
+
} else {
|
|
431
|
+
fetchOpts.body = JSON.stringify(requestData);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const sanitizedConfig = {
|
|
435
|
+
url: fullUrl,
|
|
436
|
+
method,
|
|
437
|
+
headers: {
|
|
438
|
+
...requestHeaders,
|
|
439
|
+
"x-pica-secret": "***REDACTED***"
|
|
440
|
+
},
|
|
441
|
+
params: args.queryParams ? Object.fromEntries(
|
|
442
|
+
Object.entries(args.queryParams).map(([k, v]) => [k, String(v)])
|
|
443
|
+
) : void 0,
|
|
444
|
+
data: requestData
|
|
445
|
+
};
|
|
446
|
+
const response = await fetch(fullUrl, fetchOpts);
|
|
447
|
+
if (!response.ok) {
|
|
448
|
+
const text4 = await response.text();
|
|
449
|
+
throw new ApiError(response.status, text4 || `HTTP ${response.status}`);
|
|
450
|
+
}
|
|
451
|
+
const responseData = await response.json();
|
|
452
|
+
return {
|
|
453
|
+
requestConfig: sanitizedConfig,
|
|
454
|
+
responseData
|
|
455
|
+
};
|
|
456
|
+
}
|
|
323
457
|
async waitForConnection(platform, timeoutMs = 5 * 60 * 1e3, pollIntervalMs = 5e3, onPoll) {
|
|
324
458
|
const startTime = Date.now();
|
|
325
459
|
const existingConnections = await this.listConnections();
|
|
@@ -347,6 +481,72 @@ var TimeoutError = class extends Error {
|
|
|
347
481
|
function sleep(ms) {
|
|
348
482
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
349
483
|
}
|
|
484
|
+
function replacePathVariables(path3, variables) {
|
|
485
|
+
if (!path3) return path3;
|
|
486
|
+
let result = path3;
|
|
487
|
+
result = result.replace(/\{\{([^}]+)\}\}/g, (_match, variable) => {
|
|
488
|
+
const trimmedVariable = variable.trim();
|
|
489
|
+
const value = variables[trimmedVariable];
|
|
490
|
+
if (value === void 0 || value === null || value === "") {
|
|
491
|
+
throw new Error(`Missing value for path variable: ${trimmedVariable}`);
|
|
492
|
+
}
|
|
493
|
+
return encodeURIComponent(value.toString());
|
|
494
|
+
});
|
|
495
|
+
result = result.replace(/\{([^}]+)\}/g, (_match, variable) => {
|
|
496
|
+
const trimmedVariable = variable.trim();
|
|
497
|
+
const value = variables[trimmedVariable];
|
|
498
|
+
if (value === void 0 || value === null || value === "") {
|
|
499
|
+
throw new Error(`Missing value for path variable: ${trimmedVariable}`);
|
|
500
|
+
}
|
|
501
|
+
return encodeURIComponent(value.toString());
|
|
502
|
+
});
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
var PERMISSION_METHODS = {
|
|
506
|
+
read: ["GET"],
|
|
507
|
+
write: ["GET", "POST", "PUT", "PATCH"],
|
|
508
|
+
admin: null
|
|
509
|
+
};
|
|
510
|
+
function filterByPermissions(actions2, permissions) {
|
|
511
|
+
const allowed = PERMISSION_METHODS[permissions];
|
|
512
|
+
if (allowed === null) return actions2;
|
|
513
|
+
return actions2.filter((a) => allowed.includes(a.method.toUpperCase()));
|
|
514
|
+
}
|
|
515
|
+
function isMethodAllowed(method, permissions) {
|
|
516
|
+
const allowed = PERMISSION_METHODS[permissions];
|
|
517
|
+
if (allowed === null) return true;
|
|
518
|
+
return allowed.includes(method.toUpperCase());
|
|
519
|
+
}
|
|
520
|
+
function isActionAllowed(actionId, allowedActionIds) {
|
|
521
|
+
return allowedActionIds.includes("*") || allowedActionIds.includes(actionId);
|
|
522
|
+
}
|
|
523
|
+
function buildActionKnowledgeWithGuidance(knowledge, method, platform, actionId) {
|
|
524
|
+
const baseUrl = "https://api.picaos.com";
|
|
525
|
+
return `${knowledge}
|
|
526
|
+
|
|
527
|
+
API REQUEST STRUCTURE
|
|
528
|
+
======================
|
|
529
|
+
URL: ${baseUrl}/v1/passthrough/{{PATH}}
|
|
530
|
+
|
|
531
|
+
IMPORTANT: When constructing the URL, only include the API endpoint path after the base URL.
|
|
532
|
+
Do NOT include the full third-party API URL.
|
|
533
|
+
|
|
534
|
+
Examples:
|
|
535
|
+
Correct: ${baseUrl}/v1/passthrough/crm/v3/objects/contacts/search
|
|
536
|
+
Incorrect: ${baseUrl}/v1/passthrough/https://api.hubapi.com/crm/v3/objects/contacts/search
|
|
537
|
+
|
|
538
|
+
METHOD: ${method}
|
|
539
|
+
|
|
540
|
+
HEADERS:
|
|
541
|
+
- x-pica-secret: {{process.env.PICA_SECRET}}
|
|
542
|
+
- x-pica-connection-key: {{process.env.PICA_${platform.toUpperCase()}_CONNECTION_KEY}}
|
|
543
|
+
- x-pica-action-id: ${actionId}
|
|
544
|
+
- ... (other headers)
|
|
545
|
+
|
|
546
|
+
BODY: {{BODY}}
|
|
547
|
+
|
|
548
|
+
QUERY PARAMS: {{QUERY_PARAMS}}`;
|
|
549
|
+
}
|
|
350
550
|
|
|
351
551
|
// src/lib/browser.ts
|
|
352
552
|
import open from "open";
|
|
@@ -484,16 +684,16 @@ async function configCommand() {
|
|
|
484
684
|
p.outro("Access control updated.");
|
|
485
685
|
}
|
|
486
686
|
async function selectConnections(apiKey) {
|
|
487
|
-
const
|
|
488
|
-
|
|
687
|
+
const spinner6 = p.spinner();
|
|
688
|
+
spinner6.start("Fetching connections...");
|
|
489
689
|
let connections;
|
|
490
690
|
try {
|
|
491
691
|
const api = new PicaApi(apiKey);
|
|
492
692
|
const rawConnections = await api.listConnections();
|
|
493
693
|
connections = rawConnections.map((c) => ({ platform: c.platform, key: c.key }));
|
|
494
|
-
|
|
694
|
+
spinner6.stop(`Found ${connections.length} connection(s)`);
|
|
495
695
|
} catch {
|
|
496
|
-
|
|
696
|
+
spinner6.stop("Could not fetch connections");
|
|
497
697
|
const manual = await p.text({
|
|
498
698
|
message: "Enter connection keys manually (comma-separated):",
|
|
499
699
|
placeholder: "conn_key_1, conn_key_2",
|
|
@@ -695,16 +895,16 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
695
895
|
p2.cancel("Cancelled.");
|
|
696
896
|
process.exit(0);
|
|
697
897
|
}
|
|
698
|
-
const
|
|
699
|
-
|
|
898
|
+
const spinner6 = p2.spinner();
|
|
899
|
+
spinner6.start("Validating API key...");
|
|
700
900
|
const api = new PicaApi(newKey);
|
|
701
901
|
const isValid = await api.validateApiKey();
|
|
702
902
|
if (!isValid) {
|
|
703
|
-
|
|
903
|
+
spinner6.stop("Invalid API key");
|
|
704
904
|
p2.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
|
|
705
905
|
process.exit(1);
|
|
706
906
|
}
|
|
707
|
-
|
|
907
|
+
spinner6.stop("API key validated");
|
|
708
908
|
const ac = getAccessControl();
|
|
709
909
|
const reinstalled = [];
|
|
710
910
|
for (const s of statuses) {
|
|
@@ -840,16 +1040,16 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
840
1040
|
p2.cancel("Setup cancelled.");
|
|
841
1041
|
process.exit(0);
|
|
842
1042
|
}
|
|
843
|
-
const
|
|
844
|
-
|
|
1043
|
+
const spinner6 = p2.spinner();
|
|
1044
|
+
spinner6.start("Validating API key...");
|
|
845
1045
|
const api = new PicaApi(apiKey);
|
|
846
1046
|
const isValid = await api.validateApiKey();
|
|
847
1047
|
if (!isValid) {
|
|
848
|
-
|
|
1048
|
+
spinner6.stop("Invalid API key");
|
|
849
1049
|
p2.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
|
|
850
1050
|
process.exit(1);
|
|
851
1051
|
}
|
|
852
|
-
|
|
1052
|
+
spinner6.stop("API key validated");
|
|
853
1053
|
writeConfig({
|
|
854
1054
|
apiKey,
|
|
855
1055
|
installedAgents: [],
|
|
@@ -1049,16 +1249,16 @@ async function promptConnectIntegrations(apiKey) {
|
|
|
1049
1249
|
p2.log.warn("Could not open browser automatically.");
|
|
1050
1250
|
p2.note(url, "Open manually");
|
|
1051
1251
|
}
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1252
|
+
const spinner6 = p2.spinner();
|
|
1253
|
+
spinner6.start("Waiting for connection... (complete auth in browser)");
|
|
1054
1254
|
try {
|
|
1055
1255
|
await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
|
|
1056
|
-
|
|
1256
|
+
spinner6.stop(`${label} connected!`);
|
|
1057
1257
|
p2.log.success(`${pc3.green("\u2713")} ${label} is now available to your AI agents`);
|
|
1058
1258
|
connected.push(platform);
|
|
1059
1259
|
first = false;
|
|
1060
1260
|
} catch (error) {
|
|
1061
|
-
|
|
1261
|
+
spinner6.stop("Connection timed out");
|
|
1062
1262
|
if (error instanceof TimeoutError) {
|
|
1063
1263
|
p2.log.warn(`No worries. Connect later with: ${pc3.cyan(`pica add ${platform}`)}`);
|
|
1064
1264
|
}
|
|
@@ -1091,16 +1291,16 @@ import pc4 from "picocolors";
|
|
|
1091
1291
|
function findPlatform(platforms, query) {
|
|
1092
1292
|
const normalizedQuery = query.toLowerCase().trim();
|
|
1093
1293
|
const exact = platforms.find(
|
|
1094
|
-
(
|
|
1294
|
+
(p6) => p6.platform.toLowerCase() === normalizedQuery || p6.name.toLowerCase() === normalizedQuery
|
|
1095
1295
|
);
|
|
1096
1296
|
if (exact) return exact;
|
|
1097
1297
|
return null;
|
|
1098
1298
|
}
|
|
1099
1299
|
function findSimilarPlatforms(platforms, query, limit = 3) {
|
|
1100
1300
|
const normalizedQuery = query.toLowerCase().trim();
|
|
1101
|
-
const scored = platforms.map((
|
|
1102
|
-
const name =
|
|
1103
|
-
const slug =
|
|
1301
|
+
const scored = platforms.map((p6) => {
|
|
1302
|
+
const name = p6.name.toLowerCase();
|
|
1303
|
+
const slug = p6.platform.toLowerCase();
|
|
1104
1304
|
let score = 0;
|
|
1105
1305
|
if (name.includes(normalizedQuery) || slug.includes(normalizedQuery)) {
|
|
1106
1306
|
score = 10;
|
|
@@ -1109,7 +1309,7 @@ function findSimilarPlatforms(platforms, query, limit = 3) {
|
|
|
1109
1309
|
} else {
|
|
1110
1310
|
score = countMatchingChars(normalizedQuery, slug);
|
|
1111
1311
|
}
|
|
1112
|
-
return { platform:
|
|
1312
|
+
return { platform: p6, score };
|
|
1113
1313
|
}).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
1114
1314
|
return scored.map((item) => item.platform);
|
|
1115
1315
|
}
|
|
@@ -1131,14 +1331,14 @@ async function connectionAddCommand(platformArg) {
|
|
|
1131
1331
|
process.exit(1);
|
|
1132
1332
|
}
|
|
1133
1333
|
const api = new PicaApi(apiKey);
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1334
|
+
const spinner6 = p3.spinner();
|
|
1335
|
+
spinner6.start("Loading platforms...");
|
|
1136
1336
|
let platforms;
|
|
1137
1337
|
try {
|
|
1138
1338
|
platforms = await api.listPlatforms();
|
|
1139
|
-
|
|
1339
|
+
spinner6.stop(`${platforms.length} platforms available`);
|
|
1140
1340
|
} catch (error) {
|
|
1141
|
-
|
|
1341
|
+
spinner6.stop("Failed to load platforms");
|
|
1142
1342
|
p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1143
1343
|
process.exit(1);
|
|
1144
1344
|
}
|
|
@@ -1236,11 +1436,11 @@ async function connectionListCommand() {
|
|
|
1236
1436
|
process.exit(1);
|
|
1237
1437
|
}
|
|
1238
1438
|
const api = new PicaApi(apiKey);
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1439
|
+
const spinner6 = p3.spinner();
|
|
1440
|
+
spinner6.start("Loading connections...");
|
|
1241
1441
|
try {
|
|
1242
1442
|
const connections = await api.listConnections();
|
|
1243
|
-
|
|
1443
|
+
spinner6.stop(`${connections.length} connection${connections.length === 1 ? "" : "s"} found`);
|
|
1244
1444
|
if (connections.length === 0) {
|
|
1245
1445
|
p3.note(
|
|
1246
1446
|
`No connections yet.
|
|
@@ -1269,7 +1469,7 @@ Add one with: ${pc4.cyan("pica connection add gmail")}`,
|
|
|
1269
1469
|
console.log();
|
|
1270
1470
|
p3.note(`Add more with: ${pc4.cyan("pica connection add <platform>")}`, "Tip");
|
|
1271
1471
|
} catch (error) {
|
|
1272
|
-
|
|
1472
|
+
spinner6.stop("Failed to load connections");
|
|
1273
1473
|
p3.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1274
1474
|
process.exit(1);
|
|
1275
1475
|
}
|
|
@@ -1297,11 +1497,11 @@ async function platformsCommand(options) {
|
|
|
1297
1497
|
process.exit(1);
|
|
1298
1498
|
}
|
|
1299
1499
|
const api = new PicaApi(apiKey);
|
|
1300
|
-
const
|
|
1301
|
-
|
|
1500
|
+
const spinner6 = p4.spinner();
|
|
1501
|
+
spinner6.start("Loading platforms...");
|
|
1302
1502
|
try {
|
|
1303
1503
|
const platforms = await api.listPlatforms();
|
|
1304
|
-
|
|
1504
|
+
spinner6.stop(`${platforms.length} platforms available`);
|
|
1305
1505
|
if (options.json) {
|
|
1306
1506
|
console.log(JSON.stringify(platforms, null, 2));
|
|
1307
1507
|
return;
|
|
@@ -1353,12 +1553,246 @@ async function platformsCommand(options) {
|
|
|
1353
1553
|
console.log();
|
|
1354
1554
|
p4.note(`Connect with: ${pc5.cyan("pica connection add <platform>")}`, "Tip");
|
|
1355
1555
|
} catch (error) {
|
|
1356
|
-
|
|
1556
|
+
spinner6.stop("Failed to load platforms");
|
|
1357
1557
|
p4.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1358
1558
|
process.exit(1);
|
|
1359
1559
|
}
|
|
1360
1560
|
}
|
|
1361
1561
|
|
|
1562
|
+
// src/commands/actions.ts
|
|
1563
|
+
import * as p5 from "@clack/prompts";
|
|
1564
|
+
import pc6 from "picocolors";
|
|
1565
|
+
function getConfig() {
|
|
1566
|
+
const apiKey = getApiKey();
|
|
1567
|
+
if (!apiKey) {
|
|
1568
|
+
p5.cancel("Not configured. Run `pica init` first.");
|
|
1569
|
+
process.exit(1);
|
|
1570
|
+
}
|
|
1571
|
+
const ac = getAccessControl();
|
|
1572
|
+
const permissions = ac.permissions || "admin";
|
|
1573
|
+
const connectionKeys = ac.connectionKeys || ["*"];
|
|
1574
|
+
const actionIds = ac.actionIds || ["*"];
|
|
1575
|
+
const knowledgeAgent = ac.knowledgeAgent || false;
|
|
1576
|
+
return { apiKey, permissions, connectionKeys, actionIds, knowledgeAgent };
|
|
1577
|
+
}
|
|
1578
|
+
async function actionsSearchCommand(platform, query, options) {
|
|
1579
|
+
p5.intro(pc6.bgCyan(pc6.black(" Pica ")));
|
|
1580
|
+
const { apiKey, permissions, actionIds, knowledgeAgent } = getConfig();
|
|
1581
|
+
const api = new PicaApi(apiKey);
|
|
1582
|
+
const spinner6 = p5.spinner();
|
|
1583
|
+
spinner6.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
|
|
1584
|
+
try {
|
|
1585
|
+
const agentType = knowledgeAgent ? "knowledge" : options.type;
|
|
1586
|
+
let actions2 = await api.searchActions(platform, query, agentType);
|
|
1587
|
+
actions2 = filterByPermissions(actions2, permissions);
|
|
1588
|
+
actions2 = actions2.filter((a) => isActionAllowed(a.systemId, actionIds));
|
|
1589
|
+
const cleanedActions = actions2.map((action) => ({
|
|
1590
|
+
actionId: action.systemId,
|
|
1591
|
+
title: action.title,
|
|
1592
|
+
method: action.method,
|
|
1593
|
+
path: action.path
|
|
1594
|
+
}));
|
|
1595
|
+
if (cleanedActions.length === 0) {
|
|
1596
|
+
spinner6.stop("No actions found");
|
|
1597
|
+
p5.note(
|
|
1598
|
+
`No actions found for platform '${platform}' matching query '${query}'.
|
|
1599
|
+
|
|
1600
|
+
Suggestions:
|
|
1601
|
+
- Try a more general query (e.g., 'list', 'get', 'search', 'create')
|
|
1602
|
+
- Verify the platform name is correct
|
|
1603
|
+
- Check available platforms with ${pc6.cyan("pica platforms")}
|
|
1604
|
+
|
|
1605
|
+
Examples of good queries:
|
|
1606
|
+
- "search contacts"
|
|
1607
|
+
- "send email"
|
|
1608
|
+
- "create customer"
|
|
1609
|
+
- "list orders"`,
|
|
1610
|
+
"No Results"
|
|
1611
|
+
);
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
spinner6.stop(
|
|
1615
|
+
`Found ${cleanedActions.length} action(s) for '${platform}' matching '${query}'`
|
|
1616
|
+
);
|
|
1617
|
+
console.log();
|
|
1618
|
+
const rows = cleanedActions.map((a) => ({
|
|
1619
|
+
method: colorMethod(a.method),
|
|
1620
|
+
title: a.title,
|
|
1621
|
+
actionId: a.actionId,
|
|
1622
|
+
path: a.path
|
|
1623
|
+
}));
|
|
1624
|
+
printTable(
|
|
1625
|
+
[
|
|
1626
|
+
{ key: "method", label: "Method" },
|
|
1627
|
+
{ key: "title", label: "Title" },
|
|
1628
|
+
{ key: "actionId", label: "Action ID", color: pc6.dim },
|
|
1629
|
+
{ key: "path", label: "Path", color: pc6.dim }
|
|
1630
|
+
],
|
|
1631
|
+
rows
|
|
1632
|
+
);
|
|
1633
|
+
console.log();
|
|
1634
|
+
p5.note(
|
|
1635
|
+
`Get details: ${pc6.cyan(`pica actions knowledge ${platform} <actionId>`)}
|
|
1636
|
+
Execute: ${pc6.cyan(`pica actions execute ${platform} <actionId> <connectionKey>`)}`,
|
|
1637
|
+
"Next Steps"
|
|
1638
|
+
);
|
|
1639
|
+
} catch (error) {
|
|
1640
|
+
spinner6.stop("Search failed");
|
|
1641
|
+
p5.cancel(
|
|
1642
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1643
|
+
);
|
|
1644
|
+
process.exit(1);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
async function actionsKnowledgeCommand(platform, actionId) {
|
|
1648
|
+
p5.intro(pc6.bgCyan(pc6.black(" Pica ")));
|
|
1649
|
+
const { apiKey, actionIds, connectionKeys } = getConfig();
|
|
1650
|
+
const api = new PicaApi(apiKey);
|
|
1651
|
+
if (!isActionAllowed(actionId, actionIds)) {
|
|
1652
|
+
p5.cancel(`Action "${actionId}" is not in the allowed action list.`);
|
|
1653
|
+
process.exit(1);
|
|
1654
|
+
}
|
|
1655
|
+
if (!connectionKeys.includes("*")) {
|
|
1656
|
+
const spinner7 = p5.spinner();
|
|
1657
|
+
spinner7.start("Checking connections...");
|
|
1658
|
+
try {
|
|
1659
|
+
const connections = await api.listConnections();
|
|
1660
|
+
const connectedPlatforms = connections.map((c) => c.platform);
|
|
1661
|
+
if (!connectedPlatforms.includes(platform)) {
|
|
1662
|
+
spinner7.stop("Platform not connected");
|
|
1663
|
+
p5.cancel(`Platform "${platform}" has no allowed connections.`);
|
|
1664
|
+
process.exit(1);
|
|
1665
|
+
}
|
|
1666
|
+
spinner7.stop("Connection verified");
|
|
1667
|
+
} catch (error) {
|
|
1668
|
+
spinner7.stop("Failed to check connections");
|
|
1669
|
+
p5.cancel(
|
|
1670
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1671
|
+
);
|
|
1672
|
+
process.exit(1);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
const spinner6 = p5.spinner();
|
|
1676
|
+
spinner6.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
|
|
1677
|
+
try {
|
|
1678
|
+
const { knowledge, method } = await api.getActionKnowledge(actionId);
|
|
1679
|
+
const knowledgeWithGuidance = buildActionKnowledgeWithGuidance(
|
|
1680
|
+
knowledge,
|
|
1681
|
+
method,
|
|
1682
|
+
platform,
|
|
1683
|
+
actionId
|
|
1684
|
+
);
|
|
1685
|
+
spinner6.stop("Knowledge loaded");
|
|
1686
|
+
console.log();
|
|
1687
|
+
console.log(knowledgeWithGuidance);
|
|
1688
|
+
console.log();
|
|
1689
|
+
p5.note(
|
|
1690
|
+
`Execute: ${pc6.cyan(`pica actions execute ${platform} ${actionId} <connectionKey>`)}`,
|
|
1691
|
+
"Next Step"
|
|
1692
|
+
);
|
|
1693
|
+
} catch (error) {
|
|
1694
|
+
spinner6.stop("Failed to load knowledge");
|
|
1695
|
+
p5.cancel(
|
|
1696
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1697
|
+
);
|
|
1698
|
+
process.exit(1);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
async function actionsExecuteCommand(platform, actionId, connectionKey, options) {
|
|
1702
|
+
p5.intro(pc6.bgCyan(pc6.black(" Pica ")));
|
|
1703
|
+
const { apiKey, permissions, actionIds, connectionKeys, knowledgeAgent } = getConfig();
|
|
1704
|
+
if (knowledgeAgent) {
|
|
1705
|
+
p5.cancel(
|
|
1706
|
+
`Action execution is disabled (knowledge-only mode).
|
|
1707
|
+
Configure with: ${pc6.cyan("pica config")}`
|
|
1708
|
+
);
|
|
1709
|
+
process.exit(1);
|
|
1710
|
+
}
|
|
1711
|
+
if (!isActionAllowed(actionId, actionIds)) {
|
|
1712
|
+
p5.cancel(`Action "${actionId}" is not in the allowed action list.`);
|
|
1713
|
+
process.exit(1);
|
|
1714
|
+
}
|
|
1715
|
+
if (!connectionKeys.includes("*") && !connectionKeys.includes(connectionKey)) {
|
|
1716
|
+
p5.cancel(`Connection key "${connectionKey}" is not allowed.`);
|
|
1717
|
+
process.exit(1);
|
|
1718
|
+
}
|
|
1719
|
+
const api = new PicaApi(apiKey);
|
|
1720
|
+
const spinner6 = p5.spinner();
|
|
1721
|
+
spinner6.start("Loading action details...");
|
|
1722
|
+
try {
|
|
1723
|
+
const actionDetails = await api.getActionDetails(actionId);
|
|
1724
|
+
if (!isMethodAllowed(actionDetails.method, permissions)) {
|
|
1725
|
+
spinner6.stop("Permission denied");
|
|
1726
|
+
p5.cancel(
|
|
1727
|
+
`Method "${actionDetails.method}" is not allowed under "${permissions}" permission level.`
|
|
1728
|
+
);
|
|
1729
|
+
process.exit(1);
|
|
1730
|
+
}
|
|
1731
|
+
spinner6.stop(`Action: ${actionDetails.title} [${actionDetails.method}]`);
|
|
1732
|
+
const data = options.data ? parseJsonArg(options.data, "--data") : void 0;
|
|
1733
|
+
const pathVariables = options.pathVars ? parseJsonArg(options.pathVars, "--path-vars") : void 0;
|
|
1734
|
+
const queryParams = options.queryParams ? parseJsonArg(options.queryParams, "--query-params") : void 0;
|
|
1735
|
+
const headers = options.headers ? parseJsonArg(options.headers, "--headers") : void 0;
|
|
1736
|
+
const execSpinner = p5.spinner();
|
|
1737
|
+
execSpinner.start("Executing action...");
|
|
1738
|
+
const result = await api.executePassthroughRequest(
|
|
1739
|
+
{
|
|
1740
|
+
platform,
|
|
1741
|
+
actionId,
|
|
1742
|
+
connectionKey,
|
|
1743
|
+
data,
|
|
1744
|
+
pathVariables,
|
|
1745
|
+
queryParams,
|
|
1746
|
+
headers,
|
|
1747
|
+
isFormData: options.formData,
|
|
1748
|
+
isFormUrlEncoded: options.formUrlEncoded
|
|
1749
|
+
},
|
|
1750
|
+
actionDetails
|
|
1751
|
+
);
|
|
1752
|
+
execSpinner.stop("Action executed successfully");
|
|
1753
|
+
console.log();
|
|
1754
|
+
console.log(pc6.dim("Request:"));
|
|
1755
|
+
console.log(
|
|
1756
|
+
pc6.dim(
|
|
1757
|
+
` ${result.requestConfig.method} ${result.requestConfig.url}`
|
|
1758
|
+
)
|
|
1759
|
+
);
|
|
1760
|
+
console.log();
|
|
1761
|
+
console.log(pc6.bold("Response:"));
|
|
1762
|
+
console.log(JSON.stringify(result.responseData, null, 2));
|
|
1763
|
+
} catch (error) {
|
|
1764
|
+
spinner6.stop("Execution failed");
|
|
1765
|
+
p5.cancel(
|
|
1766
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1767
|
+
);
|
|
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
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
function colorMethod(method) {
|
|
1780
|
+
switch (method.toUpperCase()) {
|
|
1781
|
+
case "GET":
|
|
1782
|
+
return pc6.green(method);
|
|
1783
|
+
case "POST":
|
|
1784
|
+
return pc6.yellow(method);
|
|
1785
|
+
case "PUT":
|
|
1786
|
+
return pc6.blue(method);
|
|
1787
|
+
case "PATCH":
|
|
1788
|
+
return pc6.magenta(method);
|
|
1789
|
+
case "DELETE":
|
|
1790
|
+
return pc6.red(method);
|
|
1791
|
+
default:
|
|
1792
|
+
return method;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1362
1796
|
// src/index.ts
|
|
1363
1797
|
var require2 = createRequire(import.meta.url);
|
|
1364
1798
|
var { version } = require2("../package.json");
|
|
@@ -1380,6 +1814,23 @@ connection.command("list").alias("ls").description("List your connections").acti
|
|
|
1380
1814
|
program.command("platforms").alias("p").description("List available platforms").option("-c, --category <category>", "Filter by category").option("--json", "Output as JSON").action(async (options) => {
|
|
1381
1815
|
await platformsCommand(options);
|
|
1382
1816
|
});
|
|
1817
|
+
var actions = program.command("actions").alias("a").description("Search, explore, and execute platform actions");
|
|
1818
|
+
actions.command("search <platform> <query>").description("Search for actions on a platform").option("-t, --type <type>", "Agent type: execute or knowledge (default: knowledge)").action(async (platform, query, options) => {
|
|
1819
|
+
await actionsSearchCommand(platform, query, options);
|
|
1820
|
+
});
|
|
1821
|
+
actions.command("knowledge <platform> <actionId>").alias("k").description("Get detailed documentation for an action").action(async (platform, actionId) => {
|
|
1822
|
+
await actionsKnowledgeCommand(platform, actionId);
|
|
1823
|
+
});
|
|
1824
|
+
actions.command("execute <platform> <actionId> <connectionKey>").alias("x").description("Execute an action on a connected platform").option("-d, --data <json>", "Request body as JSON").option("--path-vars <json>", "Path variables as JSON").option("--query-params <json>", "Query parameters as JSON").option("--headers <json>", "Additional headers as JSON").option("--form-data", "Send as multipart/form-data").option("--form-url-encoded", "Send as application/x-www-form-urlencoded").action(async (platform, actionId, connectionKey, options) => {
|
|
1825
|
+
await actionsExecuteCommand(platform, actionId, connectionKey, {
|
|
1826
|
+
data: options.data,
|
|
1827
|
+
pathVars: options.pathVars,
|
|
1828
|
+
queryParams: options.queryParams,
|
|
1829
|
+
headers: options.headers,
|
|
1830
|
+
formData: options.formData,
|
|
1831
|
+
formUrlEncoded: options.formUrlEncoded
|
|
1832
|
+
});
|
|
1833
|
+
});
|
|
1383
1834
|
program.command("add [platform]").description("Shortcut for: connection add").action(async (platform) => {
|
|
1384
1835
|
await connectionAddCommand(platform);
|
|
1385
1836
|
});
|