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 +1 -1
- package/dist/index.js +66 -6
- package/dist/install.d.ts +1 -1
- package/dist/install.js +2 -2
- package/dist/output.d.ts +12 -0
- package/dist/output.js +12 -7
- package/dist/prompts.d.ts +8 -0
- package/dist/prompts.js +145 -0
- package/package.json +1 -1
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
|
|
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
|
|
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(
|
|
119
|
+
printStep(`Installing plugins (${scope} scope)...`);
|
|
60
120
|
const results = [];
|
|
61
|
-
for (const plugin of
|
|
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',
|
|
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>;
|
package/dist/prompts.js
ADDED
|
@@ -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
|
+
}
|