@prave/cli 1.4.0 โ 1.4.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.
- package/dist/commands/list.js +2 -0
- package/dist/commands/search.js +2 -0
- package/dist/commands/uninstall.js +38 -7
- package/dist/commands/whatdoes.js +2 -0
- package/dist/lib/nudge.js +61 -0
- package/package.json +6 -4
- package/scripts/postinstall.mjs +31 -0
package/dist/commands/list.js
CHANGED
|
@@ -5,6 +5,7 @@ import { tokenTier } from '@prave/shared';
|
|
|
5
5
|
import { track } from '../lib/analytics.js';
|
|
6
6
|
import { api } from '../lib/api.js';
|
|
7
7
|
import { CONFIG } from '../lib/config.js';
|
|
8
|
+
import { nudgeIfAnonymous } from '../lib/nudge.js';
|
|
8
9
|
import { log } from '../utils/logger.js';
|
|
9
10
|
const TIER_EMOJI = {
|
|
10
11
|
lean: '๐ข',
|
|
@@ -68,6 +69,7 @@ export async function listCommand(opts = {}) {
|
|
|
68
69
|
console.log(` ${chalk.cyan('โข')} ${name}`);
|
|
69
70
|
}
|
|
70
71
|
log.dim(`\n${localSlugs.length} local skill${localSlugs.length === 1 ? '' : 's'} in ${CONFIG.skillsDir}`);
|
|
72
|
+
await nudgeIfAnonymous('list');
|
|
71
73
|
return;
|
|
72
74
|
}
|
|
73
75
|
// Enriched path โ pull intelligence and merge by slug (best-effort).
|
package/dist/commands/search.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { track } from '../lib/analytics.js';
|
|
3
3
|
import { api } from '../lib/api.js';
|
|
4
|
+
import { nudgeIfAnonymous } from '../lib/nudge.js';
|
|
4
5
|
import { log } from '../utils/logger.js';
|
|
5
6
|
const SLUG_COL = 32;
|
|
6
7
|
function formatInstalls(n) {
|
|
@@ -41,4 +42,5 @@ export async function searchCommand(query) {
|
|
|
41
42
|
if (skills.length > 0) {
|
|
42
43
|
console.log(chalk.dim(' โ prave install <slug>'));
|
|
43
44
|
}
|
|
45
|
+
await nudgeIfAnonymous('search');
|
|
44
46
|
}
|
|
@@ -2,15 +2,23 @@ import { rm } from 'node:fs/promises';
|
|
|
2
2
|
import { join, resolve, sep } from 'node:path';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { track } from '../lib/analytics.js';
|
|
5
|
+
import { api, ApiError } from '../lib/api.js';
|
|
5
6
|
import { CONFIG } from '../lib/config.js';
|
|
7
|
+
import { loadCredentials } from '../lib/credentials.js';
|
|
6
8
|
import { assertSlug, InvalidSlugError } from '../lib/slug.js';
|
|
7
9
|
/**
|
|
8
|
-
* `prave uninstall <slug>` โ removes ~/.claude/skills/<slug>
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* `prave uninstall <slug>` โ removes ~/.claude/skills/<slug> AND tells
|
|
11
|
+
* the server to drop the corresponding install record so the
|
|
12
|
+
* dashboard counter goes down.
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* The remote DELETE is best-effort: a network failure or "not logged
|
|
15
|
+
* in" state still lets the local rm succeed (so the user isn't stuck
|
|
16
|
+
* with dead files when offline). The mismatch then heals on the next
|
|
17
|
+
* `prave sync` or manual dashboard cleanup.
|
|
18
|
+
*
|
|
19
|
+
* Slug is validated against [a-z0-9][a-z0-9-]{0,63} before any path
|
|
20
|
+
* math so a hostile arg like `../../etc` cannot escape the skills
|
|
21
|
+
* directory.
|
|
14
22
|
*/
|
|
15
23
|
export async function uninstallCommand(slug) {
|
|
16
24
|
track('cli_uninstall', { slug });
|
|
@@ -37,10 +45,33 @@ export async function uninstallCommand(slug) {
|
|
|
37
45
|
const spinner = ora(`Removing ${slug}โฆ`).start();
|
|
38
46
|
try {
|
|
39
47
|
await rm(dir, { recursive: true, force: true });
|
|
40
|
-
spinner.succeed(`Removed ${dir}`);
|
|
41
48
|
}
|
|
42
49
|
catch (err) {
|
|
43
|
-
spinner.fail(`Couldn
|
|
50
|
+
spinner.fail(`Couldn't remove ${slug}: ${err.message}`);
|
|
44
51
|
process.exitCode = 1;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Best-effort server-side ledger sweep. We only attempt it when the
|
|
55
|
+
// user is logged in โ anonymous CLI usage (running before
|
|
56
|
+
// `prave login`) still does the local rm and exits cleanly.
|
|
57
|
+
const creds = await loadCredentials();
|
|
58
|
+
if (creds) {
|
|
59
|
+
try {
|
|
60
|
+
await api.del(`/api/v1/skills/${encodeURIComponent(slug)}/install`, true);
|
|
61
|
+
spinner.succeed(`Removed ${slug} (local + server install record)`);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
// 404 = install record already absent (matches our idempotent
|
|
65
|
+
// server behaviour). Anything else is a soft warning.
|
|
66
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
67
|
+
spinner.succeed(`Removed ${slug} (local; server had no install record)`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
spinner.warn(`Removed ${slug} locally โ server ledger update failed (${err.message}). Run \`prave sync\` later to reconcile.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
spinner.succeed(`Removed ${slug} (local only โ not signed in)`);
|
|
45
76
|
}
|
|
46
77
|
}
|
|
@@ -3,6 +3,7 @@ import ora from 'ora';
|
|
|
3
3
|
import { tokenTier } from '@prave/shared';
|
|
4
4
|
import { track } from '../lib/analytics.js';
|
|
5
5
|
import { api, ApiError } from '../lib/api.js';
|
|
6
|
+
import { nudgeIfAnonymous } from '../lib/nudge.js';
|
|
6
7
|
import { log } from '../utils/logger.js';
|
|
7
8
|
const TIER_BADGE = {
|
|
8
9
|
lean: chalk.green('๐ข Lean'),
|
|
@@ -71,6 +72,7 @@ export async function whatdoesCommand(skillName) {
|
|
|
71
72
|
console.log(`๐ Requires: ${requires}`);
|
|
72
73
|
console.log(`${data.conflicts.length > 0 ? chalk.yellow('โ ๏ธ ') : 'โ ๏ธ '}Conflicts: ${conflicts}`);
|
|
73
74
|
console.log(chalk.dim(RULE));
|
|
75
|
+
await nudgeIfAnonymous('whatdoes');
|
|
74
76
|
}
|
|
75
77
|
catch (err) {
|
|
76
78
|
spinner.stop();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadCredentials } from './credentials.js';
|
|
3
|
+
let alreadyNudged = false;
|
|
4
|
+
export async function nudgeIfAnonymous(context = 'generic') {
|
|
5
|
+
if (alreadyNudged)
|
|
6
|
+
return;
|
|
7
|
+
if (process.env.PRAVE_QUIET === '1')
|
|
8
|
+
return;
|
|
9
|
+
if (process.env.PRAVE_TELEMETRY === '0')
|
|
10
|
+
return; // user opted out of analytics
|
|
11
|
+
// CI / non-interactive shells are unlikely to act on a nudge โ and
|
|
12
|
+
// the noise breaks pipe-parsing scripts.
|
|
13
|
+
if (!process.stdout.isTTY)
|
|
14
|
+
return;
|
|
15
|
+
const creds = await loadCredentials();
|
|
16
|
+
if (creds)
|
|
17
|
+
return; // logged-in โ no nudge
|
|
18
|
+
alreadyNudged = true;
|
|
19
|
+
const cta = chalk.cyan('prave login');
|
|
20
|
+
const url = chalk.dim('โ takes 10 seconds, free forever');
|
|
21
|
+
const message = (() => {
|
|
22
|
+
switch (context) {
|
|
23
|
+
case 'search':
|
|
24
|
+
return `${chalk.dim('Save these to your library, get token costs + conflicts.')}\n${chalk.dim('โ')} ${cta} ${url}`;
|
|
25
|
+
case 'whatdoes':
|
|
26
|
+
return `${chalk.dim('Want the full audit โ triggers, conflicts, token cost?')}\n${chalk.dim('โ')} ${cta} ${url}`;
|
|
27
|
+
case 'list':
|
|
28
|
+
return `${chalk.dim('Sign in to see AI descriptions, conflicts and token cost.')}\n${chalk.dim('โ')} ${cta} ${url}`;
|
|
29
|
+
case 'overview':
|
|
30
|
+
return `${chalk.dim('Track this over time on prave.app.')}\n${chalk.dim('โ')} ${cta} ${url}`;
|
|
31
|
+
case 'generic':
|
|
32
|
+
default:
|
|
33
|
+
return `${chalk.dim('Get the full Skill Intelligence on prave.app.')}\n${chalk.dim('โ')} ${cta} ${url}`;
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
36
|
+
// One blank line so the nudge isn't glued to the command's main
|
|
37
|
+
// output. Two `console.log` calls so the chalk styles survive the
|
|
38
|
+
// pipeline cleanly.
|
|
39
|
+
console.log();
|
|
40
|
+
console.log(message);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Post-install banner used by package.json's `postinstall` script.
|
|
44
|
+
* Runs once after `npm i -g @prave/cli`. Stays short โ npm's install
|
|
45
|
+
* log is already crowded.
|
|
46
|
+
*/
|
|
47
|
+
export function printPostInstallBanner() {
|
|
48
|
+
if (process.env.CI)
|
|
49
|
+
return;
|
|
50
|
+
if (process.env.PRAVE_QUIET === '1')
|
|
51
|
+
return;
|
|
52
|
+
console.log();
|
|
53
|
+
console.log(chalk.bold(` Prave CLI installed.`));
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(` ${chalk.cyan('prave login')} ${chalk.dim('โ create your free account (browser)')}`);
|
|
56
|
+
console.log(` ${chalk.cyan('prave search <q>')} ${chalk.dim('โ find any Claude Skill')}`);
|
|
57
|
+
console.log(` ${chalk.cyan('prave docs')} ${chalk.dim('โ open the docs')}`);
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(chalk.dim(' Docs: https://prave.app/docs ยท Issues: github.com/eppstudio/prave'));
|
|
60
|
+
console.log();
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prave/cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.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": [
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
},
|
|
44
44
|
"main": "dist/index.js",
|
|
45
45
|
"files": [
|
|
46
|
-
"dist"
|
|
46
|
+
"dist",
|
|
47
|
+
"scripts/postinstall.mjs"
|
|
47
48
|
],
|
|
48
49
|
"dependencies": {
|
|
49
50
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
"ora": "^8.0.1",
|
|
54
55
|
"tar": "^7.4.3",
|
|
55
56
|
"undici": "^6.18.0",
|
|
56
|
-
"@prave/shared": "1.4.
|
|
57
|
+
"@prave/shared": "1.4.1"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"@types/node": "^20.12.7",
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"cli": "tsx src/index.ts",
|
|
70
71
|
"build": "tsc -p tsconfig.json && node scripts/inject-config.mjs",
|
|
71
72
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
72
|
-
"lint": "tsc -p tsconfig.json --noEmit"
|
|
73
|
+
"lint": "tsc -p tsconfig.json --noEmit",
|
|
74
|
+
"postinstall": "node scripts/postinstall.mjs"
|
|
73
75
|
}
|
|
74
76
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* npm postinstall hook. Runs once after `npm i -g @prave/cli` (and on
|
|
4
|
+
* upgrades). Prints a small "you're set โ what now?" banner so the
|
|
5
|
+
* thousands of CLI installs we're seeing actually find the dashboard
|
|
6
|
+
* + the docs.
|
|
7
|
+
*
|
|
8
|
+
* Silent in CI / non-interactive shells / when PRAVE_QUIET=1 is set.
|
|
9
|
+
* Also silent on dependent installs โ npm sets `npm_config_global` to
|
|
10
|
+
* 'false' or unset when we're being installed as a dependency, and
|
|
11
|
+
* the banner is only useful for the global-install user.
|
|
12
|
+
*/
|
|
13
|
+
if (process.env.CI) process.exit(0)
|
|
14
|
+
if (process.env.PRAVE_QUIET === '1') process.exit(0)
|
|
15
|
+
// `npm_config_global` is `'true'` only for `npm i -g`. Skip for local
|
|
16
|
+
// dependency installs (e.g. when our worker container builds).
|
|
17
|
+
if (process.env.npm_config_global !== 'true') process.exit(0)
|
|
18
|
+
|
|
19
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`
|
|
20
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`
|
|
21
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`
|
|
22
|
+
|
|
23
|
+
console.log()
|
|
24
|
+
console.log(` ${bold('Prave CLI installed.')}`)
|
|
25
|
+
console.log()
|
|
26
|
+
console.log(` ${cyan('prave login')} ${dim('โ create your free account (browser)')}`)
|
|
27
|
+
console.log(` ${cyan('prave search <q>')} ${dim('โ find any Claude Skill')}`)
|
|
28
|
+
console.log(` ${cyan('prave docs')} ${dim('โ open the docs')}`)
|
|
29
|
+
console.log()
|
|
30
|
+
console.log(dim(' Docs: https://prave.app/docs ยท Issues: github.com/eppstudio/prave'))
|
|
31
|
+
console.log()
|