@sstar/skill-install 1.0.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/dist/cli.js ADDED
@@ -0,0 +1,459 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const readline_1 = __importDefault(require("readline"));
9
+ const install_service_1 = require("./installer/install-service");
10
+ const skills_manager_1 = require("./skills/skills-manager");
11
+ const logger_1 = require("./core/logger");
12
+ const program = new commander_1.Command();
13
+ const skillsManager = new skills_manager_1.SkillsManager();
14
+ program
15
+ .name('skill-install')
16
+ .description('Agent Skill installation tool - download, extract, validate, and install skills')
17
+ .version('1.0.0')
18
+ .option('-t, --tool <tool>', 'AI tool: claude or codex (skip prompt if specified)')
19
+ .option('-s, --skills-dir <path>', 'Custom skills directory')
20
+ .option('-v, --verbose', 'Enable verbose logging');
21
+ // Install command
22
+ program
23
+ .command('install <source>')
24
+ .description('Install a skill from URL or local archive file')
25
+ .option('-u, --username <username>', 'Wiki username (for wiki URLs)')
26
+ .option('-p, --password <password>', 'Wiki password (not recommended, use interactive input instead)')
27
+ .option('--allow-self-signed', 'Allow self-signed SSL certificates (default: true)', true)
28
+ .option('-f, --force', 'Reinstall if skill already exists')
29
+ .option('-g, --global', 'Install to global directory')
30
+ .option('-l, --local', 'Install to local directory')
31
+ .action(async (source, options, cmd) => {
32
+ const globalOpts = cmd.parent.opts();
33
+ const logger = new logger_1.Logger('CLI');
34
+ if (globalOpts.verbose) {
35
+ logger_1.Logger.setLevel('debug');
36
+ }
37
+ try {
38
+ // Determine AI tool - prompt if not specified
39
+ let aiTool;
40
+ if (globalOpts.tool) {
41
+ aiTool = globalOpts.tool;
42
+ }
43
+ else {
44
+ aiTool = await promptAiTool();
45
+ }
46
+ // Determine skills directory
47
+ let skillsDir = globalOpts.skillsDir;
48
+ // If neither --global nor --local nor explicit --skills-dir, prompt interactively
49
+ if (!skillsDir && !options.global && !options.local) {
50
+ skillsDir = await promptSkillsDir(aiTool);
51
+ }
52
+ else if (options.global) {
53
+ skillsDir = skillsManager.getGlobalSkillsDir(aiTool);
54
+ }
55
+ else if (options.local) {
56
+ skillsDir = skillsManager.getLocalSkillsDir(aiTool);
57
+ }
58
+ console.log(`AI Tool: ${aiTool}`);
59
+ console.log(`Installing to: ${skillsDir}`);
60
+ console.log('');
61
+ // Get username/password for wiki URLs
62
+ let username = options.username || process.env.WIKI_USERNAME || '';
63
+ let password = options.password || process.env.WIKI_PASSWORD || '';
64
+ // Check if source appears to be a wiki URL
65
+ const needsAuth = source.includes('/download/attachments/') ||
66
+ source.includes('/wiki/download/') ||
67
+ source.includes('/confluence/download/');
68
+ if (needsAuth && !username) {
69
+ console.error('Error: Wiki URL detected. Username is required. Use -u option or set WIKI_USERNAME environment variable.');
70
+ process.exit(1);
71
+ }
72
+ if (needsAuth && !password) {
73
+ password = await promptPassword();
74
+ }
75
+ console.log(`Installing skill from: ${source}`);
76
+ if (needsAuth) {
77
+ console.log(`Username: ${username}`);
78
+ }
79
+ console.log('');
80
+ const installer = new install_service_1.InstallService();
81
+ const result = await installer.install({
82
+ source,
83
+ skillsDir,
84
+ username,
85
+ password,
86
+ allowSelfSigned: options.allowSelfSigned,
87
+ force: options.force
88
+ });
89
+ console.log('');
90
+ if (result.success) {
91
+ console.log(`✓ Skill installed successfully!`);
92
+ console.log(` Name: ${result.skillName}`);
93
+ console.log(` Path: ${result.skillPath}`);
94
+ process.exit(0);
95
+ }
96
+ else {
97
+ console.error(`✗ Installation failed!`);
98
+ console.error(` Error: ${result.error || 'Unknown error'}`);
99
+ process.exit(1);
100
+ }
101
+ }
102
+ catch (error) {
103
+ const errorMsg = error instanceof Error ? error.message : String(error);
104
+ console.error(`Error: ${errorMsg}`);
105
+ if (globalOpts.verbose) {
106
+ console.error(error);
107
+ }
108
+ process.exit(1);
109
+ }
110
+ });
111
+ // List command
112
+ program
113
+ .command('list')
114
+ .description('List installed skills')
115
+ .option('-a, --all', 'List skills from both global and local directories')
116
+ .action(async (options, cmd) => {
117
+ const globalOpts = cmd.parent.opts();
118
+ if (globalOpts.verbose) {
119
+ logger_1.Logger.setLevel('debug');
120
+ }
121
+ try {
122
+ // Determine AI tool - prompt if not specified
123
+ let aiTool;
124
+ if (globalOpts.tool) {
125
+ aiTool = globalOpts.tool;
126
+ }
127
+ else {
128
+ aiTool = await promptAiTool();
129
+ }
130
+ // If no specific directory and --all flag, show both directories
131
+ if (!globalOpts.skillsDir && options.all) {
132
+ await listSkillsFromBothDirectories(aiTool);
133
+ process.exit(0);
134
+ }
135
+ const skillsDir = skillsManager.getEffectiveSkillsDir(globalOpts.skillsDir, aiTool);
136
+ console.log(`AI Tool: ${aiTool}`);
137
+ console.log(`Skills directory: ${skillsDir}`);
138
+ console.log('');
139
+ const skills = await skillsManager.listInstalled(globalOpts.skillsDir, aiTool);
140
+ if (skills.length === 0) {
141
+ console.log('No skills installed.');
142
+ process.exit(0);
143
+ }
144
+ console.log(`Installed skills (${skills.length}):`);
145
+ console.log('');
146
+ for (const skill of skills) {
147
+ console.log(` ${skill.name}`);
148
+ console.log(` Description: ${skill.description}`);
149
+ console.log(` Path: ${skill.path}`);
150
+ console.log('');
151
+ }
152
+ process.exit(0);
153
+ }
154
+ catch (error) {
155
+ const errorMsg = error instanceof Error ? error.message : String(error);
156
+ console.error(`Error: ${errorMsg}`);
157
+ if (globalOpts.verbose) {
158
+ console.error(error);
159
+ }
160
+ process.exit(1);
161
+ }
162
+ });
163
+ // Uninstall command
164
+ program
165
+ .command('uninstall [name]')
166
+ .description('Uninstall a skill (leave empty to select from list)')
167
+ .option('-y, --yes', 'Skip confirmation prompt')
168
+ .action(async (name, options, cmd) => {
169
+ const globalOpts = cmd.parent.opts();
170
+ if (globalOpts.verbose) {
171
+ logger_1.Logger.setLevel('debug');
172
+ }
173
+ try {
174
+ // Determine AI tool - prompt if not specified
175
+ let aiTool;
176
+ if (globalOpts.tool) {
177
+ aiTool = globalOpts.tool;
178
+ }
179
+ else {
180
+ aiTool = await promptAiTool();
181
+ }
182
+ const skills = await skillsManager.listInstalled(globalOpts.skillsDir, aiTool);
183
+ if (skills.length === 0) {
184
+ console.log('No skills installed.');
185
+ process.exit(0);
186
+ }
187
+ // If no name provided, prompt to select from list
188
+ if (!name) {
189
+ const selectedIndices = await promptSkillSelection(skills);
190
+ if (selectedIndices.length === 0) {
191
+ console.log('No skills selected.');
192
+ process.exit(0);
193
+ }
194
+ // Uninstall each selected skill
195
+ let successCount = 0;
196
+ let failCount = 0;
197
+ for (const index of selectedIndices) {
198
+ const skill = skills[index];
199
+ try {
200
+ // Confirm uninstall unless --yes flag is provided
201
+ if (!options.yes) {
202
+ const confirmed = await confirm(`Uninstall skill "${skill.name}" from ${aiTool}?`);
203
+ if (!confirmed) {
204
+ console.log(`Skipped: ${skill.name}`);
205
+ continue;
206
+ }
207
+ }
208
+ await skillsManager.uninstall(skill.name, globalOpts.skillsDir, aiTool);
209
+ console.log(`✓ Skill "${skill.name}" uninstalled successfully.`);
210
+ successCount++;
211
+ }
212
+ catch (error) {
213
+ console.error(`✗ Failed to uninstall "${skill.name}": ${error.message}`);
214
+ failCount++;
215
+ }
216
+ }
217
+ console.log('');
218
+ console.log(`Uninstall complete: ${successCount} succeeded, ${failCount} failed.`);
219
+ process.exit(0);
220
+ }
221
+ // Single skill uninstall (existing behavior)
222
+ const skill = skills.find(s => s.name === name);
223
+ if (!skill) {
224
+ console.error(`Error: Skill "${name}" not found.`);
225
+ const availableSkills = skills.map(s => s.name).join(', ');
226
+ if (availableSkills) {
227
+ console.log(`Available skills: ${availableSkills}`);
228
+ }
229
+ process.exit(1);
230
+ }
231
+ // Confirm uninstall unless --yes flag is provided
232
+ if (!options.yes) {
233
+ const confirmed = await confirm(`Uninstall skill "${name}" from ${aiTool}?`);
234
+ if (!confirmed) {
235
+ console.log('Uninstall cancelled.');
236
+ process.exit(0);
237
+ }
238
+ }
239
+ await skillsManager.uninstall(name, globalOpts.skillsDir, aiTool);
240
+ console.log(`✓ Skill "${name}" uninstalled successfully.`);
241
+ process.exit(0);
242
+ }
243
+ catch (error) {
244
+ const errorMsg = error instanceof Error ? error.message : String(error);
245
+ console.error(`Error: ${errorMsg}`);
246
+ if (globalOpts.verbose) {
247
+ console.error(error);
248
+ }
249
+ process.exit(1);
250
+ }
251
+ });
252
+ function promptPassword() {
253
+ return new Promise((resolve, reject) => {
254
+ // Check if setRawMode is available (TTY required)
255
+ if (typeof process.stdin.setRawMode !== 'function') {
256
+ console.error('\nError: Interactive password input requires a TTY.');
257
+ console.error('Please use -p option or WIKI_PASSWORD environment variable.');
258
+ reject(new Error('TTY not available for interactive password input'));
259
+ return;
260
+ }
261
+ const rl = readline_1.default.createInterface({
262
+ input: process.stdin,
263
+ output: process.stdout
264
+ });
265
+ // Turn off echo
266
+ process.stdout.write('Password: ');
267
+ process.stdin.setRawMode(true);
268
+ let password = '';
269
+ const onData = (char) => {
270
+ const str = char.toString();
271
+ if (str === '\n' || str === '\r' || str === '\u0004') {
272
+ // Enter or Ctrl-D
273
+ process.stdin.setRawMode(false);
274
+ process.stdin.removeListener('data', onData);
275
+ process.stdout.write('\n');
276
+ rl.close();
277
+ resolve(password);
278
+ }
279
+ else if (str === '\u0003') {
280
+ // Ctrl-C
281
+ process.stdout.write('\n');
282
+ process.exit(1);
283
+ }
284
+ else if (str === '\u007f') {
285
+ // Backspace
286
+ if (password.length > 0) {
287
+ password = password.slice(0, -1);
288
+ }
289
+ }
290
+ else {
291
+ // Regular character
292
+ password += str;
293
+ }
294
+ };
295
+ process.stdin.on('data', onData);
296
+ process.stdin.setRawMode(true);
297
+ });
298
+ }
299
+ function confirm(message) {
300
+ return new Promise((resolve) => {
301
+ const rl = readline_1.default.createInterface({
302
+ input: process.stdin,
303
+ output: process.stdout
304
+ });
305
+ rl.question(`${message} (y/N): `, (answer) => {
306
+ rl.close();
307
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
308
+ });
309
+ });
310
+ }
311
+ function promptSkillsDir(aiTool) {
312
+ return new Promise((resolve) => {
313
+ const rl = readline_1.default.createInterface({
314
+ input: process.stdin,
315
+ output: process.stdout
316
+ });
317
+ const globalDir = skillsManager.getGlobalSkillsDir(aiTool);
318
+ const localDir = skillsManager.getLocalSkillsDir(aiTool);
319
+ console.log('');
320
+ console.log('Select installation directory:');
321
+ console.log(` 1. Global: ${globalDir}`);
322
+ console.log(` 2. Local: ${localDir}`);
323
+ console.log('');
324
+ rl.question('Choose [1/2] (default: 1): ', (answer) => {
325
+ rl.close();
326
+ const choice = answer.trim() || '1';
327
+ if (choice === '1') {
328
+ resolve(globalDir);
329
+ }
330
+ else if (choice === '2') {
331
+ resolve(localDir);
332
+ }
333
+ else {
334
+ console.log('Invalid choice, using global directory.');
335
+ resolve(globalDir);
336
+ }
337
+ });
338
+ });
339
+ }
340
+ function promptAiTool() {
341
+ return new Promise((resolve) => {
342
+ const rl = readline_1.default.createInterface({
343
+ input: process.stdin,
344
+ output: process.stdout
345
+ });
346
+ console.log('');
347
+ console.log('Select AI tool:');
348
+ console.log(' 1. Claude Code (~/.claude/skills/)');
349
+ console.log(' 2. Codex (~/.codex/skills/)');
350
+ console.log('');
351
+ rl.question('Choose [1/2] (default: 1): ', (answer) => {
352
+ rl.close();
353
+ const choice = answer.trim() || '1';
354
+ if (choice === '1') {
355
+ resolve('claude');
356
+ }
357
+ else if (choice === '2') {
358
+ resolve('codex');
359
+ }
360
+ else {
361
+ console.log('Invalid choice, using claude.');
362
+ resolve('claude');
363
+ }
364
+ });
365
+ });
366
+ }
367
+ function promptSkillSelection(skills) {
368
+ return new Promise((resolve) => {
369
+ const rl = readline_1.default.createInterface({
370
+ input: process.stdin,
371
+ output: process.stdout
372
+ });
373
+ console.log('');
374
+ console.log('Installed skills:');
375
+ console.log('');
376
+ for (let i = 0; i < skills.length; i++) {
377
+ const skill = skills[i];
378
+ console.log(` [${i + 1}] ${skill.name}`);
379
+ console.log(` ${skill.description}`);
380
+ console.log('');
381
+ }
382
+ console.log('Enter the numbers of the skills to uninstall (separated by spaces, e.g., "1 3 5"):');
383
+ console.log('Press Enter to cancel:');
384
+ rl.question('Your choice: ', (answer) => {
385
+ rl.close();
386
+ const input = answer.trim();
387
+ if (!input) {
388
+ resolve([]);
389
+ return;
390
+ }
391
+ // Parse the input and extract valid indices
392
+ const selectedNumbers = input.split(/\s+/).map(s => parseInt(s, 10));
393
+ const validIndices = [];
394
+ for (const num of selectedNumbers) {
395
+ if (isNaN(num)) {
396
+ console.log(`Skipping invalid number: ${input}`);
397
+ continue;
398
+ }
399
+ if (num < 1 || num > skills.length) {
400
+ console.log(`Skipping out of range number: ${num}`);
401
+ continue;
402
+ }
403
+ validIndices.push(num - 1); // Convert to 0-based index
404
+ }
405
+ // Remove duplicates
406
+ const uniqueIndices = [...new Set(validIndices)];
407
+ if (uniqueIndices.length === 0) {
408
+ console.log('No valid skills selected.');
409
+ resolve([]);
410
+ return;
411
+ }
412
+ console.log('');
413
+ console.log(`Selected skills: ${uniqueIndices.map(i => skills[i].name).join(', ')}`);
414
+ resolve(uniqueIndices);
415
+ });
416
+ });
417
+ }
418
+ async function listSkillsFromBothDirectories(aiTool) {
419
+ const globalDir = skillsManager.getGlobalSkillsDir(aiTool);
420
+ const localDir = skillsManager.getLocalSkillsDir(aiTool);
421
+ const globalSkills = await skillsManager.listInstalled(globalDir, aiTool);
422
+ const localSkills = await skillsManager.listInstalled(localDir, aiTool);
423
+ let hasAny = false;
424
+ console.log(`AI Tool: ${aiTool}`);
425
+ console.log('');
426
+ // Global skills
427
+ console.log(`Global skills (${globalDir}):`);
428
+ console.log('');
429
+ if (globalSkills.length === 0) {
430
+ console.log(' No skills installed.');
431
+ }
432
+ else {
433
+ hasAny = true;
434
+ for (const skill of globalSkills) {
435
+ console.log(` ${skill.name}`);
436
+ console.log(` Description: ${skill.description}`);
437
+ console.log('');
438
+ }
439
+ }
440
+ console.log('');
441
+ // Local skills
442
+ console.log(`Local skills (${localDir}):`);
443
+ console.log('');
444
+ if (localSkills.length === 0) {
445
+ console.log(' No skills installed.');
446
+ }
447
+ else {
448
+ hasAny = true;
449
+ for (const skill of localSkills) {
450
+ console.log(` ${skill.name}`);
451
+ console.log(` Description: ${skill.description}`);
452
+ console.log('');
453
+ }
454
+ }
455
+ if (!hasAny) {
456
+ console.log('No skills installed in either location.');
457
+ }
458
+ }
459
+ program.parse();
@@ -0,0 +1,19 @@
1
+ export declare enum ErrorType {
2
+ AUTHENTICATION = "AUTHENTICATION",
3
+ ACCESS_DENIED = "ACCESS_DENIED",
4
+ INVALID_INPUT = "INVALID_INPUT",
5
+ NETWORK = "NETWORK",
6
+ NOT_FOUND = "NOT_FOUND",
7
+ CONTENT_VALIDATION = "CONTENT_VALIDATION",
8
+ ATTACHMENT = "ATTACHMENT",
9
+ INVALID_SKILL = "INVALID_SKILL",
10
+ EXTRACTION_FAILED = "EXTRACTION_FAILED",
11
+ ALREADY_INSTALLED = "ALREADY_INSTALLED",
12
+ UNKNOWN = "UNKNOWN"
13
+ }
14
+ export declare class WikiError extends Error {
15
+ readonly type: ErrorType;
16
+ readonly cause?: unknown | undefined;
17
+ constructor(type: ErrorType, message: string, cause?: unknown | undefined);
18
+ static is(error: unknown): error is WikiError;
19
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WikiError = exports.ErrorType = void 0;
4
+ var ErrorType;
5
+ (function (ErrorType) {
6
+ ErrorType["AUTHENTICATION"] = "AUTHENTICATION";
7
+ ErrorType["ACCESS_DENIED"] = "ACCESS_DENIED";
8
+ ErrorType["INVALID_INPUT"] = "INVALID_INPUT";
9
+ ErrorType["NETWORK"] = "NETWORK";
10
+ ErrorType["NOT_FOUND"] = "NOT_FOUND";
11
+ ErrorType["CONTENT_VALIDATION"] = "CONTENT_VALIDATION";
12
+ ErrorType["ATTACHMENT"] = "ATTACHMENT";
13
+ ErrorType["INVALID_SKILL"] = "INVALID_SKILL";
14
+ ErrorType["EXTRACTION_FAILED"] = "EXTRACTION_FAILED";
15
+ ErrorType["ALREADY_INSTALLED"] = "ALREADY_INSTALLED";
16
+ ErrorType["UNKNOWN"] = "UNKNOWN";
17
+ })(ErrorType || (exports.ErrorType = ErrorType = {}));
18
+ class WikiError extends Error {
19
+ constructor(type, message, cause) {
20
+ super(message);
21
+ this.type = type;
22
+ this.cause = cause;
23
+ this.name = 'WikiError';
24
+ }
25
+ static is(error) {
26
+ return error instanceof WikiError;
27
+ }
28
+ }
29
+ exports.WikiError = WikiError;
@@ -0,0 +1,14 @@
1
+ export type LogLevelName = 'debug' | 'info' | 'warn' | 'error';
2
+ export declare class Logger {
3
+ private readonly scope;
4
+ private static globalLevel;
5
+ constructor(scope: string);
6
+ static setLevel(level: LogLevelName): void;
7
+ private shouldLog;
8
+ private format;
9
+ private stringify;
10
+ debug(message: string, ...extra: unknown[]): void;
11
+ info(message: string, ...extra: unknown[]): void;
12
+ warn(message: string, ...extra: unknown[]): void;
13
+ error(message: string, ...extra: unknown[]): void;
14
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Logger = void 0;
4
+ const levelWeights = {
5
+ debug: 10,
6
+ info: 20,
7
+ warn: 30,
8
+ error: 40
9
+ };
10
+ class Logger {
11
+ constructor(scope) {
12
+ this.scope = scope;
13
+ }
14
+ static setLevel(level) {
15
+ if (levelWeights[level] !== undefined) {
16
+ Logger.globalLevel = level;
17
+ }
18
+ }
19
+ shouldLog(level) {
20
+ return levelWeights[level] >= levelWeights[Logger.globalLevel];
21
+ }
22
+ format(level, message, extra) {
23
+ const timestamp = new Date().toISOString();
24
+ const context = extra && extra.length ? ' ' + extra.map(this.stringify).join(' ') : '';
25
+ return `[${timestamp}] [${level.toUpperCase()}] [${this.scope}] ${message}${context}`;
26
+ }
27
+ stringify(value) {
28
+ if (typeof value === 'string') {
29
+ if (value.length > 400) {
30
+ return `${value.slice(0, 400)}…`;
31
+ }
32
+ return value;
33
+ }
34
+ try {
35
+ return JSON.stringify(value, (key, val) => {
36
+ if (typeof key === 'string' && key.toLowerCase().includes('password')) {
37
+ return '[REDACTED]';
38
+ }
39
+ return val;
40
+ });
41
+ }
42
+ catch {
43
+ return String(value);
44
+ }
45
+ }
46
+ debug(message, ...extra) {
47
+ if (this.shouldLog('debug')) {
48
+ process.stderr.write(this.format('debug', message, extra) + '\n');
49
+ }
50
+ }
51
+ info(message, ...extra) {
52
+ if (this.shouldLog('info')) {
53
+ process.stderr.write(this.format('info', message, extra) + '\n');
54
+ }
55
+ }
56
+ warn(message, ...extra) {
57
+ if (this.shouldLog('warn')) {
58
+ process.stderr.write(this.format('warn', message, extra) + '\n');
59
+ }
60
+ }
61
+ error(message, ...extra) {
62
+ if (this.shouldLog('error')) {
63
+ process.stderr.write(this.format('error', message, extra) + '\n');
64
+ }
65
+ }
66
+ }
67
+ exports.Logger = Logger;
68
+ Logger.globalLevel = 'info';
@@ -0,0 +1,41 @@
1
+ export interface DownloadOptions {
2
+ url: string;
3
+ outputPath: string;
4
+ username?: string;
5
+ password?: string;
6
+ baseUrl?: string;
7
+ allowSelfSigned?: boolean;
8
+ requireAuth?: boolean;
9
+ }
10
+ export interface DownloadResult {
11
+ success: boolean;
12
+ filepath: string;
13
+ size: number;
14
+ error?: string;
15
+ }
16
+ /**
17
+ * Download Manager with support for both authenticated (wiki) and public downloads
18
+ */
19
+ export declare class DownloadManager {
20
+ private readonly logger;
21
+ private http?;
22
+ private auth?;
23
+ /**
24
+ * Download a file from a URL
25
+ * If requireAuth is true, use the AuthService for authentication
26
+ */
27
+ download(options: DownloadOptions): Promise<DownloadResult>;
28
+ /**
29
+ * Download with authentication (for wiki URLs)
30
+ */
31
+ private downloadWithAuth;
32
+ /**
33
+ * Direct download without authentication (for public URLs)
34
+ */
35
+ private downloadDirect;
36
+ private formatSize;
37
+ /**
38
+ * Extract filename from URL
39
+ */
40
+ extractFilenameFromUrl(url: string): string | null;
41
+ }