@sstar/skill-install 1.1.1 → 1.2.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.
@@ -0,0 +1 @@
1
+ export * from './prompts';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./prompts"), exports);
@@ -0,0 +1,78 @@
1
+ import { AiTool } from '../skills/skills-manager';
2
+ import { SkillInPackage } from '../installer/install-service';
3
+ import { PluginInfo } from '../plugins/types';
4
+ /**
5
+ * Display intro message
6
+ */
7
+ export declare function intro(title?: string): void;
8
+ /**
9
+ * Display outro message
10
+ */
11
+ export declare function outro(message?: string): void;
12
+ /**
13
+ * Create a spinner
14
+ */
15
+ export declare function spinner(): {
16
+ start(msg: string): void;
17
+ stop(msg?: string): void;
18
+ message(msg: string): void;
19
+ error(msg: string): void;
20
+ };
21
+ /**
22
+ * Check if value is a cancel symbol (inquirer uses Ctrl+C which throws)
23
+ */
24
+ export declare function isCancel(value: unknown): boolean;
25
+ /**
26
+ * Prompt for AI tool selection
27
+ */
28
+ export declare function promptAiTool(): Promise<AiTool>;
29
+ /**
30
+ * Prompt for skills directory (global vs local)
31
+ */
32
+ export declare function promptSkillsDir(aiTool: AiTool, globalDir: string, localDir: string): Promise<string>;
33
+ /**
34
+ * Prompt for password (masked input)
35
+ */
36
+ export declare function promptPassword(message?: string): Promise<string>;
37
+ /**
38
+ * Prompt for yes/no confirmation
39
+ */
40
+ export declare function promptYesNo(message: string, initialValue?: boolean): Promise<boolean>;
41
+ /**
42
+ * Prompt for text input
43
+ */
44
+ export declare function promptText(message: string, placeholder?: string, initialValue?: string): Promise<string>;
45
+ /**
46
+ * Select skills from a multi-skill package
47
+ */
48
+ export declare function selectSkillsFromPackage(skills: SkillInPackage[]): Promise<SkillInPackage[]>;
49
+ /**
50
+ * Select skills to uninstall
51
+ */
52
+ export declare function promptSkillSelection(skills: Array<{
53
+ name: string;
54
+ description: string;
55
+ }>): Promise<number[]>;
56
+ /**
57
+ * Select plugins from marketplace
58
+ */
59
+ export declare function selectPluginsFromMarketplace(plugins: PluginInfo[]): Promise<Array<{
60
+ name: string;
61
+ version?: string;
62
+ }>>;
63
+ /**
64
+ * Display success message
65
+ */
66
+ export declare function success(message: string): void;
67
+ /**
68
+ * Display error message
69
+ */
70
+ export declare function error(message: string): void;
71
+ /**
72
+ * Display info message
73
+ */
74
+ export declare function info(message: string): void;
75
+ /**
76
+ * Display warning message
77
+ */
78
+ export declare function warn(message: string): void;
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.intro = intro;
7
+ exports.outro = outro;
8
+ exports.spinner = spinner;
9
+ exports.isCancel = isCancel;
10
+ exports.promptAiTool = promptAiTool;
11
+ exports.promptSkillsDir = promptSkillsDir;
12
+ exports.promptPassword = promptPassword;
13
+ exports.promptYesNo = promptYesNo;
14
+ exports.promptText = promptText;
15
+ exports.selectSkillsFromPackage = selectSkillsFromPackage;
16
+ exports.promptSkillSelection = promptSkillSelection;
17
+ exports.selectPluginsFromMarketplace = selectPluginsFromMarketplace;
18
+ exports.success = success;
19
+ exports.error = error;
20
+ exports.info = info;
21
+ exports.warn = warn;
22
+ const prompts_1 = require("@inquirer/prompts");
23
+ const picocolors_1 = __importDefault(require("picocolors"));
24
+ /**
25
+ * Common theme for prompts - clack-like style
26
+ */
27
+ const commonTheme = {
28
+ icon: {
29
+ cursor: picocolors_1.default.cyan('❯'),
30
+ },
31
+ style: {
32
+ description: (text) => picocolors_1.default.green(text),
33
+ },
34
+ };
35
+ /**
36
+ * Display intro message
37
+ */
38
+ function intro(title) {
39
+ console.log();
40
+ console.log(picocolors_1.default.cyan('◆') + (title ? ` ${title}` : ''));
41
+ console.log(picocolors_1.default.gray('│'));
42
+ }
43
+ /**
44
+ * Display outro message
45
+ */
46
+ function outro(message) {
47
+ console.log(picocolors_1.default.gray('│'));
48
+ console.log(picocolors_1.default.cyan('└') + (message ? ` ${message}` : ''));
49
+ console.log();
50
+ }
51
+ /**
52
+ * Create a spinner
53
+ */
54
+ function spinner() {
55
+ let intervalId = null;
56
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
57
+ let frameIndex = 0;
58
+ let currentMsg = '';
59
+ const clearLine = () => {
60
+ process.stdout.write('\r\x1b[K');
61
+ };
62
+ return {
63
+ start(msg) {
64
+ currentMsg = msg;
65
+ intervalId = setInterval(() => {
66
+ const frame = picocolors_1.default.cyan(frames[frameIndex]);
67
+ clearLine();
68
+ process.stdout.write(`${frame} ${currentMsg}`);
69
+ frameIndex = (frameIndex + 1) % frames.length;
70
+ }, 80);
71
+ },
72
+ stop(msg) {
73
+ if (intervalId) {
74
+ clearInterval(intervalId);
75
+ intervalId = null;
76
+ }
77
+ clearLine();
78
+ if (msg) {
79
+ console.log(picocolors_1.default.green('✓') + ` ${msg}`);
80
+ }
81
+ },
82
+ message(msg) {
83
+ currentMsg = msg;
84
+ },
85
+ error(msg) {
86
+ if (intervalId) {
87
+ clearInterval(intervalId);
88
+ intervalId = null;
89
+ }
90
+ clearLine();
91
+ console.log(picocolors_1.default.red('✗') + ` ${msg}`);
92
+ },
93
+ };
94
+ }
95
+ /**
96
+ * Check if value is a cancel symbol (inquirer uses Ctrl+C which throws)
97
+ */
98
+ function isCancel(value) {
99
+ return value === null || value === undefined;
100
+ }
101
+ /**
102
+ * Prompt for AI tool selection
103
+ */
104
+ async function promptAiTool() {
105
+ const choice = await (0, prompts_1.select)({
106
+ message: 'Select AI tool',
107
+ choices: [
108
+ { value: 'claude', name: 'Claude Code', description: '~/.claude/skills/' },
109
+ { value: 'codex', name: 'Codex', description: '~/.codex/skills/' },
110
+ ],
111
+ theme: commonTheme,
112
+ });
113
+ return choice;
114
+ }
115
+ /**
116
+ * Prompt for skills directory (global vs local)
117
+ */
118
+ async function promptSkillsDir(aiTool, globalDir, localDir) {
119
+ const choice = await (0, prompts_1.select)({
120
+ message: 'Select installation directory',
121
+ choices: [
122
+ { value: globalDir, name: 'Global', description: globalDir },
123
+ { value: localDir, name: 'Local', description: localDir },
124
+ ],
125
+ theme: commonTheme,
126
+ });
127
+ return choice;
128
+ }
129
+ /**
130
+ * Prompt for password (masked input)
131
+ */
132
+ async function promptPassword(message = 'Password') {
133
+ const value = await (0, prompts_1.password)({
134
+ message,
135
+ mask: '*',
136
+ });
137
+ return value;
138
+ }
139
+ /**
140
+ * Prompt for yes/no confirmation
141
+ */
142
+ async function promptYesNo(message, initialValue = true) {
143
+ const value = await (0, prompts_1.confirm)({
144
+ message,
145
+ default: initialValue,
146
+ });
147
+ return value;
148
+ }
149
+ /**
150
+ * Prompt for text input
151
+ */
152
+ async function promptText(message, placeholder, initialValue) {
153
+ const value = await (0, prompts_1.input)({
154
+ message,
155
+ default: initialValue,
156
+ });
157
+ return value;
158
+ }
159
+ /**
160
+ * Select skills from a multi-skill package
161
+ */
162
+ async function selectSkillsFromPackage(skills) {
163
+ if (skills.length === 0)
164
+ return [];
165
+ if (skills.length === 1) {
166
+ const install = await promptYesNo(`Install skill "${skills[0].name}"?`);
167
+ return install ? skills : [];
168
+ }
169
+ const indices = await (0, prompts_1.checkbox)({
170
+ message: `Select skills to install (${skills.length} found)`,
171
+ choices: skills.map((skill, index) => ({
172
+ value: index,
173
+ name: skill.name,
174
+ description: skill.description.length > 50
175
+ ? skill.description.slice(0, 50) + '...'
176
+ : skill.description,
177
+ })),
178
+ required: false,
179
+ theme: {
180
+ icon: {
181
+ checked: picocolors_1.default.green('◉'),
182
+ unchecked: '○',
183
+ cursor: picocolors_1.default.cyan('❯'),
184
+ },
185
+ style: {
186
+ description: (text) => picocolors_1.default.green(text),
187
+ },
188
+ },
189
+ });
190
+ if (indices.length === 0) {
191
+ const installAll = await promptYesNo('No skills selected. Install all?', false);
192
+ return installAll ? skills : [];
193
+ }
194
+ return indices.map(i => skills[i]);
195
+ }
196
+ /**
197
+ * Select skills to uninstall
198
+ */
199
+ async function promptSkillSelection(skills) {
200
+ if (skills.length === 0)
201
+ return [];
202
+ const indices = await (0, prompts_1.checkbox)({
203
+ message: 'Select skills to uninstall',
204
+ choices: skills.map((skill, index) => ({
205
+ value: index,
206
+ name: skill.name,
207
+ description: skill.description.length > 50
208
+ ? skill.description.slice(0, 50) + '...'
209
+ : skill.description,
210
+ })),
211
+ required: false,
212
+ theme: {
213
+ icon: {
214
+ checked: picocolors_1.default.green('◉'),
215
+ unchecked: '○',
216
+ cursor: picocolors_1.default.cyan('❯'),
217
+ },
218
+ style: {
219
+ description: (text) => picocolors_1.default.green(text),
220
+ },
221
+ },
222
+ });
223
+ return indices;
224
+ }
225
+ /**
226
+ * Select plugins from marketplace
227
+ */
228
+ async function selectPluginsFromMarketplace(plugins) {
229
+ if (plugins.length === 0)
230
+ return [];
231
+ if (plugins.length === 1) {
232
+ const install = await promptYesNo(`Install plugin "${plugins[0].name}"?`);
233
+ return install ? [{ name: plugins[0].name, version: plugins[0].version }] : [];
234
+ }
235
+ const indices = await (0, prompts_1.checkbox)({
236
+ message: `Select plugins to install (${plugins.length} found)`,
237
+ choices: plugins.map((plugin, index) => ({
238
+ value: index,
239
+ name: `${plugin.name} v${plugin.version || 'unknown'}`,
240
+ description: plugin.metadata.description?.slice(0, 50) || '',
241
+ })),
242
+ required: false,
243
+ theme: {
244
+ icon: {
245
+ checked: picocolors_1.default.green('◉'),
246
+ unchecked: '○',
247
+ cursor: picocolors_1.default.cyan('❯'),
248
+ },
249
+ style: {
250
+ description: (text) => picocolors_1.default.green(text),
251
+ },
252
+ },
253
+ });
254
+ if (indices.length === 0) {
255
+ const installAll = await promptYesNo('No plugins selected. Install all?', false);
256
+ return installAll ? plugins.map(p => ({ name: p.name, version: p.version })) : [];
257
+ }
258
+ return indices.map(i => ({ name: plugins[i].name, version: plugins[i].version }));
259
+ }
260
+ /**
261
+ * Display success message
262
+ */
263
+ function success(message) {
264
+ console.log(picocolors_1.default.green(`✓ ${message}`));
265
+ }
266
+ /**
267
+ * Display error message
268
+ */
269
+ function error(message) {
270
+ console.log(picocolors_1.default.red(`✗ ${message}`));
271
+ }
272
+ /**
273
+ * Display info message
274
+ */
275
+ function info(message) {
276
+ console.log(picocolors_1.default.cyan(`ℹ ${message}`));
277
+ }
278
+ /**
279
+ * Display warning message
280
+ */
281
+ function warn(message) {
282
+ console.log(picocolors_1.default.yellow(`⚠ ${message}`));
283
+ }
@@ -2,12 +2,8 @@ import { ParsedSkillItem } from './wiki-parser';
2
2
  export declare class SkillSelector {
3
3
  private readonly logger;
4
4
  /**
5
- * Display skill list and wait for user selection
6
- * Returns the download URL of selected skill, or null if cancelled
5
+ * Display skill list and wait for user selection (multi-select)
6
+ * Returns the download URLs of selected skills, or empty array if cancelled
7
7
  */
8
- displayAndSelect(items: ParsedSkillItem[]): Promise<string | null>;
9
- /**
10
- * Prompt user to select a skill from the list
11
- */
12
- private promptChoice;
8
+ displayAndSelect(items: ParsedSkillItem[]): Promise<string[]>;
13
9
  }
@@ -4,101 +4,57 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SkillSelector = void 0;
7
- const readline_1 = __importDefault(require("readline"));
7
+ const checkbox_1 = __importDefault(require("@inquirer/checkbox"));
8
+ const picocolors_1 = __importDefault(require("picocolors"));
8
9
  const logger_1 = require("../core/logger");
9
- // Color codes for terminal output
10
- const colors = {
11
- reset: '\x1b[0m',
12
- bright: '\x1b[1m',
13
- dim: '\x1b[2m',
14
- red: '\x1b[31m',
15
- green: '\x1b[32m',
16
- yellow: '\x1b[33m',
17
- blue: '\x1b[34m',
18
- magenta: '\x1b[35m',
19
- cyan: '\x1b[36m',
20
- white: '\x1b[37m',
21
- gray: '\x1b[90m'
22
- };
23
10
  class SkillSelector {
24
11
  constructor() {
25
12
  this.logger = new logger_1.Logger('SkillSelector');
26
13
  }
27
14
  /**
28
- * Display skill list and wait for user selection
29
- * Returns the download URL of selected skill, or null if cancelled
15
+ * Display skill list and wait for user selection (multi-select)
16
+ * Returns the download URLs of selected skills, or empty array if cancelled
30
17
  */
31
18
  async displayAndSelect(items) {
32
19
  if (items.length === 0) {
33
20
  this.logger.info('No skills found to display.');
34
- return null;
35
- }
36
- console.log('');
37
- console.log(`${colors.cyan}Found ${items.length} skill(s):${colors.reset}`);
38
- console.log('');
39
- // Display all skills
40
- for (let i = 0; i < items.length; i++) {
41
- const item = items[i];
42
- // Add separator line before each skill
43
- console.log(`${colors.dim}${'─'.repeat(60)}${colors.reset}`);
44
- // Index with color
45
- console.log(`${colors.bright}${colors.green}[${i + 1}]${colors.reset} ${colors.bright}${colors.yellow}${item.name}${colors.reset} ${colors.dim}${colors.gray}${item.pageUrl}${colors.reset}`);
46
- // Display comment (may be multiline)
47
- if (item.description) {
48
- const lines = item.description.split('\n');
49
- for (const line of lines) {
50
- console.log(`${colors.dim} ${line}${colors.reset}`);
51
- }
52
- }
21
+ return [];
53
22
  }
54
- // Add separator line after the last skill
55
- console.log(`${colors.dim}${'─'.repeat(60)}${colors.reset}`);
56
- console.log('');
57
- // Prompt for selection
58
- const choice = await this.promptChoice(items.length);
59
- if (choice === null) {
60
- return null;
61
- }
62
- const selected = items[choice];
63
- this.logger.info(`Selected: ${selected.name}`);
64
- return selected.downloadUrl;
65
- }
66
- /**
67
- * Prompt user to select a skill from the list
68
- */
69
- promptChoice(max) {
70
- return new Promise((resolve) => {
71
- const rl = readline_1.default.createInterface({
72
- input: process.stdin,
73
- output: process.stdout
74
- });
75
- rl.question(`Select skill to install [1-${max}] (0 to cancel): `, (answer) => {
76
- rl.close();
77
- const trimmed = answer.trim();
78
- if (trimmed === '') {
79
- console.log('No selection made.');
80
- resolve(null);
81
- return;
82
- }
83
- const num = parseInt(trimmed, 10);
84
- if (isNaN(num)) {
85
- console.log('Invalid input. Please enter a number.');
86
- resolve(null);
87
- return;
88
- }
89
- if (num < 0 || num > max) {
90
- console.log(`Invalid selection. Please enter a number between 0 and ${max}.`);
91
- resolve(null);
92
- return;
93
- }
94
- if (num === 0) {
95
- console.log('Cancelled.');
96
- resolve(null);
97
- return;
98
- }
99
- resolve(num - 1); // Convert to 0-based index
100
- });
23
+ const selectedIndices = await (0, checkbox_1.default)({
24
+ message: `Found ${items.length} skill(s)`,
25
+ pageSize: 30, // Show more items since each takes 3 lines
26
+ choices: items.map((item, index) => {
27
+ // Build multi-line name with colors
28
+ const num = picocolors_1.default.bold(picocolors_1.default.white(`[${index + 1}/${items.length}]`));
29
+ const name = picocolors_1.default.bold(picocolors_1.default.yellow(item.name));
30
+ const uploader = item.uploader ? picocolors_1.default.cyan(` (${item.uploader})`) : '';
31
+ const desc = picocolors_1.default.green(item.description);
32
+ const url = picocolors_1.default.magenta(item.pageUrl);
33
+ return {
34
+ value: index,
35
+ name: `${num} ${name}${uploader}\n ${desc}\n ${url}`,
36
+ description: '', // Disable default description
37
+ };
38
+ }),
39
+ required: false,
40
+ theme: {
41
+ icon: {
42
+ checked: picocolors_1.default.green('◉'),
43
+ unchecked: '○',
44
+ cursor: picocolors_1.default.cyan('❯'),
45
+ },
46
+ style: {
47
+ description: (text) => text, // No extra styling
48
+ },
49
+ },
101
50
  });
51
+ if (selectedIndices.length === 0) {
52
+ this.logger.info('No skills selected.');
53
+ return [];
54
+ }
55
+ const selectedItems = selectedIndices.map(index => items[index]);
56
+ this.logger.info(`Selected: ${selectedItems.map(s => s.name).join(', ')}`);
57
+ return selectedItems.map(s => s.downloadUrl);
102
58
  }
103
59
  }
104
60
  exports.SkillSelector = SkillSelector;
@@ -5,6 +5,7 @@ export interface WikiSkillItem {
5
5
  downloadUrl: string;
6
6
  containerId: string;
7
7
  pageUrl: string;
8
+ uploader?: string;
8
9
  }
9
10
  export interface SearchOptions {
10
11
  username: string;
@@ -70,7 +70,8 @@ class WikiSearcher {
70
70
  name: filename,
71
71
  downloadUrl: this.buildDownloadUrl(baseUrl, item),
72
72
  containerId: item.container?.id || '',
73
- pageUrl: item._links?.webui ? `${baseUrl}${item._links.webui}` : ''
73
+ pageUrl: item._links?.webui ? `${baseUrl}${item._links.webui}` : '',
74
+ uploader: item.version?.by?.displayName
74
75
  });
75
76
  }
76
77
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sstar/skill-install",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Agent Skill installation tool - download, extract, validate, and install skills for Claude Code and Codex",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -37,11 +37,14 @@
37
37
  "node": ">=18.0.0"
38
38
  },
39
39
  "dependencies": {
40
+ "@inquirer/prompts": "^8.3.0",
41
+ "@inquirer/type": "^4.0.3",
40
42
  "adm-zip": "^0.5.16",
41
43
  "axios": "^1.6.0",
42
44
  "cheerio": "^1.0.0-rc.12",
43
45
  "commander": "^12.0.0",
44
46
  "js-yaml": "^4.1.1",
47
+ "picocolors": "^1.1.1",
45
48
  "tar": "^7.5.6",
46
49
  "tough-cookie": "^4.1.3"
47
50
  },