@prave/cli 1.0.6 → 1.0.8
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/find.js +16 -0
- package/dist/commands/login.js +4 -0
- package/dist/commands/usage.js +36 -3
- package/dist/lib/api.js +22 -1
- package/package.json +2 -2
package/dist/commands/find.js
CHANGED
|
@@ -76,6 +76,22 @@ export async function findCommand(query, opts = {}) {
|
|
|
76
76
|
}
|
|
77
77
|
catch (err) {
|
|
78
78
|
spinner.stop();
|
|
79
|
+
if (err instanceof ApiError && err.status === 402) {
|
|
80
|
+
// Server returned the semantic-search upsell. Render it as a
|
|
81
|
+
// structured upgrade hint instead of a raw error so the message
|
|
82
|
+
// reads like guidance, not a crash.
|
|
83
|
+
log.error(err.message);
|
|
84
|
+
console.log();
|
|
85
|
+
console.log(chalk.dim(' Pro · $12/mo includes:'));
|
|
86
|
+
console.log(chalk.dim(' · `prave search "<natural language>"` ranked by intent'));
|
|
87
|
+
console.log(chalk.dim(' · Skill Intelligence audit + 30-day trigger telemetry'));
|
|
88
|
+
console.log(chalk.dim(' · Cross-machine sync · Tester · Authoring'));
|
|
89
|
+
console.log();
|
|
90
|
+
console.log(` ${chalk.bold('→ Upgrade:')} ${chalk.cyan('https://prave.app/#pricing')}`);
|
|
91
|
+
console.log(chalk.dim(' Or browse the registry without semantic search:'), chalk.cyan('https://prave.app/discover'));
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
79
95
|
log.error(err instanceof ApiError ? err.message : err.message);
|
|
80
96
|
process.exitCode = 1;
|
|
81
97
|
}
|
package/dist/commands/login.js
CHANGED
|
@@ -31,6 +31,10 @@ export async function loginCommand() {
|
|
|
31
31
|
access_token: data.access_token,
|
|
32
32
|
refresh_token: data.refresh_token,
|
|
33
33
|
user_id: data.user_id,
|
|
34
|
+
// Server hands us the JWT's `exp` claim so the CLI can refresh
|
|
35
|
+
// proactively. Stored as unix-seconds; null when the server
|
|
36
|
+
// couldn't decode it (very old creds, mock mode).
|
|
37
|
+
expires_at: data.expires_at ?? undefined,
|
|
34
38
|
});
|
|
35
39
|
spinner.succeed('Logged in.');
|
|
36
40
|
// Onboarding: prefill from the SaaS profile, let the user toggle
|
package/dist/commands/usage.js
CHANGED
|
@@ -149,10 +149,43 @@ export async function usageReportCommand() {
|
|
|
149
149
|
await debugLog('no auth — skipping');
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
|
+
// Build the telemetry blob from whatever Claude Code shipped in the
|
|
153
|
+
// payload. Everything is best-effort — missing fields just don't
|
|
154
|
+
// appear in `meta`. The server schema (usageMetaSchema) validates
|
|
155
|
+
// primitives only, so we coerce here.
|
|
156
|
+
const meta = {
|
|
157
|
+
payload_bytes: stdinPayload.length,
|
|
158
|
+
};
|
|
159
|
+
const tr = parsed.tool_response;
|
|
160
|
+
if (tr && typeof tr === 'object' && tr !== null) {
|
|
161
|
+
const obj = tr;
|
|
162
|
+
if (typeof obj.duration_ms === 'number')
|
|
163
|
+
meta.duration_ms = obj.duration_ms;
|
|
164
|
+
if (typeof obj.duration === 'number')
|
|
165
|
+
meta.duration_ms = obj.duration;
|
|
166
|
+
if (typeof obj.output === 'string')
|
|
167
|
+
meta.output_chars = obj.output.length;
|
|
168
|
+
}
|
|
169
|
+
if (typeof parsed.tool_name === 'string')
|
|
170
|
+
meta.tool_name = parsed.tool_name;
|
|
171
|
+
if (typeof parsed.session_id === 'string')
|
|
172
|
+
meta.agent_session = parsed.session_id;
|
|
173
|
+
const ti = parsed.tool_input;
|
|
174
|
+
if (ti && typeof ti === 'object' && ti !== null) {
|
|
175
|
+
const obj = ti;
|
|
176
|
+
if (typeof obj.prompt === 'string')
|
|
177
|
+
meta.prompt_chars = obj.prompt.length;
|
|
178
|
+
}
|
|
152
179
|
try {
|
|
153
|
-
const { data } = await api.post('/api/v1/intelligence/usage/by-slug', {
|
|
154
|
-
|
|
155
|
-
|
|
180
|
+
const { data } = await api.post('/api/v1/intelligence/usage/by-slug', {
|
|
181
|
+
slug,
|
|
182
|
+
agent_type: 'claude',
|
|
183
|
+
triggered_at: new Date().toISOString(),
|
|
184
|
+
meta,
|
|
185
|
+
}, true);
|
|
186
|
+
if (debug) {
|
|
187
|
+
await debugLog(`ok recorded=${data.recorded} stub=${data.created_stub} meta=${JSON.stringify(meta)}`);
|
|
188
|
+
}
|
|
156
189
|
}
|
|
157
190
|
catch (err) {
|
|
158
191
|
if (debug)
|
package/dist/lib/api.js
CHANGED
|
@@ -39,6 +39,8 @@ async function refreshTokens() {
|
|
|
39
39
|
access_token: payload.data.access_token,
|
|
40
40
|
refresh_token: payload.data.refresh_token ?? creds.refresh_token,
|
|
41
41
|
user_id: payload.data.user_id,
|
|
42
|
+
// Persist the new expiry so the next call's proactive check works.
|
|
43
|
+
expires_at: payload.data.expires_at ?? undefined,
|
|
42
44
|
};
|
|
43
45
|
await saveCredentials(next);
|
|
44
46
|
return next;
|
|
@@ -54,12 +56,31 @@ async function refreshTokens() {
|
|
|
54
56
|
refreshing = null;
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Pre-flight check: if the access token is expired or within 60s of
|
|
61
|
+
* expiring, refresh BEFORE the call. The post-call 401 retry below
|
|
62
|
+
* still catches the race where the token slips between this check and
|
|
63
|
+
* the server-side verify, but doing it proactively avoids the wasted
|
|
64
|
+
* round-trip on every long-idle session.
|
|
65
|
+
*/
|
|
66
|
+
async function ensureFreshAccessToken(creds) {
|
|
67
|
+
const exp = creds.expires_at;
|
|
68
|
+
if (!exp)
|
|
69
|
+
return creds; // legacy creds without expiry → 401 path takes over
|
|
70
|
+
const now = Math.floor(Date.now() / 1000);
|
|
71
|
+
const SKEW = 60;
|
|
72
|
+
if (exp > now + SKEW)
|
|
73
|
+
return creds;
|
|
74
|
+
const refreshed = await refreshTokens();
|
|
75
|
+
return refreshed ?? creds;
|
|
76
|
+
}
|
|
57
77
|
async function call(method, path, body, withAuth = false, attempt = 0) {
|
|
58
78
|
const headers = { 'Content-Type': 'application/json' };
|
|
59
79
|
if (withAuth) {
|
|
60
|
-
|
|
80
|
+
let creds = await loadCredentials();
|
|
61
81
|
if (!creds)
|
|
62
82
|
throw new ApiError('Not logged in. Run `prave login`.', 401);
|
|
83
|
+
creds = await ensureFreshAccessToken(creds);
|
|
63
84
|
headers.Authorization = `Bearer ${creds.access_token}`;
|
|
64
85
|
}
|
|
65
86
|
const { statusCode, body: resBody } = await request(`${CONFIG.apiUrl}${path}`, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prave/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Prave CLI — import, export, install, sync Claude Skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"open": "^10.1.0",
|
|
17
17
|
"ora": "^8.0.1",
|
|
18
18
|
"undici": "^6.18.0",
|
|
19
|
-
"@prave/shared": "1.0.
|
|
19
|
+
"@prave/shared": "1.0.8"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^20.12.7",
|