@prave/cli 1.6.1 → 1.6.2
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/commands/advisor.js +27 -9
- package/dist/commands/mcp-server.js +15 -4
- package/dist/commands/usage.js +24 -3
- package/dist/index.js +1 -0
- package/dist/lib/hook.js +7 -2
- package/dist/lib/usage-scanner.js +31 -5
- package/package.json +2 -2
package/dist/commands/advisor.js
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { track } from '../lib/analytics.js';
|
|
4
4
|
import { api, ApiError } from '../lib/api.js';
|
|
5
|
+
import { CONFIG } from '../lib/config.js';
|
|
5
6
|
import { requireAuth } from '../lib/credentials.js';
|
|
6
7
|
import { log } from '../utils/logger.js';
|
|
7
8
|
const RULE = '─────────────────────────────────────────';
|
|
@@ -21,21 +22,24 @@ export async function advisorCommand(task, opts = {}) {
|
|
|
21
22
|
const { data } = await api.post('/api/v1/advisor', { mode, task: mode === 'manual' ? task : undefined }, true);
|
|
22
23
|
spinner.stop();
|
|
23
24
|
console.log(chalk.dim(RULE));
|
|
24
|
-
|
|
25
|
+
const hasReasoned = data.recommendations.length > 0;
|
|
26
|
+
console.log(chalk.bold(hasReasoned ? 'Recommended for you' : 'Starting points'));
|
|
25
27
|
console.log(chalk.dim(RULE));
|
|
26
28
|
console.log(wrap(data.prose, 78));
|
|
27
29
|
console.log();
|
|
28
|
-
if (
|
|
29
|
-
log.dim('No specific Skills to recommend yet.');
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
30
|
+
if (hasReasoned) {
|
|
32
31
|
for (const rec of data.recommendations) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
printSkill(rec.slug, rec.reason);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (data.candidate_slugs.length > 0) {
|
|
36
|
+
for (const slug of data.candidate_slugs) {
|
|
37
|
+
printSkill(slug);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
40
|
+
else {
|
|
41
|
+
log.dim('No specific Skills to recommend yet.');
|
|
42
|
+
}
|
|
39
43
|
console.log(chalk.dim(`Quota: ${data.quota.used} / ${data.quota.limit} today on the ${data.quota.plan} plan.`));
|
|
40
44
|
}
|
|
41
45
|
catch (err) {
|
|
@@ -44,6 +48,20 @@ export async function advisorCommand(task, opts = {}) {
|
|
|
44
48
|
process.exitCode = 1;
|
|
45
49
|
}
|
|
46
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Print one Skill row — bullet · slug · optional Why · install command
|
|
53
|
+
* · clickable prave.app link. The link is what lets the user verify
|
|
54
|
+
* the Skill on the web before pulling it into their library.
|
|
55
|
+
*/
|
|
56
|
+
function printSkill(slug, reason) {
|
|
57
|
+
console.log(` ${chalk.cyan('▸')} ${chalk.bold(slug)}`);
|
|
58
|
+
if (reason) {
|
|
59
|
+
console.log(` ${chalk.dim('Why:')} ${wrap(reason, 70).split('\n').join('\n ')}`);
|
|
60
|
+
}
|
|
61
|
+
console.log(` ${chalk.dim(`prave install ${slug}`)}`);
|
|
62
|
+
console.log(` ${chalk.dim(`${CONFIG.webUrl}/skills/${slug}`)}`);
|
|
63
|
+
console.log();
|
|
64
|
+
}
|
|
47
65
|
function truncate(s, n) {
|
|
48
66
|
return s.length <= n ? s : `${s.slice(0, n - 1)}…`;
|
|
49
67
|
}
|
|
@@ -4,6 +4,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
4
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
5
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
6
6
|
import { api, ApiError } from '../lib/api.js';
|
|
7
|
+
import { CONFIG } from '../lib/config.js';
|
|
7
8
|
import { loadCredentials } from '../lib/credentials.js';
|
|
8
9
|
const TOOLS = [
|
|
9
10
|
{
|
|
@@ -228,16 +229,26 @@ async function handleAdvisor(args) {
|
|
|
228
229
|
}
|
|
229
230
|
const { data } = await api.post('/api/v1/advisor', { mode, task: mode === 'manual' ? args.task : undefined }, true);
|
|
230
231
|
const lines = [data.prose, ''];
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
232
|
+
const hasReasoned = data.recommendations.length > 0;
|
|
233
|
+
if (hasReasoned) {
|
|
235
234
|
for (const rec of data.recommendations) {
|
|
236
235
|
lines.push(`• ${rec.slug}`);
|
|
237
236
|
lines.push(` Why: ${rec.reason}`);
|
|
238
237
|
lines.push(` Install: prave install ${rec.slug}`);
|
|
238
|
+
lines.push(` View: ${CONFIG.webUrl}/skills/${rec.slug}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else if (data.candidate_slugs.length > 0) {
|
|
242
|
+
lines.push('Starting points (no reasoned picks — browse these on prave.app):');
|
|
243
|
+
for (const slug of data.candidate_slugs) {
|
|
244
|
+
lines.push(`• ${slug}`);
|
|
245
|
+
lines.push(` Install: prave install ${slug}`);
|
|
246
|
+
lines.push(` View: ${CONFIG.webUrl}/skills/${slug}`);
|
|
239
247
|
}
|
|
240
248
|
}
|
|
249
|
+
else {
|
|
250
|
+
lines.push('(No specific Skills recommended for this context.)');
|
|
251
|
+
}
|
|
241
252
|
lines.push('');
|
|
242
253
|
lines.push(`Advisor quota: ${data.quota.used} / ${data.quota.limit} today on the ${data.quota.plan} plan.`);
|
|
243
254
|
return mcpText(lines.join('\n'));
|
package/dist/commands/usage.js
CHANGED
|
@@ -118,9 +118,30 @@ export async function usageScanCommand(opts) {
|
|
|
118
118
|
* - one POST attempt with a hard 4 s deadline (background-friendly)
|
|
119
119
|
* - on auth/network failure we log + bail; never throw
|
|
120
120
|
*/
|
|
121
|
+
/**
|
|
122
|
+
* Telemetry hook reporter. Reads the agent's hook payload from stdin,
|
|
123
|
+
* extracts the slug, and posts to /api/v1/intelligence/usage/by-slug.
|
|
124
|
+
*
|
|
125
|
+
* Forward-compat: `--agent` is parameterised so when Codex/Gemini/
|
|
126
|
+
* Cursor/Continue/Cline/Amp ship their own lifecycle-hook contracts,
|
|
127
|
+
* the install code only needs to write a different value into the
|
|
128
|
+
* generated hook command. The reporter itself stays the same.
|
|
129
|
+
*
|
|
130
|
+
* Today only `claude` produces hook payloads; the API endpoint accepts
|
|
131
|
+
* the full enum already (intelligence.routes.ts), so this CLI change
|
|
132
|
+
* unlocks the path with zero server work.
|
|
133
|
+
*/
|
|
134
|
+
const TRACKED_AGENTS = ['claude', 'codex', 'cursor', 'gemini', 'cline', 'amp'];
|
|
135
|
+
function normaliseAgent(input) {
|
|
136
|
+
const lower = (input ?? '').toLowerCase();
|
|
137
|
+
return TRACKED_AGENTS.includes(lower)
|
|
138
|
+
? lower
|
|
139
|
+
: 'claude';
|
|
140
|
+
}
|
|
121
141
|
export async function usageReportCommand(opts = {}) {
|
|
122
142
|
const debug = process.env.PRAVE_DEBUG === '1' || process.env.PRAVE_DEBUG === 'true';
|
|
123
143
|
const source = opts.source === 'prompt' ? 'prompt' : 'tool';
|
|
144
|
+
const agent_type = normaliseAgent(opts.agent);
|
|
124
145
|
await rotateHookLog();
|
|
125
146
|
const stdinPayload = await readStdin();
|
|
126
147
|
if (!stdinPayload) {
|
|
@@ -197,7 +218,7 @@ export async function usageReportCommand(opts = {}) {
|
|
|
197
218
|
if (!session) {
|
|
198
219
|
await bufferEvent({
|
|
199
220
|
slug,
|
|
200
|
-
agent_type
|
|
221
|
+
agent_type,
|
|
201
222
|
triggered_at,
|
|
202
223
|
meta: { ...meta, source },
|
|
203
224
|
});
|
|
@@ -209,7 +230,7 @@ export async function usageReportCommand(opts = {}) {
|
|
|
209
230
|
try {
|
|
210
231
|
const { data } = await api.post('/api/v1/intelligence/usage/by-slug', {
|
|
211
232
|
slug,
|
|
212
|
-
agent_type
|
|
233
|
+
agent_type,
|
|
213
234
|
triggered_at,
|
|
214
235
|
meta: { ...meta, source },
|
|
215
236
|
}, true);
|
|
@@ -223,7 +244,7 @@ export async function usageReportCommand(opts = {}) {
|
|
|
223
244
|
// the event isn't lost. The next successful command will replay.
|
|
224
245
|
await bufferEvent({
|
|
225
246
|
slug,
|
|
226
|
-
agent_type
|
|
247
|
+
agent_type,
|
|
227
248
|
triggered_at,
|
|
228
249
|
meta: { ...meta, source },
|
|
229
250
|
});
|
package/dist/index.js
CHANGED
|
@@ -141,6 +141,7 @@ usage
|
|
|
141
141
|
.command('report')
|
|
142
142
|
.description('Internal: invoked by the agent when a Skill fires (reads payload from stdin)')
|
|
143
143
|
.option('--source <kind>', 'event channel that fired this report', 'tool')
|
|
144
|
+
.option('--agent <name>', 'which agent fired the event (claude | codex | cursor | gemini | cline | amp). Forward-compat: only `claude` is wired by `prave usage hook install` today, but the parameter lets future installers point at other runtimes.', 'claude')
|
|
144
145
|
.action((opts) => usageReportCommand(opts));
|
|
145
146
|
const hook = usage.command('hook').description('Enable / disable real-time Skill invocation tracking');
|
|
146
147
|
hook
|
package/dist/lib/hook.js
CHANGED
|
@@ -43,11 +43,16 @@ const REGISTRY_CHANNELS = [
|
|
|
43
43
|
'PreCompact',
|
|
44
44
|
'SessionStart',
|
|
45
45
|
];
|
|
46
|
-
|
|
46
|
+
// Stamped with `--agent claude` so the reporter attributes the
|
|
47
|
+
// invocation to the right runtime in our usage telemetry. Forward-
|
|
48
|
+
// compat: when Codex/Gemini/Cursor add equivalent lifecycle hooks,
|
|
49
|
+
// the new per-agent install branch will write `--agent codex` (etc.)
|
|
50
|
+
// — the reporter itself already accepts the full enum.
|
|
51
|
+
const HOOK_COMMAND = 'prave usage report --agent=claude';
|
|
47
52
|
// Companion command for the UserPromptSubmit channel so a typed-slash
|
|
48
53
|
// `/graphify` is captured even when the Skill tool path doesn't fire a
|
|
49
54
|
// matching PostToolUse with a populated `tool_input.skill` field.
|
|
50
|
-
const PROMPT_HOOK_COMMAND = 'prave usage report --source=prompt';
|
|
55
|
+
const PROMPT_HOOK_COMMAND = 'prave usage report --source=prompt --agent=claude';
|
|
51
56
|
/**
|
|
52
57
|
* Install on BOTH `PostToolUse` (matcher `Skill`) and `UserPromptSubmit`
|
|
53
58
|
* (catches slash commands like `/graphify` before tool dispatch). The two
|
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
const PROJECTS_DIR = join(homedir(), '.claude', 'projects');
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
5
|
+
* Per-agent transcript roots. Today only `claude` ships a structured
|
|
6
|
+
* JSONL transcript at a known path; the other agents either don't
|
|
7
|
+
* persist transcripts in a comparable shape (Codex, Gemini CLI), keep
|
|
8
|
+
* them in opaque formats (Cursor, Continue), or don't expose them on
|
|
9
|
+
* disk at all (Cline, Amp).
|
|
10
|
+
*
|
|
11
|
+
* Forward-compat: when an agent publishes a JSONL-style transcript
|
|
12
|
+
* convention with `tool_use`-shaped lines, drop an entry into the map
|
|
13
|
+
* and the scanner picks it up automatically. The `extractEventsFromLine`
|
|
14
|
+
* parser is JSON-shape-agnostic — it walks any object tree looking for
|
|
15
|
+
* a `name === 'Skill'` tool-use plus the canonical `<command-name>`
|
|
16
|
+
* marker. As long as the new agent's transcript surfaces those it's a
|
|
17
|
+
* zero-code-change integration.
|
|
18
|
+
*/
|
|
19
|
+
const AGENT_TRANSCRIPT_ROOTS = {
|
|
20
|
+
claude: join(homedir(), '.claude', 'projects'),
|
|
21
|
+
// codex: join(homedir(), '.codex', 'projects'), // TBD — Codex transcript path
|
|
22
|
+
// gemini: join(homedir(), '.gemini-cli', 'sessions'), // TBD — Gemini CLI session path
|
|
23
|
+
// cursor: … // currently no disk transcript
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Scan every .jsonl transcript newer than `since` for Skill invocations
|
|
27
|
+
* across every agent we know how to read.
|
|
7
28
|
*
|
|
8
29
|
* @param installedSlugs the slugs we recognise — anything outside this
|
|
9
30
|
* set is ignored to avoid noise from quoted text.
|
|
@@ -18,9 +39,15 @@ export async function scanTranscriptsForUsage(installedSlugs, since) {
|
|
|
18
39
|
const slugSet = new Set(installedSlugs);
|
|
19
40
|
const sinceMs = since.getTime();
|
|
20
41
|
const events = [];
|
|
21
|
-
const
|
|
42
|
+
for (const root of Object.values(AGENT_TRANSCRIPT_ROOTS)) {
|
|
43
|
+
await scanTranscriptRoot(root, slugSet, sinceMs, events);
|
|
44
|
+
}
|
|
45
|
+
return events;
|
|
46
|
+
}
|
|
47
|
+
async function scanTranscriptRoot(root, slugSet, sinceMs, events) {
|
|
48
|
+
const projectDirs = await safeReaddir(root);
|
|
22
49
|
for (const projectName of projectDirs) {
|
|
23
|
-
const projectDir = join(
|
|
50
|
+
const projectDir = join(root, projectName);
|
|
24
51
|
const projectStat = await safeStat(projectDir);
|
|
25
52
|
if (!projectStat?.isDirectory())
|
|
26
53
|
continue;
|
|
@@ -46,7 +73,6 @@ export async function scanTranscriptsForUsage(installedSlugs, since) {
|
|
|
46
73
|
}
|
|
47
74
|
}
|
|
48
75
|
}
|
|
49
|
-
return events;
|
|
50
76
|
}
|
|
51
77
|
const COMMAND_NAME_RE = /<command-name>\s*\/?([a-z0-9][a-z0-9_-]{0,80})\s*<\/command-name>/gi;
|
|
52
78
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prave/cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
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.
|
|
57
|
+
"@prave/shared": "1.5.2"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^20.12.7",
|