@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/src/index.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import { initCommand } from './commands/init.js';
|
|
5
|
-
import { connectionAddCommand, connectionListCommand } from './commands/connection.js';
|
|
6
|
-
import { platformsCommand } from './commands/platforms.js';
|
|
7
|
-
import { actionsSearchCommand, actionsKnowledgeCommand, actionsExecuteCommand } from './commands/actions.js';
|
|
8
|
-
|
|
9
|
-
const program = new Command();
|
|
10
|
-
|
|
11
|
-
program
|
|
12
|
-
.name('pica')
|
|
13
|
-
.description('CLI for managing Pica integrations')
|
|
14
|
-
.version('0.1.0');
|
|
15
|
-
|
|
16
|
-
program
|
|
17
|
-
.command('init')
|
|
18
|
-
.description('Set up Pica and install MCP to your AI agents')
|
|
19
|
-
.option('-y, --yes', 'Skip confirmations')
|
|
20
|
-
.option('-g, --global', 'Install MCP globally (available in all projects)')
|
|
21
|
-
.option('-p, --project', 'Install MCP for this project only (creates .mcp.json)')
|
|
22
|
-
.action(async (options) => {
|
|
23
|
-
await initCommand(options);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const connection = program
|
|
27
|
-
.command('connection')
|
|
28
|
-
.description('Manage connections');
|
|
29
|
-
|
|
30
|
-
connection
|
|
31
|
-
.command('add [platform]')
|
|
32
|
-
.alias('a')
|
|
33
|
-
.description('Add a new connection')
|
|
34
|
-
.action(async (platform) => {
|
|
35
|
-
await connectionAddCommand(platform);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
connection
|
|
39
|
-
.command('list')
|
|
40
|
-
.alias('ls')
|
|
41
|
-
.description('List your connections')
|
|
42
|
-
.action(async () => {
|
|
43
|
-
await connectionListCommand();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
program
|
|
47
|
-
.command('platforms')
|
|
48
|
-
.alias('p')
|
|
49
|
-
.description('List available platforms')
|
|
50
|
-
.option('-c, --category <category>', 'Filter by category')
|
|
51
|
-
.option('--json', 'Output as JSON')
|
|
52
|
-
.action(async (options) => {
|
|
53
|
-
await platformsCommand(options);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Shortcuts
|
|
57
|
-
program
|
|
58
|
-
.command('add [platform]')
|
|
59
|
-
.description('Shortcut for: connection add')
|
|
60
|
-
.action(async (platform) => {
|
|
61
|
-
await connectionAddCommand(platform);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
program
|
|
65
|
-
.command('list')
|
|
66
|
-
.alias('ls')
|
|
67
|
-
.description('Shortcut for: connection list')
|
|
68
|
-
.action(async () => {
|
|
69
|
-
await connectionListCommand();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Actions command group
|
|
73
|
-
const actions = program
|
|
74
|
-
.command('actions')
|
|
75
|
-
.alias('a')
|
|
76
|
-
.description('Discover and execute platform actions');
|
|
77
|
-
|
|
78
|
-
actions
|
|
79
|
-
.command('search <platform> [query]')
|
|
80
|
-
.description('Search actions on a platform')
|
|
81
|
-
.option('--json', 'Output as JSON')
|
|
82
|
-
.option('-l, --limit <limit>', 'Max results', '10')
|
|
83
|
-
.action(async (platform: string, query: string | undefined, options: { json?: boolean; limit?: string }) => {
|
|
84
|
-
await actionsSearchCommand(platform, query, options);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
actions
|
|
88
|
-
.command('knowledge <actionId>')
|
|
89
|
-
.alias('k')
|
|
90
|
-
.description('Get API docs for an action')
|
|
91
|
-
.option('--json', 'Output as JSON')
|
|
92
|
-
.option('--full', 'Show full knowledge (no truncation)')
|
|
93
|
-
.action(async (actionId: string, options: { json?: boolean; full?: boolean }) => {
|
|
94
|
-
await actionsKnowledgeCommand(actionId, options);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
actions
|
|
98
|
-
.command('execute <actionId>')
|
|
99
|
-
.alias('x')
|
|
100
|
-
.description('Execute an action')
|
|
101
|
-
.option('-c, --connection <key>', 'Connection key to use')
|
|
102
|
-
.option('-d, --data <json>', 'Request body as JSON')
|
|
103
|
-
.option('-p, --path-var <key=value...>', 'Path variable', collectValues)
|
|
104
|
-
.option('-q, --query <key=value...>', 'Query parameter', collectValues)
|
|
105
|
-
.option('--form-data', 'Send as multipart/form-data')
|
|
106
|
-
.option('--form-urlencoded', 'Send as application/x-www-form-urlencoded')
|
|
107
|
-
.option('--json', 'Output as JSON')
|
|
108
|
-
.action(async (actionId: string, options) => {
|
|
109
|
-
await actionsExecuteCommand(actionId, options);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Top-level shortcuts
|
|
113
|
-
program
|
|
114
|
-
.command('search <platform> [query]')
|
|
115
|
-
.description('Shortcut for: actions search')
|
|
116
|
-
.option('--json', 'Output as JSON')
|
|
117
|
-
.option('-l, --limit <limit>', 'Max results', '10')
|
|
118
|
-
.action(async (platform: string, query: string | undefined, options: { json?: boolean; limit?: string }) => {
|
|
119
|
-
await actionsSearchCommand(platform, query, options);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
program
|
|
123
|
-
.command('exec <actionId>')
|
|
124
|
-
.description('Shortcut for: actions execute')
|
|
125
|
-
.option('-c, --connection <key>', 'Connection key to use')
|
|
126
|
-
.option('-d, --data <json>', 'Request body as JSON')
|
|
127
|
-
.option('-p, --path-var <key=value...>', 'Path variable', collectValues)
|
|
128
|
-
.option('-q, --query <key=value...>', 'Query parameter', collectValues)
|
|
129
|
-
.option('--form-data', 'Send as multipart/form-data')
|
|
130
|
-
.option('--form-urlencoded', 'Send as application/x-www-form-urlencoded')
|
|
131
|
-
.option('--json', 'Output as JSON')
|
|
132
|
-
.action(async (actionId: string, options) => {
|
|
133
|
-
await actionsExecuteCommand(actionId, options);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
function collectValues(value: string, previous: string[]): string[] {
|
|
137
|
-
return (previous || []).concat([value]);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
program.parse();
|
package/src/lib/actions.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
const ACTION_ID_PREFIX = 'conn_mod_def::';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Ensure action ID has the required prefix.
|
|
5
|
-
*/
|
|
6
|
-
export function normalizeActionId(id: string): string {
|
|
7
|
-
if (id.startsWith(ACTION_ID_PREFIX)) return id;
|
|
8
|
-
return `${ACTION_ID_PREFIX}${id}`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Extract {{variable}} names from a path string.
|
|
13
|
-
*/
|
|
14
|
-
export function extractPathVariables(path: string): string[] {
|
|
15
|
-
const matches = path.match(/\{\{(\w+)\}\}/g);
|
|
16
|
-
if (!matches) return [];
|
|
17
|
-
return matches.map(m => m.replace(/\{\{|\}\}/g, ''));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Replace {{variable}} placeholders in a path with provided values.
|
|
22
|
-
*/
|
|
23
|
-
export function replacePathVariables(
|
|
24
|
-
path: string,
|
|
25
|
-
vars: Record<string, string>
|
|
26
|
-
): string {
|
|
27
|
-
let result = path;
|
|
28
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
29
|
-
result = result.replace(`{{${key}}}`, encodeURIComponent(value));
|
|
30
|
-
}
|
|
31
|
-
return result;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Resolve path variables from data object and pathVars overrides.
|
|
36
|
-
* Returns the resolved path and the remaining data (with used keys removed).
|
|
37
|
-
*/
|
|
38
|
-
export function resolveTemplateVariables(
|
|
39
|
-
path: string,
|
|
40
|
-
data: Record<string, unknown>,
|
|
41
|
-
pathVars: Record<string, string>
|
|
42
|
-
): { resolvedPath: string; remainingData: Record<string, unknown> } {
|
|
43
|
-
const variables = extractPathVariables(path);
|
|
44
|
-
const merged: Record<string, string> = { ...pathVars };
|
|
45
|
-
const remaining = { ...data };
|
|
46
|
-
|
|
47
|
-
// Fill from data if not already provided in pathVars
|
|
48
|
-
for (const v of variables) {
|
|
49
|
-
if (!merged[v] && data[v] != null) {
|
|
50
|
-
merged[v] = String(data[v]);
|
|
51
|
-
delete remaining[v];
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
resolvedPath: replacePathVariables(path, merged),
|
|
57
|
-
remainingData: remaining,
|
|
58
|
-
};
|
|
59
|
-
}
|
package/src/lib/agents.ts
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import type { Agent } from './types.js';
|
|
5
|
-
|
|
6
|
-
export type InstallScope = 'global' | 'project';
|
|
7
|
-
|
|
8
|
-
function expandPath(p: string): string {
|
|
9
|
-
if (p.startsWith('~/')) {
|
|
10
|
-
return path.join(os.homedir(), p.slice(2));
|
|
11
|
-
}
|
|
12
|
-
return p;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function getClaudeDesktopConfigPath(): string {
|
|
16
|
-
switch (process.platform) {
|
|
17
|
-
case 'darwin':
|
|
18
|
-
return '~/Library/Application Support/Claude/claude_desktop_config.json';
|
|
19
|
-
case 'win32':
|
|
20
|
-
return path.join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json');
|
|
21
|
-
default:
|
|
22
|
-
return '~/.config/Claude/claude_desktop_config.json';
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function getClaudeDesktopDetectDir(): string {
|
|
27
|
-
switch (process.platform) {
|
|
28
|
-
case 'darwin':
|
|
29
|
-
return '~/Library/Application Support/Claude';
|
|
30
|
-
case 'win32':
|
|
31
|
-
return path.join(process.env.APPDATA || '', 'Claude');
|
|
32
|
-
default:
|
|
33
|
-
return '~/.config/Claude';
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getWindsurfConfigPath(): string {
|
|
38
|
-
if (process.platform === 'win32') {
|
|
39
|
-
return path.join(process.env.USERPROFILE || os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
40
|
-
}
|
|
41
|
-
return '~/.codeium/windsurf/mcp_config.json';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function getWindsurfDetectDir(): string {
|
|
45
|
-
if (process.platform === 'win32') {
|
|
46
|
-
return path.join(process.env.USERPROFILE || os.homedir(), '.codeium', 'windsurf');
|
|
47
|
-
}
|
|
48
|
-
return '~/.codeium/windsurf';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function getCursorConfigPath(): string {
|
|
52
|
-
if (process.platform === 'win32') {
|
|
53
|
-
return path.join(process.env.USERPROFILE || os.homedir(), '.cursor', 'mcp.json');
|
|
54
|
-
}
|
|
55
|
-
return '~/.cursor/mcp.json';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const AGENTS: Agent[] = [
|
|
59
|
-
{
|
|
60
|
-
id: 'claude-code',
|
|
61
|
-
name: 'Claude Code',
|
|
62
|
-
configPath: '~/.claude.json',
|
|
63
|
-
configKey: 'mcpServers',
|
|
64
|
-
detectDir: '~/.claude',
|
|
65
|
-
projectConfigPath: '.mcp.json',
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: 'claude-desktop',
|
|
69
|
-
name: 'Claude Desktop',
|
|
70
|
-
configPath: getClaudeDesktopConfigPath(),
|
|
71
|
-
configKey: 'mcpServers',
|
|
72
|
-
detectDir: getClaudeDesktopDetectDir(),
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
id: 'cursor',
|
|
76
|
-
name: 'Cursor',
|
|
77
|
-
configPath: getCursorConfigPath(),
|
|
78
|
-
configKey: 'mcpServers',
|
|
79
|
-
detectDir: '~/.cursor',
|
|
80
|
-
projectConfigPath: '.cursor/mcp.json',
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
id: 'windsurf',
|
|
84
|
-
name: 'Windsurf',
|
|
85
|
-
configPath: getWindsurfConfigPath(),
|
|
86
|
-
configKey: 'mcpServers',
|
|
87
|
-
detectDir: getWindsurfDetectDir(),
|
|
88
|
-
},
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
export function getAllAgents(): Agent[] {
|
|
92
|
-
return AGENTS;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function detectInstalledAgents(): Agent[] {
|
|
96
|
-
return AGENTS.filter(agent => {
|
|
97
|
-
const detectDir = expandPath(agent.detectDir);
|
|
98
|
-
return fs.existsSync(detectDir);
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function supportsProjectScope(agent: Agent): boolean {
|
|
103
|
-
return agent.projectConfigPath !== undefined;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function getAgentConfigPath(agent: Agent, scope: InstallScope = 'global'): string {
|
|
107
|
-
if (scope === 'project' && agent.projectConfigPath) {
|
|
108
|
-
return path.join(process.cwd(), agent.projectConfigPath);
|
|
109
|
-
}
|
|
110
|
-
return expandPath(agent.configPath);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function readAgentConfig(agent: Agent, scope: InstallScope = 'global'): Record<string, unknown> {
|
|
114
|
-
const configPath = getAgentConfigPath(agent, scope);
|
|
115
|
-
if (!fs.existsSync(configPath)) {
|
|
116
|
-
return {};
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
const content = fs.readFileSync(configPath, 'utf-8');
|
|
120
|
-
return JSON.parse(content);
|
|
121
|
-
} catch {
|
|
122
|
-
return {};
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function writeAgentConfig(agent: Agent, config: Record<string, unknown>, scope: InstallScope = 'global'): void {
|
|
127
|
-
const configPath = getAgentConfigPath(agent, scope);
|
|
128
|
-
const configDir = path.dirname(configPath);
|
|
129
|
-
|
|
130
|
-
if (!fs.existsSync(configDir)) {
|
|
131
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function getMcpServerConfig(apiKey: string): Record<string, unknown> {
|
|
138
|
-
return {
|
|
139
|
-
command: 'npx',
|
|
140
|
-
args: ['-y', '@picahq/mcp'],
|
|
141
|
-
env: {
|
|
142
|
-
PICA_SECRET: apiKey,
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function installMcpConfig(agent: Agent, apiKey: string, scope: InstallScope = 'global'): void {
|
|
148
|
-
const config = readAgentConfig(agent, scope);
|
|
149
|
-
const configKey = agent.configKey;
|
|
150
|
-
|
|
151
|
-
const mcpServers = (config[configKey] as Record<string, unknown>) || {};
|
|
152
|
-
mcpServers['pica'] = getMcpServerConfig(apiKey);
|
|
153
|
-
|
|
154
|
-
config[configKey] = mcpServers;
|
|
155
|
-
writeAgentConfig(agent, config, scope);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function isMcpInstalled(agent: Agent, scope: InstallScope = 'global'): boolean {
|
|
159
|
-
const config = readAgentConfig(agent, scope);
|
|
160
|
-
const configKey = agent.configKey;
|
|
161
|
-
const mcpServers = config[configKey] as Record<string, unknown> | undefined;
|
|
162
|
-
return mcpServers?.['pica'] !== undefined;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function getProjectConfigPaths(agents: Agent[]): string[] {
|
|
166
|
-
const paths: string[] = [];
|
|
167
|
-
for (const agent of agents) {
|
|
168
|
-
if (agent.projectConfigPath) {
|
|
169
|
-
paths.push(path.join(process.cwd(), agent.projectConfigPath));
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return paths;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export interface AgentStatus {
|
|
176
|
-
agent: Agent;
|
|
177
|
-
detected: boolean;
|
|
178
|
-
globalMcp: boolean;
|
|
179
|
-
projectMcp: boolean | null; // null = agent doesn't support project scope
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function getAgentStatuses(): AgentStatus[] {
|
|
183
|
-
return AGENTS.map(agent => {
|
|
184
|
-
const detected = fs.existsSync(expandPath(agent.detectDir));
|
|
185
|
-
const globalMcp = detected && isMcpInstalled(agent, 'global');
|
|
186
|
-
const projectMcp = agent.projectConfigPath
|
|
187
|
-
? isMcpInstalled(agent, 'project')
|
|
188
|
-
: null;
|
|
189
|
-
return { agent, detected, globalMcp, projectMcp };
|
|
190
|
-
});
|
|
191
|
-
}
|
package/src/lib/api.ts
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Connection,
|
|
3
|
-
ConnectionsResponse,
|
|
4
|
-
Platform,
|
|
5
|
-
PlatformsResponse,
|
|
6
|
-
PlatformAction,
|
|
7
|
-
ActionsSearchResponse,
|
|
8
|
-
ActionKnowledge,
|
|
9
|
-
KnowledgeResponse,
|
|
10
|
-
} from './types.js';
|
|
11
|
-
import { normalizeActionId } from './actions.js';
|
|
12
|
-
|
|
13
|
-
const API_BASE = 'https://api.picaos.com/v1';
|
|
14
|
-
|
|
15
|
-
export class ApiError extends Error {
|
|
16
|
-
constructor(public status: number, message: string) {
|
|
17
|
-
super(message);
|
|
18
|
-
this.name = 'ApiError';
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class PicaApi {
|
|
23
|
-
constructor(private apiKey: string) {}
|
|
24
|
-
|
|
25
|
-
private async request<T>(path: string): Promise<T> {
|
|
26
|
-
return this.requestFull<T>({ path });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
private async requestFull<T>(opts: {
|
|
30
|
-
path: string;
|
|
31
|
-
method?: string;
|
|
32
|
-
body?: unknown;
|
|
33
|
-
headers?: Record<string, string>;
|
|
34
|
-
queryParams?: Record<string, string>;
|
|
35
|
-
}): Promise<T> {
|
|
36
|
-
let url = `${API_BASE}${opts.path}`;
|
|
37
|
-
if (opts.queryParams && Object.keys(opts.queryParams).length > 0) {
|
|
38
|
-
const params = new URLSearchParams(opts.queryParams);
|
|
39
|
-
url += `?${params.toString()}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const headers: Record<string, string> = {
|
|
43
|
-
'x-pica-secret': this.apiKey,
|
|
44
|
-
'Content-Type': 'application/json',
|
|
45
|
-
...opts.headers,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const fetchOpts: RequestInit = {
|
|
49
|
-
method: opts.method || 'GET',
|
|
50
|
-
headers,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
if (opts.body !== undefined) {
|
|
54
|
-
fetchOpts.body = JSON.stringify(opts.body);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const response = await fetch(url, fetchOpts);
|
|
58
|
-
|
|
59
|
-
if (!response.ok) {
|
|
60
|
-
const text = await response.text();
|
|
61
|
-
throw new ApiError(response.status, text || `HTTP ${response.status}`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return response.json() as Promise<T>;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async validateApiKey(): Promise<boolean> {
|
|
68
|
-
try {
|
|
69
|
-
await this.listConnections();
|
|
70
|
-
return true;
|
|
71
|
-
} catch (error) {
|
|
72
|
-
if (error instanceof ApiError && error.status === 401) {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async listConnections(): Promise<Connection[]> {
|
|
80
|
-
const response = await this.request<ConnectionsResponse>('/vault/connections');
|
|
81
|
-
return response.rows || [];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async listPlatforms(): Promise<Platform[]> {
|
|
85
|
-
const allPlatforms: Platform[] = [];
|
|
86
|
-
let page = 1;
|
|
87
|
-
let totalPages = 1;
|
|
88
|
-
|
|
89
|
-
do {
|
|
90
|
-
const response = await this.request<PlatformsResponse>(`/available-connectors?page=${page}&limit=100`);
|
|
91
|
-
allPlatforms.push(...(response.rows || []));
|
|
92
|
-
totalPages = response.pages || 1;
|
|
93
|
-
page++;
|
|
94
|
-
} while (page <= totalPages);
|
|
95
|
-
|
|
96
|
-
return allPlatforms;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async searchActions(platform: string, query?: string, limit = 10): Promise<PlatformAction[]> {
|
|
100
|
-
const queryParams: Record<string, string> = {
|
|
101
|
-
limit: String(limit),
|
|
102
|
-
executeAgent: 'true',
|
|
103
|
-
};
|
|
104
|
-
if (query) queryParams.query = query;
|
|
105
|
-
|
|
106
|
-
const response = await this.requestFull<ActionsSearchResponse>({
|
|
107
|
-
path: `/available-actions/search/${encodeURIComponent(platform)}`,
|
|
108
|
-
queryParams,
|
|
109
|
-
});
|
|
110
|
-
return response.rows || [];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async getActionKnowledge(actionId: string): Promise<ActionKnowledge | null> {
|
|
114
|
-
const normalized = normalizeActionId(actionId);
|
|
115
|
-
const response = await this.requestFull<KnowledgeResponse>({
|
|
116
|
-
path: '/knowledge',
|
|
117
|
-
queryParams: { _id: normalized },
|
|
118
|
-
});
|
|
119
|
-
return response.rows?.[0] ?? null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async executeAction(opts: {
|
|
123
|
-
method: string;
|
|
124
|
-
path: string;
|
|
125
|
-
actionId: string;
|
|
126
|
-
connectionKey: string;
|
|
127
|
-
data?: unknown;
|
|
128
|
-
queryParams?: Record<string, string>;
|
|
129
|
-
headers?: Record<string, string>;
|
|
130
|
-
isFormData?: boolean;
|
|
131
|
-
isFormUrlEncoded?: boolean;
|
|
132
|
-
}): Promise<unknown> {
|
|
133
|
-
const headers: Record<string, string> = {
|
|
134
|
-
'x-pica-connection-key': opts.connectionKey,
|
|
135
|
-
'x-pica-action-id': normalizeActionId(opts.actionId),
|
|
136
|
-
...opts.headers,
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
if (opts.isFormData) {
|
|
140
|
-
headers['Content-Type'] = 'multipart/form-data';
|
|
141
|
-
} else if (opts.isFormUrlEncoded) {
|
|
142
|
-
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return this.requestFull<unknown>({
|
|
146
|
-
path: `/passthrough${opts.path}`,
|
|
147
|
-
method: opts.method.toUpperCase(),
|
|
148
|
-
body: opts.data,
|
|
149
|
-
headers,
|
|
150
|
-
queryParams: opts.queryParams,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async waitForConnection(
|
|
155
|
-
platform: string,
|
|
156
|
-
timeoutMs = 5 * 60 * 1000,
|
|
157
|
-
pollIntervalMs = 5000,
|
|
158
|
-
onPoll?: () => void
|
|
159
|
-
): Promise<Connection> {
|
|
160
|
-
const startTime = Date.now();
|
|
161
|
-
const existingConnections = await this.listConnections();
|
|
162
|
-
const existingIds = new Set(existingConnections.map(c => c.id));
|
|
163
|
-
|
|
164
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
165
|
-
await sleep(pollIntervalMs);
|
|
166
|
-
onPoll?.();
|
|
167
|
-
|
|
168
|
-
const currentConnections = await this.listConnections();
|
|
169
|
-
const newConnection = currentConnections.find(
|
|
170
|
-
c => c.platform.toLowerCase() === platform.toLowerCase() && !existingIds.has(c.id)
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
if (newConnection) {
|
|
174
|
-
return newConnection;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
throw new TimeoutError(`Timed out waiting for ${platform} connection`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export class TimeoutError extends Error {
|
|
183
|
-
constructor(message: string) {
|
|
184
|
-
super(message);
|
|
185
|
-
this.name = 'TimeoutError';
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function sleep(ms: number): Promise<void> {
|
|
190
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
191
|
-
}
|
package/src/lib/browser.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import open from 'open';
|
|
2
|
-
|
|
3
|
-
const PICA_APP_URL = 'https://app.picaos.com';
|
|
4
|
-
|
|
5
|
-
export function getConnectionUrl(platform: string): string {
|
|
6
|
-
return `${PICA_APP_URL}/connections?#open=${platform}`;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function getApiKeyUrl(): string {
|
|
10
|
-
return `${PICA_APP_URL}/settings/api-keys`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function openConnectionPage(platform: string): Promise<void> {
|
|
14
|
-
const url = getConnectionUrl(platform);
|
|
15
|
-
await open(url);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function openApiKeyPage(): Promise<void> {
|
|
19
|
-
await open(getApiKeyUrl());
|
|
20
|
-
}
|
package/src/lib/config.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import type { Config } from './types.js';
|
|
5
|
-
|
|
6
|
-
const CONFIG_DIR = path.join(os.homedir(), '.pica');
|
|
7
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
8
|
-
|
|
9
|
-
export function getConfigPath(): string {
|
|
10
|
-
return CONFIG_FILE;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function configExists(): boolean {
|
|
14
|
-
return fs.existsSync(CONFIG_FILE);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function readConfig(): Config | null {
|
|
18
|
-
if (!configExists()) {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
23
|
-
return JSON.parse(content) as Config;
|
|
24
|
-
} catch {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function writeConfig(config: Config): void {
|
|
30
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
31
|
-
fs.mkdirSync(CONFIG_DIR, { mode: 0o700 });
|
|
32
|
-
}
|
|
33
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function getApiKey(): string | null {
|
|
37
|
-
const config = readConfig();
|
|
38
|
-
return config?.apiKey ?? null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function updateInstalledAgents(agentIds: string[]): void {
|
|
42
|
-
const config = readConfig();
|
|
43
|
-
if (config) {
|
|
44
|
-
config.installedAgents = agentIds;
|
|
45
|
-
writeConfig(config);
|
|
46
|
-
}
|
|
47
|
-
}
|