@prave/cli 1.6.0 → 1.6.1

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.
@@ -0,0 +1,68 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { track } from '../lib/analytics.js';
4
+ import { api, ApiError } from '../lib/api.js';
5
+ import { requireAuth } from '../lib/credentials.js';
6
+ import { log } from '../utils/logger.js';
7
+ const RULE = '─────────────────────────────────────────';
8
+ export async function advisorCommand(task, opts = {}) {
9
+ const mode = opts.auto ? 'auto' : 'manual';
10
+ track('cli_advisor', { auto_mode: opts.auto === true, task_length: task?.length ?? 0 });
11
+ if (mode === 'manual') {
12
+ if (!task || task.trim().length < 8) {
13
+ log.error('Pass a task description of at least 8 characters, or use --auto.');
14
+ process.exitCode = 1;
15
+ return;
16
+ }
17
+ }
18
+ await requireAuth('advisor');
19
+ const spinner = ora(mode === 'auto' ? 'Thinking…' : `Thinking about "${truncate(task, 60)}"…`).start();
20
+ try {
21
+ const { data } = await api.post('/api/v1/advisor', { mode, task: mode === 'manual' ? task : undefined }, true);
22
+ spinner.stop();
23
+ console.log(chalk.dim(RULE));
24
+ console.log(chalk.bold('Recommended for you'));
25
+ console.log(chalk.dim(RULE));
26
+ console.log(wrap(data.prose, 78));
27
+ console.log();
28
+ if (data.recommendations.length === 0) {
29
+ log.dim('No specific Skills to recommend yet.');
30
+ }
31
+ else {
32
+ for (const rec of data.recommendations) {
33
+ console.log(` ${chalk.cyan('▸')} ${chalk.bold(rec.slug)}`);
34
+ console.log(` ${chalk.dim('Why:')} ${wrap(rec.reason, 70).split('\n').join('\n ')}`);
35
+ console.log(` ${chalk.dim(`prave install ${rec.slug}`)}`);
36
+ console.log();
37
+ }
38
+ }
39
+ console.log(chalk.dim(`Quota: ${data.quota.used} / ${data.quota.limit} today on the ${data.quota.plan} plan.`));
40
+ }
41
+ catch (err) {
42
+ spinner.stop();
43
+ log.error(err instanceof ApiError ? err.message : err.message);
44
+ process.exitCode = 1;
45
+ }
46
+ }
47
+ function truncate(s, n) {
48
+ return s.length <= n ? s : `${s.slice(0, n - 1)}…`;
49
+ }
50
+ function wrap(text, width) {
51
+ const out = [];
52
+ for (const para of text.split('\n')) {
53
+ const words = para.trim().split(/\s+/);
54
+ let line = '';
55
+ for (const w of words) {
56
+ if ((line + ' ' + w).trim().length > width) {
57
+ out.push(line.trim());
58
+ line = w;
59
+ }
60
+ else {
61
+ line = `${line} ${w}`;
62
+ }
63
+ }
64
+ if (line.trim())
65
+ out.push(line.trim());
66
+ }
67
+ return out.join('\n');
68
+ }
@@ -62,6 +62,23 @@ const TOOLS = [
62
62
  required: ['slug'],
63
63
  },
64
64
  },
65
+ {
66
+ name: 'prave_advisor',
67
+ description: "Contextual Skill advisor. Given a user task (e.g. 'I'm building a Next.js app with Stripe and auth'), returns a prose recommendation explaining which Skills genuinely fit and why. Tiered freemium quota — Free 3/day, Pro 10/day, Max 30/day — enforced server-side. Use this when the user asks 'what Skills should I install for X?' or 'help me pick Skills for my project'.",
68
+ inputSchema: {
69
+ type: 'object',
70
+ properties: {
71
+ task: {
72
+ type: 'string',
73
+ description: "The user's task / context. Min 8 chars. Omit to run in auto mode.",
74
+ },
75
+ auto: {
76
+ type: 'boolean',
77
+ description: "When true, ignore `task` and synthesise context from the user's installed Skills + recent usage.",
78
+ },
79
+ },
80
+ },
81
+ },
65
82
  ];
66
83
  export async function mcpServerCommand() {
67
84
  // Auth check up-front. If the user runs `prave mcp-server` without
@@ -96,6 +113,8 @@ export async function mcpServerCommand() {
96
113
  return await handleAuditLibrary();
97
114
  case 'prave_install_skill':
98
115
  return await handleInstallSkill(args);
116
+ case 'prave_advisor':
117
+ return await handleAdvisor(args);
99
118
  default:
100
119
  return mcpError(`Unknown tool: ${name}`);
101
120
  }
@@ -202,6 +221,27 @@ async function handleInstallSkill(args) {
202
221
  });
203
222
  });
204
223
  }
224
+ async function handleAdvisor(args) {
225
+ const mode = args.auto ? 'auto' : 'manual';
226
+ if (mode === 'manual' && (!args.task || args.task.trim().length < 8)) {
227
+ return mcpError("Pass a `task` of at least 8 characters describing what the user is building, or set `auto: true` to use their installed Skills as context.");
228
+ }
229
+ const { data } = await api.post('/api/v1/advisor', { mode, task: mode === 'manual' ? args.task : undefined }, true);
230
+ const lines = [data.prose, ''];
231
+ if (data.recommendations.length === 0) {
232
+ lines.push('(No specific Skills recommended for this context.)');
233
+ }
234
+ else {
235
+ for (const rec of data.recommendations) {
236
+ lines.push(`• ${rec.slug}`);
237
+ lines.push(` Why: ${rec.reason}`);
238
+ lines.push(` Install: prave install ${rec.slug}`);
239
+ }
240
+ }
241
+ lines.push('');
242
+ lines.push(`Advisor quota: ${data.quota.used} / ${data.quota.limit} today on the ${data.quota.plan} plan.`);
243
+ return mcpText(lines.join('\n'));
244
+ }
205
245
  /* ─── helpers ──────────────────────────────────────────────────── */
206
246
  function mcpText(text) {
207
247
  return { content: [{ type: 'text', text }] };
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
5
5
  import { Command } from 'commander';
6
6
  import { conflictsCommand } from './commands/conflicts.js';
7
7
  import { diffCommand } from './commands/diff.js';
8
+ import { advisorCommand } from './commands/advisor.js';
8
9
  import { docsCommand } from './commands/docs.js';
9
10
  import { hooksAuditCommand, hooksInstallCommand, hooksListCommand, hooksRemoveCommand, hooksSyncCommand, hooksUpdateCommand, } from './commands/hooks.js';
10
11
  import { importCommand } from './commands/import.js';
@@ -110,6 +111,11 @@ program
110
111
  .command('whatdoes <skillname>')
111
112
  .description("Inspect a skill's triggers, tokens, conflicts")
112
113
  .action(whatdoesCommand);
114
+ program
115
+ .command('advisor [task...]')
116
+ .description('Prose recommendation from the contextual Skill advisor — pass a task description, or use --auto to base it on your installed Skills.')
117
+ .option('--auto', 'use your installed Skills + recent usage as context (no task argument)')
118
+ .action((taskWords, opts) => advisorCommand(taskWords.length ? taskWords.join(' ') : undefined, opts));
113
119
  program
114
120
  .command('overview')
115
121
  .description('Summary of your skill set, conflicts, and token cost')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prave/cli",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "Prave CLI — discover, install, version, test, and ship Claude Skills. The developer platform for the complete Skill lifecycle.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -54,7 +54,7 @@
54
54
  "ora": "^8.0.1",
55
55
  "tar": "^7.4.3",
56
56
  "undici": "^6.18.0",
57
- "@prave/shared": "1.5.0"
57
+ "@prave/shared": "1.5.1"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "^20.12.7",