@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/commands/init.ts
DELETED
|
@@ -1,548 +0,0 @@
|
|
|
1
|
-
import * as p from '@clack/prompts';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
import { writeConfig, readConfig, getConfigPath } from '../lib/config.js';
|
|
4
|
-
import {
|
|
5
|
-
getAllAgents,
|
|
6
|
-
installMcpConfig,
|
|
7
|
-
isMcpInstalled,
|
|
8
|
-
getAgentConfigPath,
|
|
9
|
-
supportsProjectScope,
|
|
10
|
-
getAgentStatuses,
|
|
11
|
-
type InstallScope,
|
|
12
|
-
type AgentStatus,
|
|
13
|
-
} from '../lib/agents.js';
|
|
14
|
-
import { PicaApi } from '../lib/api.js';
|
|
15
|
-
import { getApiKeyUrl, openApiKeyPage } from '../lib/browser.js';
|
|
16
|
-
import { printTable } from '../lib/table.js';
|
|
17
|
-
import type { Agent } from '../lib/types.js';
|
|
18
|
-
|
|
19
|
-
export async function initCommand(options: { yes?: boolean; global?: boolean; project?: boolean }): Promise<void> {
|
|
20
|
-
p.intro(pc.bgCyan(pc.black(' Pica ')));
|
|
21
|
-
|
|
22
|
-
const existingConfig = readConfig();
|
|
23
|
-
|
|
24
|
-
if (existingConfig) {
|
|
25
|
-
await handleExistingConfig(existingConfig.apiKey, options);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// First-run: no config exists
|
|
30
|
-
await freshSetup(options);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ── Status display + action menu when config already exists ──────────
|
|
34
|
-
|
|
35
|
-
async function handleExistingConfig(
|
|
36
|
-
apiKey: string,
|
|
37
|
-
options: { yes?: boolean; global?: boolean; project?: boolean },
|
|
38
|
-
): Promise<void> {
|
|
39
|
-
const statuses = getAgentStatuses();
|
|
40
|
-
|
|
41
|
-
// Display current setup
|
|
42
|
-
const masked = maskApiKey(apiKey);
|
|
43
|
-
console.log();
|
|
44
|
-
console.log(` ${pc.bold('Current Setup')}`);
|
|
45
|
-
console.log(` ${pc.dim('─'.repeat(42))}`);
|
|
46
|
-
console.log(` ${pc.dim('API Key:')} ${masked}`);
|
|
47
|
-
console.log(` ${pc.dim('Config:')} ${getConfigPath()}`);
|
|
48
|
-
console.log();
|
|
49
|
-
|
|
50
|
-
// Agent status table
|
|
51
|
-
printTable(
|
|
52
|
-
[
|
|
53
|
-
{ key: 'agent', label: 'Agent' },
|
|
54
|
-
{ key: 'global', label: 'Global' },
|
|
55
|
-
{ key: 'project', label: 'Project' },
|
|
56
|
-
],
|
|
57
|
-
statuses.map(s => ({
|
|
58
|
-
agent: s.agent.name,
|
|
59
|
-
global: !s.detected
|
|
60
|
-
? pc.dim('-')
|
|
61
|
-
: s.globalMcp
|
|
62
|
-
? pc.green('\u25cf yes')
|
|
63
|
-
: pc.yellow('\u25cb no'),
|
|
64
|
-
project: s.projectMcp === null
|
|
65
|
-
? pc.dim('-')
|
|
66
|
-
: s.projectMcp
|
|
67
|
-
? pc.green('\u25cf yes')
|
|
68
|
-
: pc.yellow('\u25cb no'),
|
|
69
|
-
})),
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const notDetected = statuses.filter(s => !s.detected);
|
|
73
|
-
if (notDetected.length > 0) {
|
|
74
|
-
console.log(` ${pc.dim('- = not detected on this machine')}`);
|
|
75
|
-
}
|
|
76
|
-
console.log();
|
|
77
|
-
|
|
78
|
-
// Build action menu: only show relevant options
|
|
79
|
-
type Action = 'update-key' | 'install-more' | 'install-project' | 'start-fresh';
|
|
80
|
-
const actionOptions: { value: Action; label: string; hint?: string }[] = [];
|
|
81
|
-
|
|
82
|
-
actionOptions.push({
|
|
83
|
-
value: 'update-key',
|
|
84
|
-
label: 'Update API key',
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const agentsMissingGlobal = statuses.filter(s => s.detected && !s.globalMcp);
|
|
88
|
-
if (agentsMissingGlobal.length > 0) {
|
|
89
|
-
actionOptions.push({
|
|
90
|
-
value: 'install-more',
|
|
91
|
-
label: 'Install MCP to more agents',
|
|
92
|
-
hint: agentsMissingGlobal.map(s => s.agent.name).join(', '),
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const agentsMissingProject = statuses.filter(s => s.projectMcp === false);
|
|
97
|
-
if (agentsMissingProject.length > 0) {
|
|
98
|
-
actionOptions.push({
|
|
99
|
-
value: 'install-project',
|
|
100
|
-
label: 'Install MCP for this project',
|
|
101
|
-
hint: agentsMissingProject.map(s => s.agent.name).join(', '),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
actionOptions.push({
|
|
106
|
-
value: 'start-fresh',
|
|
107
|
-
label: 'Start fresh (reconfigure everything)',
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const action = await p.select({
|
|
111
|
-
message: 'What would you like to do?',
|
|
112
|
-
options: actionOptions,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
if (p.isCancel(action)) {
|
|
116
|
-
p.outro('No changes made.');
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
switch (action) {
|
|
121
|
-
case 'update-key':
|
|
122
|
-
await handleUpdateKey(statuses);
|
|
123
|
-
break;
|
|
124
|
-
case 'install-more':
|
|
125
|
-
await handleInstallMore(apiKey, agentsMissingGlobal);
|
|
126
|
-
break;
|
|
127
|
-
case 'install-project':
|
|
128
|
-
await handleInstallProject(apiKey, agentsMissingProject);
|
|
129
|
-
break;
|
|
130
|
-
case 'start-fresh':
|
|
131
|
-
await freshSetup({ yes: true });
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ── Action handlers ──────────────────────────────────────────────────
|
|
137
|
-
|
|
138
|
-
async function handleUpdateKey(statuses: AgentStatus[]): Promise<void> {
|
|
139
|
-
p.note(`Get your API key at:\n${pc.cyan(getApiKeyUrl())}`, 'API Key');
|
|
140
|
-
|
|
141
|
-
const openBrowser = await p.confirm({
|
|
142
|
-
message: 'Open browser to get API key?',
|
|
143
|
-
initialValue: true,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
if (p.isCancel(openBrowser)) {
|
|
147
|
-
p.cancel('Cancelled.');
|
|
148
|
-
process.exit(0);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (openBrowser) {
|
|
152
|
-
await openApiKeyPage();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const newKey = await p.text({
|
|
156
|
-
message: 'Enter your new Pica API key:',
|
|
157
|
-
placeholder: 'sk_live_...',
|
|
158
|
-
validate: (value) => {
|
|
159
|
-
if (!value) return 'API key is required';
|
|
160
|
-
if (!value.startsWith('sk_live_') && !value.startsWith('sk_test_')) {
|
|
161
|
-
return 'API key should start with sk_live_ or sk_test_';
|
|
162
|
-
}
|
|
163
|
-
return undefined;
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
if (p.isCancel(newKey)) {
|
|
168
|
-
p.cancel('Cancelled.');
|
|
169
|
-
process.exit(0);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Validate
|
|
173
|
-
const spinner = p.spinner();
|
|
174
|
-
spinner.start('Validating API key...');
|
|
175
|
-
|
|
176
|
-
const api = new PicaApi(newKey);
|
|
177
|
-
const isValid = await api.validateApiKey();
|
|
178
|
-
|
|
179
|
-
if (!isValid) {
|
|
180
|
-
spinner.stop('Invalid API key');
|
|
181
|
-
p.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
|
|
182
|
-
process.exit(1);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
spinner.stop('API key validated');
|
|
186
|
-
|
|
187
|
-
// Re-install MCP to every agent that currently has it (preserve scopes)
|
|
188
|
-
const reinstalled: string[] = [];
|
|
189
|
-
for (const s of statuses) {
|
|
190
|
-
if (s.globalMcp) {
|
|
191
|
-
installMcpConfig(s.agent, newKey, 'global');
|
|
192
|
-
reinstalled.push(`${s.agent.name} (global)`);
|
|
193
|
-
}
|
|
194
|
-
if (s.projectMcp) {
|
|
195
|
-
installMcpConfig(s.agent, newKey, 'project');
|
|
196
|
-
reinstalled.push(`${s.agent.name} (project)`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Update config
|
|
201
|
-
const config = readConfig();
|
|
202
|
-
writeConfig({
|
|
203
|
-
apiKey: newKey,
|
|
204
|
-
installedAgents: config?.installedAgents ?? [],
|
|
205
|
-
createdAt: config?.createdAt ?? new Date().toISOString(),
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
if (reinstalled.length > 0) {
|
|
209
|
-
p.log.success(`Updated MCP configs: ${reinstalled.join(', ')}`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
p.outro('API key updated.');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async function handleInstallMore(apiKey: string, missing: AgentStatus[]): Promise<void> {
|
|
216
|
-
if (missing.length === 1) {
|
|
217
|
-
// Only one option, just confirm
|
|
218
|
-
const agent = missing[0].agent;
|
|
219
|
-
const confirm = await p.confirm({
|
|
220
|
-
message: `Install Pica MCP to ${agent.name}?`,
|
|
221
|
-
initialValue: true,
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
if (p.isCancel(confirm) || !confirm) {
|
|
225
|
-
p.outro('No changes made.');
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
installMcpConfig(agent, apiKey, 'global');
|
|
230
|
-
updateConfigAgents(agent.id);
|
|
231
|
-
p.log.success(`${agent.name}: MCP installed`);
|
|
232
|
-
p.outro('Done.');
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const selected = await p.multiselect({
|
|
237
|
-
message: 'Select agents to install MCP:',
|
|
238
|
-
options: missing.map(s => ({
|
|
239
|
-
value: s.agent.id,
|
|
240
|
-
label: s.agent.name,
|
|
241
|
-
})),
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
if (p.isCancel(selected)) {
|
|
245
|
-
p.outro('No changes made.');
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const agents = missing.filter(s => (selected as string[]).includes(s.agent.id));
|
|
250
|
-
for (const s of agents) {
|
|
251
|
-
installMcpConfig(s.agent, apiKey, 'global');
|
|
252
|
-
updateConfigAgents(s.agent.id);
|
|
253
|
-
p.log.success(`${s.agent.name}: MCP installed`);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
p.outro('Done.');
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async function handleInstallProject(apiKey: string, missing: AgentStatus[]): Promise<void> {
|
|
260
|
-
if (missing.length === 1) {
|
|
261
|
-
const agent = missing[0].agent;
|
|
262
|
-
const confirm = await p.confirm({
|
|
263
|
-
message: `Install project-level MCP for ${agent.name}?`,
|
|
264
|
-
initialValue: true,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
if (p.isCancel(confirm) || !confirm) {
|
|
268
|
-
p.outro('No changes made.');
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
installMcpConfig(agent, apiKey, 'project');
|
|
273
|
-
const configPath = getAgentConfigPath(agent, 'project');
|
|
274
|
-
p.log.success(`${agent.name}: ${configPath} created`);
|
|
275
|
-
p.note(
|
|
276
|
-
pc.yellow('Project config files can be committed to share with your team.\n') +
|
|
277
|
-
pc.yellow('Team members will need their own API key.'),
|
|
278
|
-
'Tip',
|
|
279
|
-
);
|
|
280
|
-
p.outro('Done.');
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const selected = await p.multiselect({
|
|
285
|
-
message: 'Select agents for project-level MCP:',
|
|
286
|
-
options: missing.map(s => ({
|
|
287
|
-
value: s.agent.id,
|
|
288
|
-
label: s.agent.name,
|
|
289
|
-
})),
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
if (p.isCancel(selected)) {
|
|
293
|
-
p.outro('No changes made.');
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const agents = missing.filter(s => (selected as string[]).includes(s.agent.id));
|
|
298
|
-
for (const s of agents) {
|
|
299
|
-
installMcpConfig(s.agent, apiKey, 'project');
|
|
300
|
-
const configPath = getAgentConfigPath(s.agent, 'project');
|
|
301
|
-
p.log.success(`${s.agent.name}: ${configPath} created`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
p.note(
|
|
305
|
-
pc.yellow('Project config files can be committed to share with your team.\n') +
|
|
306
|
-
pc.yellow('Team members will need their own API key.'),
|
|
307
|
-
'Tip',
|
|
308
|
-
);
|
|
309
|
-
p.outro('Done.');
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// ── First-run setup (no existing config) ─────────────────────────────
|
|
313
|
-
|
|
314
|
-
async function freshSetup(options: { yes?: boolean; global?: boolean; project?: boolean }): Promise<void> {
|
|
315
|
-
// Get API key
|
|
316
|
-
p.note(`Get your API key at:\n${pc.cyan(getApiKeyUrl())}`, 'API Key');
|
|
317
|
-
|
|
318
|
-
const openBrowser = await p.confirm({
|
|
319
|
-
message: 'Open browser to get API key?',
|
|
320
|
-
initialValue: true,
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
if (p.isCancel(openBrowser)) {
|
|
324
|
-
p.cancel('Setup cancelled.');
|
|
325
|
-
process.exit(0);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (openBrowser) {
|
|
329
|
-
await openApiKeyPage();
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const apiKey = await p.text({
|
|
333
|
-
message: 'Enter your Pica API key:',
|
|
334
|
-
placeholder: 'sk_live_...',
|
|
335
|
-
validate: (value) => {
|
|
336
|
-
if (!value) return 'API key is required';
|
|
337
|
-
if (!value.startsWith('sk_live_') && !value.startsWith('sk_test_')) {
|
|
338
|
-
return 'API key should start with sk_live_ or sk_test_';
|
|
339
|
-
}
|
|
340
|
-
return undefined;
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
if (p.isCancel(apiKey)) {
|
|
345
|
-
p.cancel('Setup cancelled.');
|
|
346
|
-
process.exit(0);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Validate API key
|
|
350
|
-
const spinner = p.spinner();
|
|
351
|
-
spinner.start('Validating API key...');
|
|
352
|
-
|
|
353
|
-
const api = new PicaApi(apiKey);
|
|
354
|
-
const isValid = await api.validateApiKey();
|
|
355
|
-
|
|
356
|
-
if (!isValid) {
|
|
357
|
-
spinner.stop('Invalid API key');
|
|
358
|
-
p.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
|
|
359
|
-
process.exit(1);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
spinner.stop('API key validated');
|
|
363
|
-
|
|
364
|
-
// Save API key to config first
|
|
365
|
-
writeConfig({
|
|
366
|
-
apiKey,
|
|
367
|
-
installedAgents: [],
|
|
368
|
-
createdAt: new Date().toISOString(),
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// Ask which agent to install for
|
|
372
|
-
const allAgents = getAllAgents();
|
|
373
|
-
|
|
374
|
-
const agentChoice = await p.select({
|
|
375
|
-
message: 'Where do you want to install the MCP?',
|
|
376
|
-
options: [
|
|
377
|
-
{
|
|
378
|
-
value: 'all',
|
|
379
|
-
label: 'All agents',
|
|
380
|
-
hint: 'Claude Code, Claude Desktop, Cursor, Windsurf',
|
|
381
|
-
},
|
|
382
|
-
...allAgents.map(agent => ({
|
|
383
|
-
value: agent.id,
|
|
384
|
-
label: agent.name,
|
|
385
|
-
})),
|
|
386
|
-
],
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
if (p.isCancel(agentChoice)) {
|
|
390
|
-
p.cancel('Setup cancelled.');
|
|
391
|
-
process.exit(0);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const selectedAgents: Agent[] = agentChoice === 'all'
|
|
395
|
-
? allAgents
|
|
396
|
-
: allAgents.filter(a => a.id === agentChoice);
|
|
397
|
-
|
|
398
|
-
// Ask about installation scope if any selected agent supports project scope
|
|
399
|
-
let scope: InstallScope = 'global';
|
|
400
|
-
const hasProjectScopeAgent = selectedAgents.some(a => supportsProjectScope(a));
|
|
401
|
-
|
|
402
|
-
if (options.global) {
|
|
403
|
-
scope = 'global';
|
|
404
|
-
} else if (options.project) {
|
|
405
|
-
scope = 'project';
|
|
406
|
-
} else if (hasProjectScopeAgent) {
|
|
407
|
-
const scopeChoice = await p.select({
|
|
408
|
-
message: 'How do you want to install it?',
|
|
409
|
-
options: [
|
|
410
|
-
{
|
|
411
|
-
value: 'global',
|
|
412
|
-
label: 'Global (Recommended)',
|
|
413
|
-
hint: 'Available in all your projects',
|
|
414
|
-
},
|
|
415
|
-
{
|
|
416
|
-
value: 'project',
|
|
417
|
-
label: 'Project only',
|
|
418
|
-
hint: 'Creates config files in current directory',
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
if (p.isCancel(scopeChoice)) {
|
|
424
|
-
p.cancel('Setup cancelled.');
|
|
425
|
-
process.exit(0);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
scope = scopeChoice as InstallScope;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Handle project scope installation
|
|
432
|
-
if (scope === 'project') {
|
|
433
|
-
const projectAgents = selectedAgents.filter(a => supportsProjectScope(a));
|
|
434
|
-
const nonProjectAgents = selectedAgents.filter(a => !supportsProjectScope(a));
|
|
435
|
-
|
|
436
|
-
if (projectAgents.length === 0) {
|
|
437
|
-
const supported = allAgents.filter(a => supportsProjectScope(a)).map(a => a.name).join(', ');
|
|
438
|
-
p.note(
|
|
439
|
-
`${selectedAgents.map(a => a.name).join(', ')} does not support project-level MCP.\n` +
|
|
440
|
-
`Project scope is supported by: ${supported}`,
|
|
441
|
-
'Not Supported'
|
|
442
|
-
);
|
|
443
|
-
p.cancel('Run again and choose global scope or a different agent.');
|
|
444
|
-
process.exit(1);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Install project-scoped agents
|
|
448
|
-
for (const agent of projectAgents) {
|
|
449
|
-
const wasInstalled = isMcpInstalled(agent, 'project');
|
|
450
|
-
installMcpConfig(agent, apiKey, 'project');
|
|
451
|
-
const configPath = getAgentConfigPath(agent, 'project');
|
|
452
|
-
const status = wasInstalled ? 'updated' : 'created';
|
|
453
|
-
p.log.success(`${agent.name}: ${configPath} ${status}`);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// If "all agents" was selected and some don't support project scope,
|
|
457
|
-
// install those globally and let the user know
|
|
458
|
-
if (nonProjectAgents.length > 0) {
|
|
459
|
-
p.log.info(`Installing globally for agents without project scope support:`);
|
|
460
|
-
for (const agent of nonProjectAgents) {
|
|
461
|
-
const wasInstalled = isMcpInstalled(agent, 'global');
|
|
462
|
-
installMcpConfig(agent, apiKey, 'global');
|
|
463
|
-
const status = wasInstalled ? 'updated' : 'installed';
|
|
464
|
-
p.log.success(`${agent.name}: MCP ${status} (global)`);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const allInstalled = [...projectAgents, ...nonProjectAgents];
|
|
469
|
-
|
|
470
|
-
// Update config
|
|
471
|
-
writeConfig({
|
|
472
|
-
apiKey,
|
|
473
|
-
installedAgents: allInstalled.map(a => a.id),
|
|
474
|
-
createdAt: new Date().toISOString(),
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
const configPaths = projectAgents
|
|
478
|
-
.map(a => ` ${a.name}: ${pc.dim(getAgentConfigPath(a, 'project'))}`)
|
|
479
|
-
.join('\n');
|
|
480
|
-
|
|
481
|
-
let summary = `Config saved to: ${pc.dim(getConfigPath())}\n` +
|
|
482
|
-
`MCP configs:\n${configPaths}\n\n`;
|
|
483
|
-
|
|
484
|
-
if (nonProjectAgents.length > 0) {
|
|
485
|
-
const globalPaths = nonProjectAgents
|
|
486
|
-
.map(a => ` ${a.name}: ${pc.dim(getAgentConfigPath(a, 'global'))}`)
|
|
487
|
-
.join('\n');
|
|
488
|
-
summary += `Global configs:\n${globalPaths}\n\n`;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
summary +=
|
|
492
|
-
pc.yellow('Note: Project config files can be committed to share with your team.\n') +
|
|
493
|
-
pc.yellow('Team members will need their own API key.\n\n') +
|
|
494
|
-
`Next steps:\n` +
|
|
495
|
-
` ${pc.cyan('pica add gmail')} - Connect Gmail\n` +
|
|
496
|
-
` ${pc.cyan('pica platforms')} - See all 200+ integrations`;
|
|
497
|
-
|
|
498
|
-
p.note(summary, 'Setup Complete');
|
|
499
|
-
p.outro('Pica MCP installed!');
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Global scope: install to all selected agents
|
|
504
|
-
const installedAgentIds: string[] = [];
|
|
505
|
-
|
|
506
|
-
for (const agent of selectedAgents) {
|
|
507
|
-
const wasInstalled = isMcpInstalled(agent, 'global');
|
|
508
|
-
installMcpConfig(agent, apiKey, 'global');
|
|
509
|
-
installedAgentIds.push(agent.id);
|
|
510
|
-
|
|
511
|
-
const status = wasInstalled ? 'updated' : 'installed';
|
|
512
|
-
p.log.success(`${agent.name}: MCP ${status}`);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// Save config
|
|
516
|
-
writeConfig({
|
|
517
|
-
apiKey,
|
|
518
|
-
installedAgents: installedAgentIds,
|
|
519
|
-
createdAt: new Date().toISOString(),
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
p.note(
|
|
523
|
-
`Config saved to: ${pc.dim(getConfigPath())}\n\n` +
|
|
524
|
-
`Next steps:\n` +
|
|
525
|
-
` ${pc.cyan('pica add gmail')} - Connect Gmail\n` +
|
|
526
|
-
` ${pc.cyan('pica platforms')} - See all 200+ integrations\n` +
|
|
527
|
-
` ${pc.cyan('pica connection list')} - View your connections`,
|
|
528
|
-
'Setup Complete'
|
|
529
|
-
);
|
|
530
|
-
|
|
531
|
-
p.outro('Your AI agents now have access to Pica integrations!');
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// ── Helpers ──────────────────────────────────────────────────────────
|
|
535
|
-
|
|
536
|
-
function maskApiKey(key: string): string {
|
|
537
|
-
if (key.length <= 12) return key.slice(0, 8) + '...';
|
|
538
|
-
return key.slice(0, 8) + '...' + key.slice(-4);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function updateConfigAgents(agentId: string): void {
|
|
542
|
-
const config = readConfig();
|
|
543
|
-
if (!config) return;
|
|
544
|
-
if (!config.installedAgents.includes(agentId)) {
|
|
545
|
-
config.installedAgents.push(agentId);
|
|
546
|
-
writeConfig(config);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
@@ -1,92 +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 { printTable } from '../lib/table.js';
|
|
6
|
-
import type { Platform } from '../lib/types.js';
|
|
7
|
-
|
|
8
|
-
export async function platformsCommand(options: { category?: string; json?: boolean }): Promise<void> {
|
|
9
|
-
const apiKey = getApiKey();
|
|
10
|
-
if (!apiKey) {
|
|
11
|
-
p.cancel('Not configured. Run `pica init` first.');
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const api = new PicaApi(apiKey);
|
|
16
|
-
|
|
17
|
-
const spinner = p.spinner();
|
|
18
|
-
spinner.start('Loading platforms...');
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const platforms = await api.listPlatforms();
|
|
22
|
-
spinner.stop(`${platforms.length} platforms available`);
|
|
23
|
-
|
|
24
|
-
if (options.json) {
|
|
25
|
-
console.log(JSON.stringify(platforms, null, 2));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Group by category
|
|
30
|
-
const byCategory = new Map<string, Platform[]>();
|
|
31
|
-
for (const plat of platforms) {
|
|
32
|
-
const category = plat.category || 'Other';
|
|
33
|
-
if (!byCategory.has(category)) {
|
|
34
|
-
byCategory.set(category, []);
|
|
35
|
-
}
|
|
36
|
-
byCategory.get(category)!.push(plat);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.log();
|
|
40
|
-
|
|
41
|
-
// Filter by category if specified
|
|
42
|
-
if (options.category) {
|
|
43
|
-
const categoryPlatforms = byCategory.get(options.category);
|
|
44
|
-
if (!categoryPlatforms) {
|
|
45
|
-
const categories = [...byCategory.keys()].sort();
|
|
46
|
-
p.note(`Available categories:\n ${categories.join(', ')}`, 'Unknown Category');
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const rows = categoryPlatforms
|
|
51
|
-
.sort((a, b) => a.platform.localeCompare(b.platform))
|
|
52
|
-
.map(plat => ({
|
|
53
|
-
platform: plat.platform,
|
|
54
|
-
name: plat.name,
|
|
55
|
-
category: plat.category || 'Other',
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
printTable(
|
|
59
|
-
[
|
|
60
|
-
{ key: 'platform', label: 'Platform' },
|
|
61
|
-
{ key: 'name', label: 'Name' },
|
|
62
|
-
],
|
|
63
|
-
rows
|
|
64
|
-
);
|
|
65
|
-
} else {
|
|
66
|
-
const rows = platforms
|
|
67
|
-
.sort((a, b) => a.category.localeCompare(b.category) || a.platform.localeCompare(b.platform))
|
|
68
|
-
.map(plat => ({
|
|
69
|
-
platform: plat.platform,
|
|
70
|
-
name: plat.name,
|
|
71
|
-
category: plat.category || 'Other',
|
|
72
|
-
}));
|
|
73
|
-
|
|
74
|
-
printTable(
|
|
75
|
-
[
|
|
76
|
-
{ key: 'category', label: 'Category' },
|
|
77
|
-
{ key: 'platform', label: 'Platform' },
|
|
78
|
-
{ key: 'name', label: 'Name' },
|
|
79
|
-
],
|
|
80
|
-
rows
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
console.log();
|
|
85
|
-
p.note(`Connect with: ${pc.cyan('pica connection add <platform>')}`, 'Tip');
|
|
86
|
-
} catch (error) {
|
|
87
|
-
spinner.stop('Failed to load platforms');
|
|
88
|
-
p.cancel(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|