claudepluginhub 0.1.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 ADDED
@@ -0,0 +1,28 @@
1
+ # claudepluginhub
2
+
3
+ Install Claude Code plugins from [ClaudePluginHub](https://claudepluginhub.com) with a single command.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx claudepluginhub u/<userId>/<slug>
9
+ ```
10
+
11
+ This will:
12
+
13
+ 1. Fetch the plugin's marketplace JSON
14
+ 2. Register the marketplace with Claude Code
15
+ 3. Install all plugins from the marketplace
16
+
17
+ ### Full URL
18
+
19
+ You can also pass the full marketplace JSON URL:
20
+
21
+ ```bash
22
+ npx claudepluginhub https://claudepluginhub.com/api/user-plugins/<userId>/<slug>/marketplace.json
23
+ ```
24
+
25
+ ## Requirements
26
+
27
+ - Node.js 18+
28
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) installed and available in PATH
@@ -0,0 +1,20 @@
1
+ interface MarketplacePlugin {
2
+ name: string;
3
+ source: {
4
+ source: string;
5
+ repo: string;
6
+ };
7
+ strict: boolean;
8
+ description?: string;
9
+ [key: string]: unknown;
10
+ }
11
+ export interface MarketplaceJSON {
12
+ name: string;
13
+ owner: {
14
+ name: string;
15
+ };
16
+ plugins: MarketplacePlugin[];
17
+ }
18
+ export declare function resolveMarketplaceUrl(input: string): string | null;
19
+ export declare function fetchMarketplace(url: string): Promise<MarketplaceJSON | null>;
20
+ export {};
package/dist/fetch.js ADDED
@@ -0,0 +1,63 @@
1
+ import { printError } from './output.js';
2
+ const BASE_URL = 'https://claudepluginhub.com';
3
+ const ALLOWED_HOSTS = ['claudepluginhub.com', 'www.claudepluginhub.com'];
4
+ export function resolveMarketplaceUrl(input) {
5
+ // Full URL
6
+ if (input.startsWith('http://') || input.startsWith('https://')) {
7
+ try {
8
+ const parsed = new URL(input);
9
+ if (parsed.protocol !== 'https:') {
10
+ printError('Only HTTPS URLs are supported.');
11
+ return null;
12
+ }
13
+ if (!ALLOWED_HOSTS.includes(parsed.hostname)) {
14
+ printError(`Only claudepluginhub.com URLs are supported. Got: ${parsed.hostname}`);
15
+ return null;
16
+ }
17
+ return input;
18
+ }
19
+ catch {
20
+ printError('Invalid URL format.');
21
+ return null;
22
+ }
23
+ }
24
+ // Short form: u/<userId>/<slug>
25
+ const match = input.match(/^u\/([^/]+)\/([^/]+)$/);
26
+ if (match) {
27
+ const [, userId, slug] = match;
28
+ return `${BASE_URL}/api/user-plugins/${userId}/${slug}/marketplace.json?source=cli`;
29
+ }
30
+ return null;
31
+ }
32
+ export async function fetchMarketplace(url) {
33
+ try {
34
+ // Ensure ?source=cli is present for install tracking
35
+ const fetchUrl = url.includes('source=cli')
36
+ ? url
37
+ : url.includes('?')
38
+ ? `${url}&source=cli`
39
+ : `${url}?source=cli`;
40
+ const response = await fetch(fetchUrl, {
41
+ signal: AbortSignal.timeout(15_000),
42
+ });
43
+ if (!response.ok) {
44
+ if (response.status === 404) {
45
+ printError('Plugin not found. Check the identifier and try again.');
46
+ }
47
+ else {
48
+ printError(`HTTP ${response.status}: ${response.statusText}`);
49
+ }
50
+ return null;
51
+ }
52
+ const data = (await response.json());
53
+ if (!data.name || !Array.isArray(data.plugins)) {
54
+ printError('Invalid marketplace JSON format.');
55
+ return null;
56
+ }
57
+ return data;
58
+ }
59
+ catch (err) {
60
+ printError(`Network error: ${err.message}`);
61
+ return null;
62
+ }
63
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import { resolveMarketplaceUrl, fetchMarketplace } from './fetch.js';
3
+ import { detectClaude, addMarketplace, installPlugin } from './install.js';
4
+ import { print, printBanner, printStep, printSuccess, printFail, printSummary, printError, } from './output.js';
5
+ function printUsage() {
6
+ print(`Usage: npx claudepluginhub <identifier>
7
+
8
+ Examples:
9
+ npx claudepluginhub u/<userId>/<slug>
10
+ npx claudepluginhub https://claudepluginhub.com/api/user-plugins/.../marketplace.json
11
+ `);
12
+ }
13
+ async function main() {
14
+ const args = process.argv.slice(2);
15
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
16
+ printBanner();
17
+ printUsage();
18
+ process.exit(0);
19
+ }
20
+ const input = args[0];
21
+ printBanner();
22
+ // Resolve to marketplace URL
23
+ const url = resolveMarketplaceUrl(input);
24
+ if (!url) {
25
+ printError(`Invalid input: ${input}`);
26
+ print('');
27
+ printUsage();
28
+ process.exit(1);
29
+ }
30
+ // Check claude CLI is available
31
+ printStep('Checking for Claude CLI...');
32
+ const claudePath = detectClaude();
33
+ if (!claudePath) {
34
+ printError('Claude CLI not found in PATH.\nInstall it from: https://docs.anthropic.com/en/docs/claude-code/overview');
35
+ process.exit(1);
36
+ }
37
+ printSuccess('Claude CLI found');
38
+ // Fetch marketplace JSON
39
+ printStep('Fetching marketplace...');
40
+ const marketplace = await fetchMarketplace(url);
41
+ if (!marketplace) {
42
+ process.exit(1);
43
+ }
44
+ printSuccess(`"${marketplace.name}" - ${marketplace.plugins.length} plugin(s)`);
45
+ if (marketplace.plugins.length === 0) {
46
+ printError('No plugins found in this marketplace.');
47
+ process.exit(0);
48
+ }
49
+ // Add marketplace
50
+ printStep('Adding marketplace...');
51
+ const addResult = addMarketplace(claudePath, url);
52
+ if (!addResult.ok) {
53
+ printError(`Failed to add marketplace: ${addResult.error}`);
54
+ process.exit(1);
55
+ }
56
+ printSuccess('Marketplace registered');
57
+ // Install each plugin
58
+ print('');
59
+ printStep('Installing plugins...');
60
+ const results = [];
61
+ for (const plugin of marketplace.plugins) {
62
+ const installName = `${plugin.name}@${marketplace.name}`;
63
+ const result = installPlugin(claudePath, installName);
64
+ results.push({ name: plugin.name, ...result });
65
+ if (result.ok) {
66
+ printSuccess(plugin.name);
67
+ }
68
+ else {
69
+ printFail(plugin.name, result.error);
70
+ }
71
+ }
72
+ printSummary(results);
73
+ const failed = results.filter((r) => !r.ok).length;
74
+ if (failed > 0) {
75
+ process.exit(1);
76
+ }
77
+ }
78
+ main().catch((err) => {
79
+ printError(`Unexpected error: ${err.message}`);
80
+ process.exit(1);
81
+ });
@@ -0,0 +1,8 @@
1
+ interface CommandResult {
2
+ ok: boolean;
3
+ error?: string;
4
+ }
5
+ export declare function detectClaude(): string | null;
6
+ export declare function addMarketplace(claudePath: string, url: string): CommandResult;
7
+ export declare function installPlugin(claudePath: string, installName: string): CommandResult;
8
+ export {};
@@ -0,0 +1,41 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ export function detectClaude() {
3
+ try {
4
+ execFileSync('claude', ['--version'], { stdio: 'pipe' });
5
+ return 'claude';
6
+ }
7
+ catch {
8
+ return null;
9
+ }
10
+ }
11
+ export function addMarketplace(claudePath, url) {
12
+ try {
13
+ execFileSync(claudePath, ['plugin', 'marketplace', 'add', url], {
14
+ stdio: 'pipe',
15
+ timeout: 30_000,
16
+ });
17
+ return { ok: true };
18
+ }
19
+ catch (err) {
20
+ const message = err.message;
21
+ // Marketplace already added is fine (idempotent)
22
+ if (message.includes('already') || message.includes('exists')) {
23
+ return { ok: true };
24
+ }
25
+ return { ok: false, error: message.split('\n')[0] };
26
+ }
27
+ }
28
+ export function installPlugin(claudePath, installName) {
29
+ try {
30
+ execFileSync(claudePath, ['plugin', 'install', installName, '--scope', 'project'], { stdio: 'pipe', timeout: 60_000 });
31
+ return { ok: true };
32
+ }
33
+ catch (err) {
34
+ const message = err.message;
35
+ // Already installed is fine (idempotent)
36
+ if (message.includes('already installed')) {
37
+ return { ok: true };
38
+ }
39
+ return { ok: false, error: message.split('\n')[0] };
40
+ }
41
+ }
@@ -0,0 +1,11 @@
1
+ export declare function print(message: string): void;
2
+ export declare function printError(message: string): void;
3
+ export declare function printBanner(): void;
4
+ export declare function printStep(label: string): void;
5
+ export declare function printSuccess(label: string): void;
6
+ export declare function printFail(label: string, error?: string): void;
7
+ export declare function printSummary(results: {
8
+ name: string;
9
+ ok: boolean;
10
+ error?: string;
11
+ }[]): void;
package/dist/output.js ADDED
@@ -0,0 +1,37 @@
1
+ const RESET = '\x1b[0m';
2
+ const BOLD = '\x1b[1m';
3
+ const DIM = '\x1b[2m';
4
+ const RED = '\x1b[31m';
5
+ const GREEN = '\x1b[32m';
6
+ const YELLOW = '\x1b[33m';
7
+ const CYAN = '\x1b[36m';
8
+ export function print(message) {
9
+ console.log(message);
10
+ }
11
+ export function printError(message) {
12
+ console.error(`${RED}${BOLD}Error:${RESET} ${message}`);
13
+ }
14
+ export function printBanner() {
15
+ print(`\n${CYAN}${BOLD}ClaudePluginHub${RESET} ${DIM}Installer${RESET}\n`);
16
+ }
17
+ export function printStep(label) {
18
+ print(`${DIM}>${RESET} ${label}`);
19
+ }
20
+ export function printSuccess(label) {
21
+ print(` ${GREEN}ok${RESET} ${label}`);
22
+ }
23
+ export function printFail(label, error) {
24
+ print(` ${RED}fail${RESET} ${label}${error ? ` ${DIM}(${error})${RESET}` : ''}`);
25
+ }
26
+ export function printSummary(results) {
27
+ const succeeded = results.filter((r) => r.ok).length;
28
+ const failed = results.filter((r) => !r.ok).length;
29
+ print('');
30
+ if (failed === 0) {
31
+ print(`${GREEN}${BOLD}Done!${RESET} ${succeeded} plugin(s) installed successfully.`);
32
+ }
33
+ else {
34
+ print(`${YELLOW}${BOLD}Done.${RESET} ${succeeded} succeeded, ${failed} failed.`);
35
+ }
36
+ print('');
37
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "claudepluginhub",
3
+ "version": "0.1.0",
4
+ "description": "Install Claude Code plugins from ClaudePluginHub",
5
+ "bin": {
6
+ "claudepluginhub": "./dist/index.js"
7
+ },
8
+ "type": "module",
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "claude",
21
+ "claude-code",
22
+ "plugins",
23
+ "cli"
24
+ ],
25
+ "author": "ClaudePluginHub",
26
+ "homepage": "https://claudepluginhub.com",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/heiberik/claudepluginhub.git",
30
+ "directory": "packages/cli"
31
+ },
32
+ "license": "MIT",
33
+ "devDependencies": {
34
+ "typescript": "^5.9.3"
35
+ }
36
+ }