skillsets 0.1.2 → 0.2.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/README.md +23 -1
- package/dist/commands/install.js +7 -1
- package/dist/commands/list.d.ts +1 -1
- package/dist/commands/list.js +13 -5
- package/dist/commands/search.js +7 -5
- package/dist/index.js +1 -1
- package/dist/lib/api.d.ts +10 -1
- package/dist/lib/api.js +40 -1
- package/dist/lib/constants.d.ts +2 -0
- package/dist/lib/constants.js +2 -0
- package/dist/types/index.d.ts +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,9 @@ Command-line tool for discovering, installing, and contributing verified Claude
|
|
|
8
8
|
# Browse available skillsets
|
|
9
9
|
npx skillsets list
|
|
10
10
|
|
|
11
|
+
# Sort by popularity
|
|
12
|
+
npx skillsets list --sort downloads
|
|
13
|
+
|
|
11
14
|
# Search by keyword
|
|
12
15
|
npx skillsets search "sdlc"
|
|
13
16
|
|
|
@@ -19,7 +22,7 @@ npx skillsets install @supercollectible/The_Skillset
|
|
|
19
22
|
|
|
20
23
|
| Command | Purpose |
|
|
21
24
|
|---------|---------|
|
|
22
|
-
| `list` | Browse all
|
|
25
|
+
| `list` | Browse all skillsets with live star/download counts |
|
|
23
26
|
| `search <query>` | Fuzzy search by name, description, or tags |
|
|
24
27
|
| `install <id>` | Install skillset to current directory |
|
|
25
28
|
| `verify` | Verify installed skillset checksums |
|
|
@@ -27,6 +30,25 @@ npx skillsets install @supercollectible/The_Skillset
|
|
|
27
30
|
| `audit` | Validate skillset before submission |
|
|
28
31
|
| `submit` | Open PR to registry (requires `gh` CLI) |
|
|
29
32
|
|
|
33
|
+
## Options
|
|
34
|
+
|
|
35
|
+
### list
|
|
36
|
+
- `-l, --limit <n>` - Limit number of results
|
|
37
|
+
- `-s, --sort <field>` - Sort by: `name`, `stars`, `downloads`
|
|
38
|
+
- `--json` - Output as JSON
|
|
39
|
+
|
|
40
|
+
### search
|
|
41
|
+
- `-t, --tags <tags...>` - Filter by tags
|
|
42
|
+
- `-l, --limit <n>` - Limit results (default: 10)
|
|
43
|
+
|
|
44
|
+
### install
|
|
45
|
+
- `-f, --force` - Overwrite existing files
|
|
46
|
+
- `-b, --backup` - Backup existing files before install
|
|
47
|
+
|
|
48
|
+
## Live Stats
|
|
49
|
+
|
|
50
|
+
The CLI fetches live star and download counts from the API, so you always see current numbers (not stale build-time data).
|
|
51
|
+
|
|
30
52
|
## Development
|
|
31
53
|
|
|
32
54
|
```bash
|
package/dist/commands/install.js
CHANGED
|
@@ -3,7 +3,7 @@ import chalk from 'chalk';
|
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { detectConflicts, backupFiles } from '../lib/filesystem.js';
|
|
5
5
|
import { verifyChecksums } from '../lib/checksum.js';
|
|
6
|
-
import { REGISTRY_REPO } from '../lib/constants.js';
|
|
6
|
+
import { REGISTRY_REPO, DOWNLOADS_URL } from '../lib/constants.js';
|
|
7
7
|
export async function install(skillsetId, options) {
|
|
8
8
|
const spinner = ora(`Installing ${skillsetId}...`).start();
|
|
9
9
|
// Check for conflicts
|
|
@@ -45,6 +45,12 @@ export async function install(skillsetId, options) {
|
|
|
45
45
|
process.exit(1);
|
|
46
46
|
}
|
|
47
47
|
spinner.succeed(`Successfully installed ${skillsetId}`);
|
|
48
|
+
// Track download (non-blocking, silent fail)
|
|
49
|
+
fetch(DOWNLOADS_URL, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/json' },
|
|
52
|
+
body: JSON.stringify({ skillset: skillsetId }),
|
|
53
|
+
}).catch(() => { });
|
|
48
54
|
// Print next steps
|
|
49
55
|
console.log(chalk.green('\n✓ Installation complete!'));
|
|
50
56
|
console.log(chalk.gray('\nNext steps:'));
|
package/dist/commands/list.d.ts
CHANGED
package/dist/commands/list.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
-
import { fetchSearchIndex } from '../lib/api.js';
|
|
3
|
+
import { fetchSearchIndex, fetchStats, mergeStats } from '../lib/api.js';
|
|
4
4
|
export async function list(options) {
|
|
5
5
|
const spinner = ora('Fetching skillsets...').start();
|
|
6
6
|
try {
|
|
7
|
-
|
|
7
|
+
// Fetch index and live stats in parallel
|
|
8
|
+
const [index, stats] = await Promise.all([fetchSearchIndex(), fetchStats()]);
|
|
8
9
|
spinner.stop();
|
|
9
|
-
|
|
10
|
+
// Merge live stats into skillsets
|
|
11
|
+
let skillsets = mergeStats(index.skillsets, stats);
|
|
10
12
|
// Sort
|
|
11
13
|
const sortBy = options.sort || 'name';
|
|
12
14
|
if (sortBy === 'stars') {
|
|
13
15
|
skillsets.sort((a, b) => b.stars - a.stars);
|
|
14
16
|
}
|
|
17
|
+
else if (sortBy === 'downloads') {
|
|
18
|
+
skillsets.sort((a, b) => (b.downloads ?? 0) - (a.downloads ?? 0));
|
|
19
|
+
}
|
|
15
20
|
else if (sortBy === 'name') {
|
|
16
21
|
skillsets.sort((a, b) => a.name.localeCompare(b.name));
|
|
17
22
|
}
|
|
@@ -38,17 +43,20 @@ export async function list(options) {
|
|
|
38
43
|
console.log(chalk.gray(padEnd('NAME', 30) +
|
|
39
44
|
padEnd('AUTHOR', 20) +
|
|
40
45
|
padEnd('STARS', 8) +
|
|
46
|
+
padEnd('INSTALLS', 10) +
|
|
41
47
|
'DESCRIPTION'));
|
|
42
|
-
console.log(chalk.gray('─'.repeat(
|
|
48
|
+
console.log(chalk.gray('─'.repeat(110)));
|
|
43
49
|
// Rows
|
|
44
50
|
for (const s of skillsets) {
|
|
45
51
|
const name = padEnd(s.name, 30);
|
|
46
52
|
const author = padEnd(s.author.handle, 20);
|
|
47
53
|
const stars = padEnd(`★ ${s.stars}`, 8);
|
|
48
|
-
const
|
|
54
|
+
const downloads = padEnd(`↓ ${s.downloads ?? 0}`, 10);
|
|
55
|
+
const desc = truncate(s.description, 32);
|
|
49
56
|
console.log(chalk.bold(name) +
|
|
50
57
|
chalk.gray(author) +
|
|
51
58
|
chalk.yellow(stars) +
|
|
59
|
+
chalk.gray(downloads) +
|
|
52
60
|
desc);
|
|
53
61
|
}
|
|
54
62
|
console.log('');
|
package/dist/commands/search.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import Fuse from 'fuse.js';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import { fetchSearchIndex } from '../lib/api.js';
|
|
3
|
+
import { fetchSearchIndex, fetchStats, mergeStats } from '../lib/api.js';
|
|
4
4
|
import { DEFAULT_SEARCH_LIMIT } from '../lib/constants.js';
|
|
5
5
|
export async function search(query, options) {
|
|
6
6
|
console.log(chalk.blue(`Searching for: ${query}`));
|
|
7
|
-
// Fetch index
|
|
8
|
-
const index = await fetchSearchIndex();
|
|
7
|
+
// Fetch index and live stats in parallel
|
|
8
|
+
const [index, stats] = await Promise.all([fetchSearchIndex(), fetchStats()]);
|
|
9
|
+
// Merge live stats into skillsets
|
|
10
|
+
const skillsetsWithStats = mergeStats(index.skillsets, stats);
|
|
9
11
|
// Filter by tags if provided
|
|
10
|
-
let filtered =
|
|
12
|
+
let filtered = skillsetsWithStats;
|
|
11
13
|
if (options.tags && options.tags.length > 0) {
|
|
12
14
|
filtered = filtered.filter((skillset) => options.tags.some((tag) => skillset.tags.includes(tag)));
|
|
13
15
|
}
|
|
@@ -28,7 +30,7 @@ export async function search(query, options) {
|
|
|
28
30
|
console.log(chalk.bold(item.name));
|
|
29
31
|
console.log(` ${item.description}`);
|
|
30
32
|
console.log(` ${chalk.gray(`by ${item.author.handle}`)}`);
|
|
31
|
-
console.log(` ${chalk.yellow(`★ ${item.stars}`)} ${chalk.gray(`• v${item.version}`)}`);
|
|
33
|
+
console.log(` ${chalk.yellow(`★ ${item.stars}`)} ${chalk.gray(`↓ ${item.downloads ?? 0}`)} ${chalk.gray(`• v${item.version}`)}`);
|
|
32
34
|
console.log(` ${chalk.cyan(`npx skillsets install ${item.id}`)}`);
|
|
33
35
|
console.log();
|
|
34
36
|
});
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ program
|
|
|
17
17
|
.command('list')
|
|
18
18
|
.description('List all available skillsets')
|
|
19
19
|
.option('-l, --limit <number>', 'Limit results')
|
|
20
|
-
.option('-s, --sort <field>', 'Sort by: name, stars (default: name)')
|
|
20
|
+
.option('-s, --sort <field>', 'Sort by: name, stars, downloads (default: name)')
|
|
21
21
|
.option('--json', 'Output as JSON')
|
|
22
22
|
.action(async (options) => {
|
|
23
23
|
try {
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SearchIndex, SearchIndexEntry } from '../types/index.js';
|
|
1
|
+
import type { SearchIndex, SearchIndexEntry, StatsResponse } from '../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Fetches the search index from the CDN.
|
|
4
4
|
* Implements 1-hour local cache to reduce network requests.
|
|
@@ -8,3 +8,12 @@ export declare function fetchSearchIndex(): Promise<SearchIndex>;
|
|
|
8
8
|
* Fetches metadata for a specific skillset by ID.
|
|
9
9
|
*/
|
|
10
10
|
export declare function fetchSkillsetMetadata(skillsetId: string): Promise<SearchIndexEntry | undefined>;
|
|
11
|
+
/**
|
|
12
|
+
* Fetches live star and download counts from the API.
|
|
13
|
+
* Implements 1-minute local cache.
|
|
14
|
+
*/
|
|
15
|
+
export declare function fetchStats(): Promise<StatsResponse>;
|
|
16
|
+
/**
|
|
17
|
+
* Merges live stats into skillset entries.
|
|
18
|
+
*/
|
|
19
|
+
export declare function mergeStats(skillsets: SearchIndexEntry[], stats: StatsResponse): SearchIndexEntry[];
|
package/dist/lib/api.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { SEARCH_INDEX_URL, CACHE_TTL_MS } from './constants.js';
|
|
1
|
+
import { SEARCH_INDEX_URL, STATS_URL, CACHE_TTL_MS } from './constants.js';
|
|
2
2
|
let cachedIndex = null;
|
|
3
3
|
let cacheTime = 0;
|
|
4
|
+
let cachedStats = null;
|
|
5
|
+
let statsCacheTime = 0;
|
|
4
6
|
/**
|
|
5
7
|
* Fetches the search index from the CDN.
|
|
6
8
|
* Implements 1-hour local cache to reduce network requests.
|
|
@@ -27,3 +29,40 @@ export async function fetchSkillsetMetadata(skillsetId) {
|
|
|
27
29
|
const index = await fetchSearchIndex();
|
|
28
30
|
return index.skillsets.find((s) => s.id === skillsetId);
|
|
29
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Fetches live star and download counts from the API.
|
|
34
|
+
* Implements 1-minute local cache.
|
|
35
|
+
*/
|
|
36
|
+
export async function fetchStats() {
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
const STATS_CACHE_TTL_MS = 60 * 1000; // 1 minute for stats
|
|
39
|
+
// Return cached if still valid
|
|
40
|
+
if (cachedStats && now - statsCacheTime < STATS_CACHE_TTL_MS) {
|
|
41
|
+
return cachedStats;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch(STATS_URL);
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
// Return empty stats on error, don't break the command
|
|
47
|
+
return { stars: {}, downloads: {} };
|
|
48
|
+
}
|
|
49
|
+
const data = (await response.json());
|
|
50
|
+
cachedStats = data;
|
|
51
|
+
statsCacheTime = now;
|
|
52
|
+
return data;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Return empty stats on network error
|
|
56
|
+
return { stars: {}, downloads: {} };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Merges live stats into skillset entries.
|
|
61
|
+
*/
|
|
62
|
+
export function mergeStats(skillsets, stats) {
|
|
63
|
+
return skillsets.map((s) => ({
|
|
64
|
+
...s,
|
|
65
|
+
stars: stats.stars[s.id] ?? s.stars,
|
|
66
|
+
downloads: stats.downloads[s.id] ?? 0,
|
|
67
|
+
}));
|
|
68
|
+
}
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export declare const CDN_BASE_URL = "https://skillsets.cc";
|
|
2
2
|
export declare const SEARCH_INDEX_URL = "https://skillsets.cc/search-index.json";
|
|
3
|
+
export declare const STATS_URL = "https://skillsets.cc/api/stats/counts";
|
|
4
|
+
export declare const DOWNLOADS_URL = "https://skillsets.cc/api/downloads";
|
|
3
5
|
export declare const REGISTRY_REPO = "skillsets-cc/main";
|
|
4
6
|
export declare const CACHE_TTL_MS: number;
|
|
5
7
|
export declare const DEFAULT_SEARCH_LIMIT = 10;
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export const CDN_BASE_URL = 'https://skillsets.cc';
|
|
2
2
|
export const SEARCH_INDEX_URL = `${CDN_BASE_URL}/search-index.json`;
|
|
3
|
+
export const STATS_URL = `${CDN_BASE_URL}/api/stats/counts`;
|
|
4
|
+
export const DOWNLOADS_URL = `${CDN_BASE_URL}/api/downloads`;
|
|
3
5
|
export const REGISTRY_REPO = 'skillsets-cc/main';
|
|
4
6
|
export const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
5
7
|
export const DEFAULT_SEARCH_LIMIT = 10;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface SearchIndexEntry {
|
|
|
13
13
|
url?: string;
|
|
14
14
|
};
|
|
15
15
|
stars: number;
|
|
16
|
+
downloads?: number;
|
|
16
17
|
version: string;
|
|
17
18
|
status: 'active' | 'deprecated' | 'archived';
|
|
18
19
|
verification: {
|
|
@@ -28,6 +29,10 @@ export interface SearchIndexEntry {
|
|
|
28
29
|
checksum: string;
|
|
29
30
|
files: Record<string, string>;
|
|
30
31
|
}
|
|
32
|
+
export interface StatsResponse {
|
|
33
|
+
stars: Record<string, number>;
|
|
34
|
+
downloads: Record<string, number>;
|
|
35
|
+
}
|
|
31
36
|
export interface Skillset {
|
|
32
37
|
schema_version: string;
|
|
33
38
|
name: string;
|