@picahq/cli 0.2.0 → 0.3.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/dist/index.d.ts +1 -0
- package/dist/index.js +1441 -0
- package/package.json +6 -2
- package/.claude/settings.local.json +0 -11
- package/.github/workflows/publish.yml +0 -29
- package/skills/pica/SKILL.md +0 -219
- package/src/commands/actions.ts +0 -385
- package/src/commands/connection.ts +0 -196
- package/src/commands/init.ts +0 -548
- package/src/commands/platforms.ts +0 -92
- package/src/index.ts +0 -140
- package/src/lib/actions.ts +0 -59
- package/src/lib/agents.ts +0 -191
- package/src/lib/api.ts +0 -191
- package/src/lib/browser.ts +0 -20
- package/src/lib/config.ts +0 -47
- package/src/lib/platforms.ts +0 -73
- package/src/lib/table.ts +0 -60
- package/src/lib/types.ts +0 -89
- package/test/all-emails.json +0 -3479
- package/test/fetch-emails.ts +0 -82
- package/tsconfig.json +0 -16
- package/tsup.config.ts +0 -10
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@picahq/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for managing Pica
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "CLI for managing Pica",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"bin",
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
6
10
|
"bin": {
|
|
7
11
|
"pica": "./bin/cli.js"
|
|
8
12
|
},
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
name: Publish Package to NPM
|
|
2
|
-
on:
|
|
3
|
-
release:
|
|
4
|
-
types: [created]
|
|
5
|
-
jobs:
|
|
6
|
-
build:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
permissions:
|
|
9
|
-
contents: read
|
|
10
|
-
id-token: write
|
|
11
|
-
steps:
|
|
12
|
-
- uses: actions/checkout@v4
|
|
13
|
-
- uses: actions/setup-node@v4
|
|
14
|
-
with:
|
|
15
|
-
node-version: '20.x'
|
|
16
|
-
registry-url: 'https://registry.npmjs.org/'
|
|
17
|
-
- name: Install latest npm
|
|
18
|
-
run: npm install -g npm@latest
|
|
19
|
-
- name: Install dependencies
|
|
20
|
-
run: |
|
|
21
|
-
if [ -f package-lock.json ]; then
|
|
22
|
-
npm ci
|
|
23
|
-
else
|
|
24
|
-
npm install
|
|
25
|
-
fi
|
|
26
|
-
- name: Build
|
|
27
|
-
run: npm run build
|
|
28
|
-
- name: Publish
|
|
29
|
-
run: npm publish --access public
|
package/skills/pica/SKILL.md
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pica
|
|
3
|
-
description: Interact with third-party platforms (Gmail, Slack, HubSpot, Stripe, etc.) via Pica. Use when the user wants to search for available API actions, read API documentation, execute API calls, manage connections, or do anything involving third-party integrations.
|
|
4
|
-
triggers:
|
|
5
|
-
- "search actions"
|
|
6
|
-
- "find actions"
|
|
7
|
-
- "execute action"
|
|
8
|
-
- "run action"
|
|
9
|
-
- "list connections"
|
|
10
|
-
- "add connection"
|
|
11
|
-
- "list platforms"
|
|
12
|
-
- "pica"
|
|
13
|
-
- "integration"
|
|
14
|
-
- "send email"
|
|
15
|
-
- "check calendar"
|
|
16
|
-
- "post to slack"
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
# Pica
|
|
20
|
-
|
|
21
|
-
Pica gives you access to 200+ third-party platforms (Gmail, Slack, HubSpot, Stripe, Shopify, etc.) through a single interface. It handles auth, rate limiting, and retries so you just call the action you need.
|
|
22
|
-
|
|
23
|
-
## Two interfaces: MCP and CLI
|
|
24
|
-
|
|
25
|
-
| Interface | Best for |
|
|
26
|
-
|-----------|----------|
|
|
27
|
-
| **MCP tools** | AI agents doing work (search, inspect, execute actions) |
|
|
28
|
-
| **CLI** (`pica`) | Humans doing setup, OAuth, browsing |
|
|
29
|
-
|
|
30
|
-
**Rule of thumb:** If you're doing the work, use MCP. If a human needs to interact (OAuth, setup), use CLI.
|
|
31
|
-
|
|
32
|
-
## MCP tools
|
|
33
|
-
|
|
34
|
-
The Pica MCP is your primary interface. It gives you structured JSON in/out with no parsing overhead.
|
|
35
|
-
|
|
36
|
-
| Tool | What it does |
|
|
37
|
-
|------|-------------|
|
|
38
|
-
| `mcp__pica__list_pica_integrations` | List all platforms and active connections |
|
|
39
|
-
| `mcp__pica__search_pica_platform_actions` | Search for actions on a platform |
|
|
40
|
-
| `mcp__pica__get_pica_action_knowledge` | Get full API docs for an action (call before execute) |
|
|
41
|
-
| `mcp__pica__execute_pica_action` | Execute an action on a connected platform |
|
|
42
|
-
|
|
43
|
-
### Standard workflow
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
1. list_pica_integrations -> get connection keys and platform names
|
|
47
|
-
2. search_pica_platform_actions -> find the right action
|
|
48
|
-
3. get_pica_action_knowledge -> read the docs (REQUIRED before execute)
|
|
49
|
-
4. execute_pica_action -> do the thing
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Step 1: List connections and platforms
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
mcp__pica__list_pica_integrations()
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Returns all active connections (with connection keys) and available platforms. Always check this first.
|
|
59
|
-
|
|
60
|
-
### Step 2: Search for the right action
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
mcp__pica__search_pica_platform_actions({
|
|
64
|
-
platform: "gmail",
|
|
65
|
-
query: "send email",
|
|
66
|
-
agentType: "execute"
|
|
67
|
-
})
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
- `agentType: "execute"` when you intend to run the action
|
|
71
|
-
- `agentType: "knowledge"` when you want to understand the API or write code
|
|
72
|
-
- Platform names are kebab-case: `gmail`, `hubspot`, `google-calendar`, `ship-station`
|
|
73
|
-
- Get the exact platform name from `list_pica_integrations`
|
|
74
|
-
|
|
75
|
-
### Step 3: Get action knowledge (required before execute)
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
mcp__pica__get_pica_action_knowledge({
|
|
79
|
-
actionId: "conn_mod_def::ABC123::XYZ789",
|
|
80
|
-
platform: "gmail"
|
|
81
|
-
})
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Returns parameters, request/response schemas, caveats, and examples. You MUST call this before executing.
|
|
85
|
-
|
|
86
|
-
### Step 4: Execute
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
mcp__pica__execute_pica_action({
|
|
90
|
-
actionId: "conn_mod_def::ABC123::XYZ789",
|
|
91
|
-
connectionKey: "live::gmail::default::abc123",
|
|
92
|
-
platform: "gmail",
|
|
93
|
-
data: { to: "someone@example.com", subject: "Hello", body: "Hi there" },
|
|
94
|
-
pathVariables: { userId: "me" }
|
|
95
|
-
})
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Pass: `connectionKey` from step 1, `actionId` from step 2, parameters from step 3.
|
|
99
|
-
|
|
100
|
-
## CLI reference
|
|
101
|
-
|
|
102
|
-
For setup, OAuth, and human-facing tasks.
|
|
103
|
-
|
|
104
|
-
### Setup: `pica init`
|
|
105
|
-
|
|
106
|
-
First run prompts for API key, validates it, and installs the MCP into your AI agents.
|
|
107
|
-
|
|
108
|
-
Re-running `pica init` after setup shows a status dashboard:
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
Current Setup
|
|
112
|
-
──────────────────────────────────────────
|
|
113
|
-
API Key: sk_test_...9j-Y
|
|
114
|
-
Config: ~/.pica/config.json
|
|
115
|
-
|
|
116
|
-
Agent Global Project
|
|
117
|
-
────────────── ────── ───────
|
|
118
|
-
Claude Code ● yes ● yes
|
|
119
|
-
Claude Desktop ● yes -
|
|
120
|
-
Cursor ○ no ○ no
|
|
121
|
-
Windsurf - -
|
|
122
|
-
|
|
123
|
-
- = not detected on this machine
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Then offers targeted actions (only relevant options shown):
|
|
127
|
-
|
|
128
|
-
| Action | What it does |
|
|
129
|
-
|--------|-------------|
|
|
130
|
-
| Update API key | Validates new key, re-installs MCP to all agents that have it |
|
|
131
|
-
| Install MCP to more agents | Shows only detected agents missing the MCP |
|
|
132
|
-
| Install MCP for this project | Creates project-level configs in cwd |
|
|
133
|
-
| Start fresh | Full setup from scratch |
|
|
134
|
-
|
|
135
|
-
Flags: `-y` (skip confirmations), `-g` (global install), `-p` (project install).
|
|
136
|
-
|
|
137
|
-
### Other commands
|
|
138
|
-
|
|
139
|
-
| Command | Description |
|
|
140
|
-
|---------|-------------|
|
|
141
|
-
| `pica add <platform>` | Connect a platform via OAuth (opens browser) |
|
|
142
|
-
| `pica list` | List connections with keys |
|
|
143
|
-
| `pica platforms` | Browse all 200+ platforms |
|
|
144
|
-
| `pica search <platform> [query]` | Search for actions |
|
|
145
|
-
| `pica actions knowledge <id>` | Get API docs for an action |
|
|
146
|
-
| `pica exec <id>` | Execute an action |
|
|
147
|
-
|
|
148
|
-
All commands support `--json` for machine-readable output.
|
|
149
|
-
|
|
150
|
-
### Aliases
|
|
151
|
-
|
|
152
|
-
`pica ls` = list, `pica p` = platforms, `pica a search` = actions search, `pica a k` = actions knowledge, `pica a x` = actions execute.
|
|
153
|
-
|
|
154
|
-
### Exec flags
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
pica exec <actionId> \
|
|
158
|
-
-c <connectionKey> \
|
|
159
|
-
-d '{"key": "value"}' \
|
|
160
|
-
-p pathVar=value \
|
|
161
|
-
-q queryParam=value \
|
|
162
|
-
--json
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
## Key concepts
|
|
166
|
-
|
|
167
|
-
- **Connection key**: Identifies which authenticated connection to use. Format: `live::gmail::default::abc123`. Get from `list_pica_integrations` or `pica list`.
|
|
168
|
-
- **Action ID**: Identifies a specific API action. Starts with `conn_mod_def::`. Get from search results.
|
|
169
|
-
- **Platform name**: Kebab-case identifier (`gmail`, `hubspot`, `google-calendar`, `ship-station`). Get from `list_pica_integrations`.
|
|
170
|
-
- **Passthrough proxy**: All actions route through Pica's proxy which injects auth, handles rate limits, and normalizes responses. You never touch raw OAuth tokens.
|
|
171
|
-
- **Pagination**: Some actions return `nextPageToken` or similar. Pass it back in subsequent requests to page through results.
|
|
172
|
-
|
|
173
|
-
## MCP installation details
|
|
174
|
-
|
|
175
|
-
`pica init` writes MCP configs here:
|
|
176
|
-
|
|
177
|
-
| Agent | Global | Project |
|
|
178
|
-
|-------|--------|---------|
|
|
179
|
-
| Claude Code | `~/.claude.json` | `.mcp.json` |
|
|
180
|
-
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` | n/a |
|
|
181
|
-
| Cursor | `~/.cursor/mcp.json` | `.cursor/mcp.json` |
|
|
182
|
-
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | n/a |
|
|
183
|
-
|
|
184
|
-
Global = available everywhere. Project = committed to repo, shared with team (each person needs their own API key).
|
|
185
|
-
|
|
186
|
-
If MCP tools are not available, install and init:
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
cd /Users/moe/projects/one/connection-cli
|
|
190
|
-
npm install && npm run build && npm link
|
|
191
|
-
pica init
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
## Common patterns
|
|
195
|
-
|
|
196
|
-
### Send an email
|
|
197
|
-
1. Search: `platform: "gmail", query: "send email"`
|
|
198
|
-
2. Knowledge: get the action docs
|
|
199
|
-
3. Execute with `data: { to, subject, body, connectionKey }`
|
|
200
|
-
|
|
201
|
-
### Read emails
|
|
202
|
-
1. Search: `platform: "gmail", query: "get emails"`
|
|
203
|
-
2. Knowledge: understand pagination (numberOfEmails, pageToken)
|
|
204
|
-
3. Execute with `data: { connectionKey, numberOfEmails, label, query }`
|
|
205
|
-
|
|
206
|
-
### Post to Slack
|
|
207
|
-
1. Search: `platform: "slack", query: "post message"`
|
|
208
|
-
2. Knowledge: get channel ID format
|
|
209
|
-
3. Execute with `data: { channel, text }`
|
|
210
|
-
|
|
211
|
-
### CRM operations
|
|
212
|
-
1. Search: `platform: "hubspot"` or `"attio"`, query: `"create contact"` / `"list contacts"`
|
|
213
|
-
2. Knowledge: understand required fields
|
|
214
|
-
3. Execute with the right data shape
|
|
215
|
-
|
|
216
|
-
### Calendar
|
|
217
|
-
1. Search: `platform: "google-calendar", query: "list events"`
|
|
218
|
-
2. Knowledge: understand time format, calendar ID
|
|
219
|
-
3. Execute with date range parameters
|
package/src/commands/actions.ts
DELETED
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
import * as p from '@clack/prompts';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
import { getApiKey } from '../lib/config.js';
|
|
4
|
-
import { PicaApi } from '../lib/api.js';
|
|
5
|
-
import { extractPathVariables, resolveTemplateVariables } from '../lib/actions.js';
|
|
6
|
-
import { printTable } from '../lib/table.js';
|
|
7
|
-
import type { PlatformAction, ActionKnowledge } from '../lib/types.js';
|
|
8
|
-
|
|
9
|
-
function getApi(): PicaApi {
|
|
10
|
-
const apiKey = getApiKey();
|
|
11
|
-
if (!apiKey) {
|
|
12
|
-
p.cancel('Not configured. Run `pica init` first.');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
return new PicaApi(apiKey);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function colorMethod(method: string): string {
|
|
19
|
-
const m = method.toUpperCase();
|
|
20
|
-
switch (m) {
|
|
21
|
-
case 'GET': return pc.green(m);
|
|
22
|
-
case 'POST': return pc.yellow(m);
|
|
23
|
-
case 'PUT': return pc.blue(m);
|
|
24
|
-
case 'PATCH': return pc.cyan(m);
|
|
25
|
-
case 'DELETE': return pc.red(m);
|
|
26
|
-
default: return pc.dim(m);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function padMethod(method: string): string {
|
|
31
|
-
return method.toUpperCase().padEnd(7);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// --- Search ---
|
|
35
|
-
|
|
36
|
-
export async function actionsSearchCommand(
|
|
37
|
-
platform: string,
|
|
38
|
-
query?: string,
|
|
39
|
-
options: { json?: boolean; limit?: string } = {}
|
|
40
|
-
): Promise<void> {
|
|
41
|
-
const api = getApi();
|
|
42
|
-
|
|
43
|
-
if (!query) {
|
|
44
|
-
const input = await p.text({
|
|
45
|
-
message: `Search actions on ${pc.cyan(platform)}:`,
|
|
46
|
-
placeholder: 'send email, create contact, list orders...',
|
|
47
|
-
});
|
|
48
|
-
if (p.isCancel(input)) {
|
|
49
|
-
p.cancel('Cancelled.');
|
|
50
|
-
process.exit(0);
|
|
51
|
-
}
|
|
52
|
-
query = input;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const spinner = p.spinner();
|
|
56
|
-
spinner.start(`Searching ${platform} actions...`);
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const limit = options.limit ? parseInt(options.limit, 10) : 10;
|
|
60
|
-
const actions = await api.searchActions(platform, query, limit);
|
|
61
|
-
spinner.stop(`${actions.length} action${actions.length === 1 ? '' : 's'} found`);
|
|
62
|
-
|
|
63
|
-
if (options.json) {
|
|
64
|
-
console.log(JSON.stringify(actions, null, 2));
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (actions.length === 0) {
|
|
69
|
-
p.note(`No actions found for "${query}" on ${platform}.`, 'No Results');
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
console.log();
|
|
74
|
-
|
|
75
|
-
const rows = actions.map(action => ({
|
|
76
|
-
method: colorMethod(padMethod(action.method)),
|
|
77
|
-
path: action.path,
|
|
78
|
-
title: action.title,
|
|
79
|
-
id: action._id,
|
|
80
|
-
}));
|
|
81
|
-
|
|
82
|
-
printTable(
|
|
83
|
-
[
|
|
84
|
-
{ key: 'method', label: 'Method' },
|
|
85
|
-
{ key: 'title', label: 'Title' },
|
|
86
|
-
{ key: 'path', label: 'Path', color: pc.dim },
|
|
87
|
-
{ key: 'id', label: 'Action ID', color: pc.dim },
|
|
88
|
-
],
|
|
89
|
-
rows
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
console.log();
|
|
93
|
-
p.note(
|
|
94
|
-
`Get docs: ${pc.cyan('pica actions knowledge <actionId>')}\n` +
|
|
95
|
-
`Execute: ${pc.cyan('pica actions execute <actionId>')}`,
|
|
96
|
-
'Next Steps'
|
|
97
|
-
);
|
|
98
|
-
} catch (error) {
|
|
99
|
-
spinner.stop('Search failed');
|
|
100
|
-
p.cancel(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// --- Knowledge ---
|
|
106
|
-
|
|
107
|
-
export async function actionsKnowledgeCommand(
|
|
108
|
-
actionId: string,
|
|
109
|
-
options: { json?: boolean; full?: boolean } = {}
|
|
110
|
-
): Promise<void> {
|
|
111
|
-
const api = getApi();
|
|
112
|
-
|
|
113
|
-
const spinner = p.spinner();
|
|
114
|
-
spinner.start('Loading action knowledge...');
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
const knowledge = await api.getActionKnowledge(actionId);
|
|
118
|
-
spinner.stop('Action knowledge loaded');
|
|
119
|
-
|
|
120
|
-
if (!knowledge) {
|
|
121
|
-
p.cancel(`No knowledge found for action: ${actionId}`);
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (options.json) {
|
|
126
|
-
console.log(JSON.stringify(knowledge, null, 2));
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
printKnowledge(knowledge, options.full);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
spinner.stop('Failed to load knowledge');
|
|
133
|
-
p.cancel(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function printKnowledge(k: ActionKnowledge, full?: boolean): void {
|
|
139
|
-
console.log();
|
|
140
|
-
console.log(pc.bold(` ${k.title}`));
|
|
141
|
-
console.log();
|
|
142
|
-
console.log(` Platform: ${pc.cyan(k.connectionPlatform)}`);
|
|
143
|
-
console.log(` Method: ${colorMethod(k.method)}`);
|
|
144
|
-
console.log(` Path: ${k.path}`);
|
|
145
|
-
console.log(` Base URL: ${pc.dim(k.baseUrl)}`);
|
|
146
|
-
|
|
147
|
-
if (k.tags?.length) {
|
|
148
|
-
console.log(` Tags: ${k.tags.map(t => pc.dim(t)).join(', ')}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const pathVars = extractPathVariables(k.path);
|
|
152
|
-
if (pathVars.length > 0) {
|
|
153
|
-
console.log(` Path Vars: ${pathVars.map(v => pc.yellow(`{{${v}}}`)).join(', ')}`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
console.log(` Active: ${k.active ? pc.green('yes') : pc.red('no')}`);
|
|
157
|
-
console.log(` ID: ${pc.dim(k._id)}`);
|
|
158
|
-
|
|
159
|
-
if (k.knowledge) {
|
|
160
|
-
console.log();
|
|
161
|
-
console.log(pc.bold(' API Documentation'));
|
|
162
|
-
console.log(pc.dim(' ' + '─'.repeat(40)));
|
|
163
|
-
console.log();
|
|
164
|
-
|
|
165
|
-
const lines = k.knowledge.split('\n');
|
|
166
|
-
const displayLines = full ? lines : lines.slice(0, 50);
|
|
167
|
-
|
|
168
|
-
for (const line of displayLines) {
|
|
169
|
-
console.log(` ${line}`);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (!full && lines.length > 50) {
|
|
173
|
-
console.log();
|
|
174
|
-
console.log(pc.dim(` ... ${lines.length - 50} more lines. Use --full to see all.`));
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
console.log();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// --- Execute ---
|
|
182
|
-
|
|
183
|
-
export async function actionsExecuteCommand(
|
|
184
|
-
actionId: string,
|
|
185
|
-
options: {
|
|
186
|
-
json?: boolean;
|
|
187
|
-
connection?: string;
|
|
188
|
-
data?: string;
|
|
189
|
-
pathVar?: string[];
|
|
190
|
-
query?: string[];
|
|
191
|
-
formData?: boolean;
|
|
192
|
-
formUrlencoded?: boolean;
|
|
193
|
-
} = {}
|
|
194
|
-
): Promise<void> {
|
|
195
|
-
const api = getApi();
|
|
196
|
-
|
|
197
|
-
// 1. Fetch action knowledge to get method + path
|
|
198
|
-
const spinner = p.spinner();
|
|
199
|
-
spinner.start('Loading action details...');
|
|
200
|
-
|
|
201
|
-
let knowledge: ActionKnowledge;
|
|
202
|
-
try {
|
|
203
|
-
const k = await api.getActionKnowledge(actionId);
|
|
204
|
-
if (!k) {
|
|
205
|
-
spinner.stop('Action not found');
|
|
206
|
-
p.cancel(`No action found for: ${actionId}`);
|
|
207
|
-
process.exit(1);
|
|
208
|
-
}
|
|
209
|
-
knowledge = k;
|
|
210
|
-
spinner.stop(`${colorMethod(knowledge.method)} ${knowledge.path}`);
|
|
211
|
-
} catch (error) {
|
|
212
|
-
spinner.stop('Failed to load action');
|
|
213
|
-
p.cancel(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
214
|
-
process.exit(1);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// 2. Resolve connection key
|
|
218
|
-
let connectionKey = options.connection;
|
|
219
|
-
if (!connectionKey) {
|
|
220
|
-
connectionKey = await resolveConnection(api, knowledge.connectionPlatform);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// 3. Resolve path variables
|
|
224
|
-
const pathVarMap = parseKeyValuePairs(options.pathVar || []);
|
|
225
|
-
const pathVars = extractPathVariables(knowledge.path);
|
|
226
|
-
for (const v of pathVars) {
|
|
227
|
-
if (!pathVarMap[v]) {
|
|
228
|
-
const input = await p.text({
|
|
229
|
-
message: `Value for path variable ${pc.yellow(`{{${v}}}`)}:`,
|
|
230
|
-
validate: (val) => val.trim() ? undefined : 'Value is required',
|
|
231
|
-
});
|
|
232
|
-
if (p.isCancel(input)) {
|
|
233
|
-
p.cancel('Cancelled.');
|
|
234
|
-
process.exit(0);
|
|
235
|
-
}
|
|
236
|
-
pathVarMap[v] = input;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// 4. Resolve body data
|
|
241
|
-
let bodyData: Record<string, unknown> = {};
|
|
242
|
-
if (options.data) {
|
|
243
|
-
try {
|
|
244
|
-
bodyData = JSON.parse(options.data);
|
|
245
|
-
} catch {
|
|
246
|
-
p.cancel('Invalid JSON in --data flag.');
|
|
247
|
-
process.exit(1);
|
|
248
|
-
}
|
|
249
|
-
} else if (!['GET', 'DELETE', 'HEAD'].includes(knowledge.method.toUpperCase())) {
|
|
250
|
-
const input = await p.text({
|
|
251
|
-
message: 'Request body (JSON):',
|
|
252
|
-
placeholder: '{"key": "value"} or leave empty',
|
|
253
|
-
});
|
|
254
|
-
if (p.isCancel(input)) {
|
|
255
|
-
p.cancel('Cancelled.');
|
|
256
|
-
process.exit(0);
|
|
257
|
-
}
|
|
258
|
-
if (input.trim()) {
|
|
259
|
-
try {
|
|
260
|
-
bodyData = JSON.parse(input);
|
|
261
|
-
} catch {
|
|
262
|
-
p.cancel('Invalid JSON.');
|
|
263
|
-
process.exit(1);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// 5. Resolve query params
|
|
269
|
-
const queryParams = parseKeyValuePairs(options.query || []);
|
|
270
|
-
|
|
271
|
-
// 6. Resolve template variables in path
|
|
272
|
-
const { resolvedPath, remainingData } = resolveTemplateVariables(
|
|
273
|
-
knowledge.path,
|
|
274
|
-
bodyData,
|
|
275
|
-
pathVarMap
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
// 7. Show summary
|
|
279
|
-
console.log();
|
|
280
|
-
console.log(pc.bold(' Request Summary'));
|
|
281
|
-
console.log(` ${colorMethod(knowledge.method)} ${knowledge.baseUrl}${resolvedPath}`);
|
|
282
|
-
console.log(` Connection: ${pc.dim(connectionKey)}`);
|
|
283
|
-
if (Object.keys(remainingData).length > 0) {
|
|
284
|
-
console.log(` Body: ${pc.dim(JSON.stringify(remainingData))}`);
|
|
285
|
-
}
|
|
286
|
-
if (Object.keys(queryParams).length > 0) {
|
|
287
|
-
console.log(` Query: ${pc.dim(JSON.stringify(queryParams))}`);
|
|
288
|
-
}
|
|
289
|
-
console.log();
|
|
290
|
-
|
|
291
|
-
// 8. Execute
|
|
292
|
-
const execSpinner = p.spinner();
|
|
293
|
-
execSpinner.start('Executing...');
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
const result = await api.executeAction({
|
|
297
|
-
method: knowledge.method,
|
|
298
|
-
path: resolvedPath,
|
|
299
|
-
actionId,
|
|
300
|
-
connectionKey,
|
|
301
|
-
data: Object.keys(remainingData).length > 0 ? remainingData : undefined,
|
|
302
|
-
queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
303
|
-
isFormData: options.formData,
|
|
304
|
-
isFormUrlEncoded: options.formUrlencoded,
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
execSpinner.stop(pc.green('Success'));
|
|
308
|
-
|
|
309
|
-
if (options.json) {
|
|
310
|
-
console.log(JSON.stringify(result, null, 2));
|
|
311
|
-
} else {
|
|
312
|
-
console.log();
|
|
313
|
-
console.log(pc.bold(' Response'));
|
|
314
|
-
console.log(pc.dim(' ' + '─'.repeat(40)));
|
|
315
|
-
console.log();
|
|
316
|
-
console.log(formatResponse(result));
|
|
317
|
-
console.log();
|
|
318
|
-
}
|
|
319
|
-
} catch (error) {
|
|
320
|
-
execSpinner.stop(pc.red('Failed'));
|
|
321
|
-
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
322
|
-
p.cancel(`Execution failed: ${msg}`);
|
|
323
|
-
process.exit(1);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// --- Helpers ---
|
|
328
|
-
|
|
329
|
-
async function resolveConnection(api: PicaApi, platform: string): Promise<string> {
|
|
330
|
-
const spinner = p.spinner();
|
|
331
|
-
spinner.start('Loading connections...');
|
|
332
|
-
|
|
333
|
-
const connections = await api.listConnections();
|
|
334
|
-
const matching = connections.filter(
|
|
335
|
-
c => c.platform.toLowerCase() === platform.toLowerCase()
|
|
336
|
-
);
|
|
337
|
-
spinner.stop(`${matching.length} ${platform} connection${matching.length === 1 ? '' : 's'} found`);
|
|
338
|
-
|
|
339
|
-
if (matching.length === 0) {
|
|
340
|
-
p.cancel(
|
|
341
|
-
`No ${platform} connections found.\n\n` +
|
|
342
|
-
`Add one with: ${pc.cyan(`pica connection add ${platform}`)}`
|
|
343
|
-
);
|
|
344
|
-
process.exit(1);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (matching.length === 1) {
|
|
348
|
-
p.log.info(`Using connection: ${pc.dim(matching[0].key)}`);
|
|
349
|
-
return matching[0].key;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const selected = await p.select({
|
|
353
|
-
message: `Multiple ${platform} connections found. Which one?`,
|
|
354
|
-
options: matching.map(c => ({
|
|
355
|
-
value: c.key,
|
|
356
|
-
label: `${c.key}`,
|
|
357
|
-
hint: c.state,
|
|
358
|
-
})),
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
if (p.isCancel(selected)) {
|
|
362
|
-
p.cancel('Cancelled.');
|
|
363
|
-
process.exit(0);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return selected as string;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function parseKeyValuePairs(pairs: string[]): Record<string, string> {
|
|
370
|
-
const result: Record<string, string> = {};
|
|
371
|
-
for (const pair of pairs) {
|
|
372
|
-
const eqIdx = pair.indexOf('=');
|
|
373
|
-
if (eqIdx === -1) continue;
|
|
374
|
-
const key = pair.slice(0, eqIdx);
|
|
375
|
-
const value = pair.slice(eqIdx + 1);
|
|
376
|
-
result[key] = value;
|
|
377
|
-
}
|
|
378
|
-
return result;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function formatResponse(data: unknown, indent = 2): string {
|
|
382
|
-
const prefix = ' '.repeat(indent);
|
|
383
|
-
const json = JSON.stringify(data, null, 2);
|
|
384
|
-
return json.split('\n').map(line => `${prefix}${line}`).join('\n');
|
|
385
|
-
}
|