claudepluginhub 0.1.1 → 0.1.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/fetch.js CHANGED
@@ -22,7 +22,7 @@ export function resolveMarketplaceUrl(input) {
22
22
  }
23
23
  }
24
24
  // Short form: u/<userId>/<slug>
25
- const match = input.match(/^u\/([^/]+)\/([^/]+)$/);
25
+ const match = input.match(/^u\/([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_-]+)$/);
26
26
  if (match) {
27
27
  const [, userId, slug] = match;
28
28
  return `${BASE_URL}/api/user-plugins/${userId}/${slug}/marketplace.json?source=cli`;
package/dist/index.js CHANGED
@@ -1,23 +1,51 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolveMarketplaceUrl, fetchMarketplace } from './fetch.js';
3
3
  import { detectClaude, addMarketplace, installPlugin } from './install.js';
4
+ import { checkboxPrompt, scopePrompt } from './prompts.js';
4
5
  import { print, printBanner, printStep, printSuccess, printFail, printSummary, printError, } from './output.js';
6
+ const VALID_SCOPES = ['user', 'project', 'local'];
5
7
  function printUsage() {
6
- print(`Usage: npx claudepluginhub <identifier>
8
+ print(`Usage: npx claudepluginhub <identifier> [options]
7
9
 
8
10
  Examples:
9
11
  npx claudepluginhub u/<userId>/<slug>
10
12
  npx claudepluginhub https://claudepluginhub.com/api/user-plugins/.../marketplace.json
13
+
14
+ Options:
15
+ --yes, -y Skip prompts (install all, project scope)
16
+ --scope <scope> Set scope: user, project (default), local
17
+ -h, --help Show this help
11
18
  `);
12
19
  }
20
+ function parseArgs(argv) {
21
+ const yes = argv.includes('--yes') || argv.includes('-y');
22
+ let scope = null;
23
+ const scopeIdx = argv.indexOf('--scope');
24
+ if (scopeIdx !== -1 && argv[scopeIdx + 1]) {
25
+ const val = argv[scopeIdx + 1];
26
+ if (!VALID_SCOPES.includes(val)) {
27
+ printError(`Invalid scope "${val}". Must be: user, project, or local`);
28
+ process.exit(1);
29
+ }
30
+ scope = val;
31
+ }
32
+ const positional = argv.filter((a, i) => !a.startsWith('-') && (i === 0 || argv[i - 1] !== '--scope'));
33
+ const input = positional[0] ?? null;
34
+ return { input, yes, scope };
35
+ }
13
36
  async function main() {
14
37
  const args = process.argv.slice(2);
15
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
38
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
16
39
  printBanner();
17
40
  printUsage();
18
41
  process.exit(0);
19
42
  }
20
- const input = args[0];
43
+ const { input, yes, scope: scopeOverride } = parseArgs(args);
44
+ if (!input) {
45
+ printBanner();
46
+ printUsage();
47
+ process.exit(1);
48
+ }
21
49
  printBanner();
22
50
  // Resolve to marketplace URL
23
51
  const url = resolveMarketplaceUrl(input);
@@ -46,7 +74,39 @@ async function main() {
46
74
  printError('No plugins found in this marketplace.');
47
75
  process.exit(0);
48
76
  }
77
+ // Plugin selection (interactive, >1 plugin)
78
+ let selectedPlugins = marketplace.plugins;
79
+ if (!yes && marketplace.plugins.length > 1) {
80
+ print('');
81
+ const items = marketplace.plugins.map((p) => ({
82
+ label: p.name,
83
+ description: p.description,
84
+ selected: true,
85
+ }));
86
+ const chosen = await checkboxPrompt('Select plugins to install:', items);
87
+ if (chosen === null) {
88
+ print('\nCancelled.');
89
+ process.exit(0);
90
+ }
91
+ selectedPlugins = chosen.map((i) => marketplace.plugins[i]);
92
+ if (selectedPlugins.length === 0) {
93
+ printError('No plugins selected.');
94
+ process.exit(0);
95
+ }
96
+ }
97
+ // Scope selection
98
+ let scope = scopeOverride ?? 'project';
99
+ if (!yes && !scopeOverride) {
100
+ print('');
101
+ const chosenScope = await scopePrompt('project');
102
+ if (chosenScope === null) {
103
+ print('\nCancelled.');
104
+ process.exit(0);
105
+ }
106
+ scope = chosenScope;
107
+ }
49
108
  // Add marketplace
109
+ print('');
50
110
  printStep('Adding marketplace...');
51
111
  const addResult = addMarketplace(claudePath, url);
52
112
  if (!addResult.ok) {
@@ -56,11 +116,11 @@ async function main() {
56
116
  printSuccess('Marketplace registered');
57
117
  // Install each plugin
58
118
  print('');
59
- printStep('Installing plugins...');
119
+ printStep(`Installing plugins (${scope} scope)...`);
60
120
  const results = [];
61
- for (const plugin of marketplace.plugins) {
121
+ for (const plugin of selectedPlugins) {
62
122
  const installName = `${plugin.name}@${marketplace.name}`;
63
- const result = installPlugin(claudePath, installName);
123
+ const result = installPlugin(claudePath, installName, scope);
64
124
  results.push({ name: plugin.name, ...result });
65
125
  if (result.ok) {
66
126
  printSuccess(plugin.name);
package/dist/install.d.ts CHANGED
@@ -4,5 +4,5 @@ interface CommandResult {
4
4
  }
5
5
  export declare function detectClaude(): string | null;
6
6
  export declare function addMarketplace(claudePath: string, url: string): CommandResult;
7
- export declare function installPlugin(claudePath: string, installName: string): CommandResult;
7
+ export declare function installPlugin(claudePath: string, installName: string, scope?: 'user' | 'project' | 'local'): CommandResult;
8
8
  export {};
package/dist/install.js CHANGED
@@ -25,9 +25,9 @@ export function addMarketplace(claudePath, url) {
25
25
  return { ok: false, error: message.split('\n')[0] };
26
26
  }
27
27
  }
28
- export function installPlugin(claudePath, installName) {
28
+ export function installPlugin(claudePath, installName, scope = 'project') {
29
29
  try {
30
- execFileSync(claudePath, ['plugin', 'install', installName, '--scope', 'project'], { stdio: 'pipe', timeout: 60_000 });
30
+ execFileSync(claudePath, ['plugin', 'install', installName, '--scope', scope], { stdio: 'pipe', timeout: 60_000 });
31
31
  return { ok: true };
32
32
  }
33
33
  catch (err) {
package/dist/output.d.ts CHANGED
@@ -1,3 +1,15 @@
1
+ export declare const RESET = "\u001B[0m";
2
+ export declare const BOLD = "\u001B[1m";
3
+ export declare const DIM = "\u001B[2m";
4
+ export declare const RED = "\u001B[31m";
5
+ export declare const GREEN = "\u001B[32m";
6
+ export declare const YELLOW = "\u001B[33m";
7
+ export declare const CYAN = "\u001B[36m";
8
+ export declare const INVERSE = "\u001B[7m";
9
+ export declare const HIDE_CURSOR = "\u001B[?25l";
10
+ export declare const SHOW_CURSOR = "\u001B[?25h";
11
+ export declare const CLEAR_LINE = "\u001B[2K";
12
+ export declare const moveUp: (n: number) => string;
1
13
  export declare function print(message: string): void;
2
14
  export declare function printError(message: string): void;
3
15
  export declare function printBanner(): void;
package/dist/output.js CHANGED
@@ -1,10 +1,15 @@
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';
1
+ export const RESET = '\x1b[0m';
2
+ export const BOLD = '\x1b[1m';
3
+ export const DIM = '\x1b[2m';
4
+ export const RED = '\x1b[31m';
5
+ export const GREEN = '\x1b[32m';
6
+ export const YELLOW = '\x1b[33m';
7
+ export const CYAN = '\x1b[36m';
8
+ export const INVERSE = '\x1b[7m';
9
+ export const HIDE_CURSOR = '\x1b[?25l';
10
+ export const SHOW_CURSOR = '\x1b[?25h';
11
+ export const CLEAR_LINE = '\x1b[2K';
12
+ export const moveUp = (n) => `\x1b[${n}A`;
8
13
  export function print(message) {
9
14
  console.log(message);
10
15
  }
@@ -0,0 +1,8 @@
1
+ export type Scope = 'user' | 'project' | 'local';
2
+ export interface SelectableItem {
3
+ label: string;
4
+ description?: string;
5
+ selected: boolean;
6
+ }
7
+ export declare function checkboxPrompt(title: string, items: SelectableItem[]): Promise<number[] | null>;
8
+ export declare function scopePrompt(defaultScope?: Scope): Promise<Scope | null>;
@@ -0,0 +1,145 @@
1
+ import { RESET, BOLD, DIM, GREEN, CYAN, INVERSE, HIDE_CURSOR, SHOW_CURSOR, moveUp, } from './output.js';
2
+ function isInteractive() {
3
+ return !!process.stdin.isTTY;
4
+ }
5
+ function write(text) {
6
+ process.stdout.write(text);
7
+ }
8
+ function readKey() {
9
+ return new Promise((resolve) => {
10
+ process.stdin.once('data', resolve);
11
+ });
12
+ }
13
+ function renderCheckbox(title, items, cursor) {
14
+ const lines = [`${BOLD}${title}${RESET}`];
15
+ lines.push(`${DIM} Space=toggle a=all Enter=confirm Ctrl+C=cancel${RESET}`);
16
+ for (let i = 0; i < items.length; i++) {
17
+ const item = items[i];
18
+ const marker = item.selected ? `${GREEN}[x]${RESET}` : `[ ]`;
19
+ const prefix = i === cursor ? `${INVERSE}>` : ' ';
20
+ const label = i === cursor ? `${BOLD}${item.label}${RESET}` : item.label;
21
+ const desc = item.description ? ` ${DIM}${item.description}${RESET}` : '';
22
+ const suffix = i === cursor ? RESET : '';
23
+ lines.push(`${prefix} ${marker} ${label}${desc}${suffix}`);
24
+ }
25
+ return lines.join('\n');
26
+ }
27
+ export async function checkboxPrompt(title, items) {
28
+ if (!isInteractive()) {
29
+ return items.map((_, i) => i);
30
+ }
31
+ const wasRaw = process.stdin.isRaw;
32
+ process.stdin.setRawMode(true);
33
+ process.stdin.resume();
34
+ write(HIDE_CURSOR);
35
+ let cursor = 0;
36
+ const totalLines = items.length + 2; // title + hint + items
37
+ // Initial render
38
+ write(renderCheckbox(title, items, cursor));
39
+ try {
40
+ while (true) {
41
+ const key = await readKey();
42
+ const str = key.toString();
43
+ // Ctrl+C
44
+ if (str === '\x03') {
45
+ write('\n' + SHOW_CURSOR);
46
+ return null;
47
+ }
48
+ // Enter
49
+ if (str === '\r' || str === '\n') {
50
+ write('\n' + SHOW_CURSOR);
51
+ return items
52
+ .map((item, i) => (item.selected ? i : -1))
53
+ .filter((i) => i !== -1);
54
+ }
55
+ // Space - toggle
56
+ if (str === ' ') {
57
+ items[cursor].selected = !items[cursor].selected;
58
+ }
59
+ // 'a' - toggle all
60
+ if (str === 'a') {
61
+ const allSelected = items.every((item) => item.selected);
62
+ for (const item of items) {
63
+ item.selected = !allSelected;
64
+ }
65
+ }
66
+ // Arrow up or 'k'
67
+ if (str === '\x1b[A' || str === 'k') {
68
+ cursor = cursor > 0 ? cursor - 1 : items.length - 1;
69
+ }
70
+ // Arrow down or 'j'
71
+ if (str === '\x1b[B' || str === 'j') {
72
+ cursor = cursor < items.length - 1 ? cursor + 1 : 0;
73
+ }
74
+ // Re-render
75
+ write(moveUp(totalLines - 1) + '\r');
76
+ write(renderCheckbox(title, items, cursor));
77
+ }
78
+ }
79
+ finally {
80
+ process.stdin.setRawMode(wasRaw ?? false);
81
+ process.stdin.pause();
82
+ write(SHOW_CURSOR);
83
+ }
84
+ }
85
+ const SCOPE_OPTIONS = [
86
+ { value: 'user', label: 'User', hint: 'available across all projects' },
87
+ {
88
+ value: 'project',
89
+ label: 'Project',
90
+ hint: 'shared via git with collaborators',
91
+ },
92
+ { value: 'local', label: 'Local', hint: 'this repo only, gitignored' },
93
+ ];
94
+ function renderScope(cursor) {
95
+ const lines = [`${BOLD}Install scope:${RESET}`];
96
+ for (let i = 0; i < SCOPE_OPTIONS.length; i++) {
97
+ const opt = SCOPE_OPTIONS[i];
98
+ const prefix = i === cursor ? `${CYAN}>${RESET}` : ' ';
99
+ const label = i === cursor ? `${BOLD}${opt.label}${RESET}` : `${opt.label}`;
100
+ lines.push(`${prefix} ${label} ${DIM}(${opt.hint})${RESET}`);
101
+ }
102
+ lines.push(`${DIM} Up/Down=move Enter=confirm${RESET}`);
103
+ return lines.join('\n');
104
+ }
105
+ export async function scopePrompt(defaultScope = 'project') {
106
+ if (!isInteractive()) {
107
+ return defaultScope;
108
+ }
109
+ const wasRaw = process.stdin.isRaw;
110
+ process.stdin.setRawMode(true);
111
+ process.stdin.resume();
112
+ write(HIDE_CURSOR);
113
+ let cursor = SCOPE_OPTIONS.findIndex((o) => o.value === defaultScope);
114
+ if (cursor === -1)
115
+ cursor = 1;
116
+ const totalLines = SCOPE_OPTIONS.length + 2; // title + options + hint
117
+ write(renderScope(cursor));
118
+ try {
119
+ while (true) {
120
+ const key = await readKey();
121
+ const str = key.toString();
122
+ if (str === '\x03') {
123
+ write('\n' + SHOW_CURSOR);
124
+ return null;
125
+ }
126
+ if (str === '\r' || str === '\n') {
127
+ write('\n' + SHOW_CURSOR);
128
+ return SCOPE_OPTIONS[cursor].value;
129
+ }
130
+ if (str === '\x1b[A' || str === 'k') {
131
+ cursor = cursor > 0 ? cursor - 1 : SCOPE_OPTIONS.length - 1;
132
+ }
133
+ if (str === '\x1b[B' || str === 'j') {
134
+ cursor = cursor < SCOPE_OPTIONS.length - 1 ? cursor + 1 : 0;
135
+ }
136
+ write(moveUp(totalLines - 1) + '\r');
137
+ write(renderScope(cursor));
138
+ }
139
+ }
140
+ finally {
141
+ process.stdin.setRawMode(wasRaw ?? false);
142
+ process.stdin.pause();
143
+ write(SHOW_CURSOR);
144
+ }
145
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudepluginhub",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Install Claude Code plugins from ClaudePluginHub",
5
5
  "bin": {
6
6
  "claudepluginhub": "./dist/index.js"