@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/LICENSE +21 -0
- package/README.md +296 -0
- package/dist/archive/archive-extractor.d.ts +29 -0
- package/dist/archive/archive-extractor.js +123 -0
- package/dist/auth/auth-service.d.ts +21 -0
- package/dist/auth/auth-service.js +260 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +459 -0
- package/dist/core/errors.d.ts +19 -0
- package/dist/core/errors.js +29 -0
- package/dist/core/logger.d.ts +14 -0
- package/dist/core/logger.js +68 -0
- package/dist/download/download-manager.d.ts +41 -0
- package/dist/download/download-manager.js +184 -0
- package/dist/downloader.d.ts +29 -0
- package/dist/downloader.js +163 -0
- package/dist/http/http-client.d.ts +25 -0
- package/dist/http/http-client.js +172 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +14 -0
- package/dist/installer/install-service.d.ts +40 -0
- package/dist/installer/install-service.js +184 -0
- package/dist/skills/skill-validator.d.ts +33 -0
- package/dist/skills/skill-validator.js +124 -0
- package/dist/skills/skills-manager.d.ts +54 -0
- package/dist/skills/skills-manager.js +127 -0
- package/dist/source/source-detector.d.ts +29 -0
- package/dist/source/source-detector.js +88 -0
- package/package.json +62 -0
|
@@ -0,0 +1,184 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.DownloadManager = void 0;
|
|
40
|
+
const fs_1 = require("fs");
|
|
41
|
+
const promises_1 = require("fs/promises");
|
|
42
|
+
const path_1 = __importDefault(require("path"));
|
|
43
|
+
const promises_2 = require("stream/promises");
|
|
44
|
+
const axios_1 = __importDefault(require("axios"));
|
|
45
|
+
const http_client_1 = require("../http/http-client");
|
|
46
|
+
const auth_service_1 = require("../auth/auth-service");
|
|
47
|
+
const logger_1 = require("../core/logger");
|
|
48
|
+
const errors_1 = require("../core/errors");
|
|
49
|
+
/**
|
|
50
|
+
* Download Manager with support for both authenticated (wiki) and public downloads
|
|
51
|
+
*/
|
|
52
|
+
class DownloadManager {
|
|
53
|
+
constructor() {
|
|
54
|
+
this.logger = new logger_1.Logger('DownloadManager');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Download a file from a URL
|
|
58
|
+
* If requireAuth is true, use the AuthService for authentication
|
|
59
|
+
*/
|
|
60
|
+
async download(options) {
|
|
61
|
+
try {
|
|
62
|
+
// Ensure parent directory exists
|
|
63
|
+
const dir = path_1.default.dirname(options.outputPath);
|
|
64
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
65
|
+
this.logger.info(`Downloading: ${options.url} -> ${options.outputPath}`);
|
|
66
|
+
if (options.requireAuth) {
|
|
67
|
+
return await this.downloadWithAuth(options);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
return await this.downloadDirect(options);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
75
|
+
this.logger.error(`Download failed: ${errorMsg}`);
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
filepath: options.outputPath,
|
|
79
|
+
size: 0,
|
|
80
|
+
error: errorMsg
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Download with authentication (for wiki URLs)
|
|
86
|
+
*/
|
|
87
|
+
async downloadWithAuth(options) {
|
|
88
|
+
if (!options.username || !options.password || !options.baseUrl) {
|
|
89
|
+
throw new errors_1.WikiError(errors_1.ErrorType.AUTHENTICATION, 'Username, password, and baseUrl are required for authenticated downloads');
|
|
90
|
+
}
|
|
91
|
+
// Initialize HTTP client and auth service if not already done
|
|
92
|
+
if (!this.http || !this.auth) {
|
|
93
|
+
this.http = new http_client_1.HttpClient({
|
|
94
|
+
baseUrl: options.baseUrl,
|
|
95
|
+
allowSelfSigned: options.allowSelfSigned ?? true,
|
|
96
|
+
timeoutMs: 60000
|
|
97
|
+
});
|
|
98
|
+
this.auth = new auth_service_1.AuthService(this.http, {
|
|
99
|
+
baseUrl: options.baseUrl,
|
|
100
|
+
username: options.username,
|
|
101
|
+
password: options.password
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// Ensure authenticated before downloading
|
|
105
|
+
await this.auth.ensureAuthenticated();
|
|
106
|
+
this.logger.info('Authentication successful, starting download');
|
|
107
|
+
// Perform streaming download
|
|
108
|
+
const response = await this.http.get(options.url, {
|
|
109
|
+
responseType: 'stream'
|
|
110
|
+
});
|
|
111
|
+
// Save file
|
|
112
|
+
const writer = (0, fs_1.createWriteStream)(options.outputPath);
|
|
113
|
+
await (0, promises_2.pipeline)(response.data, writer);
|
|
114
|
+
// Get file size
|
|
115
|
+
const stats = await Promise.resolve().then(() => __importStar(require('fs'))).then(fs => fs.promises.stat(options.outputPath));
|
|
116
|
+
this.logger.info(`Download complete: ${options.outputPath} (${this.formatSize(stats.size)})`);
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
filepath: options.outputPath,
|
|
120
|
+
size: stats.size
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Direct download without authentication (for public URLs)
|
|
125
|
+
*/
|
|
126
|
+
async downloadDirect(options) {
|
|
127
|
+
const response = await (0, axios_1.default)({
|
|
128
|
+
method: 'GET',
|
|
129
|
+
url: options.url,
|
|
130
|
+
responseType: 'stream',
|
|
131
|
+
timeout: 60000,
|
|
132
|
+
maxRedirects: 5,
|
|
133
|
+
headers: {
|
|
134
|
+
'User-Agent': 'Skill-Install/1.0.0'
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
// Save file
|
|
138
|
+
const writer = (0, fs_1.createWriteStream)(options.outputPath);
|
|
139
|
+
await (0, promises_2.pipeline)(response.data, writer);
|
|
140
|
+
// Get file size
|
|
141
|
+
const stats = await Promise.resolve().then(() => __importStar(require('fs'))).then(fs => fs.promises.stat(options.outputPath));
|
|
142
|
+
this.logger.info(`Download complete: ${options.outputPath} (${this.formatSize(stats.size)})`);
|
|
143
|
+
return {
|
|
144
|
+
success: true,
|
|
145
|
+
filepath: options.outputPath,
|
|
146
|
+
size: stats.size
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
formatSize(bytes) {
|
|
150
|
+
if (bytes === 0)
|
|
151
|
+
return '0 B';
|
|
152
|
+
const k = 1024;
|
|
153
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
154
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
155
|
+
return `${(bytes / Math.pow(k, i)).toFixed(i === 0 ? 0 : 2)} ${sizes[i]}`;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Extract filename from URL
|
|
159
|
+
*/
|
|
160
|
+
extractFilenameFromUrl(url) {
|
|
161
|
+
try {
|
|
162
|
+
const urlObj = new URL(url);
|
|
163
|
+
const pathname = urlObj.pathname;
|
|
164
|
+
const parts = pathname.split('/');
|
|
165
|
+
const lastPart = parts[parts.length - 1];
|
|
166
|
+
// Remove query parameters
|
|
167
|
+
const filename = lastPart.split('?')[0];
|
|
168
|
+
if (filename && filename !== '' && filename !== 'attachments') {
|
|
169
|
+
return decodeURIComponent(filename);
|
|
170
|
+
}
|
|
171
|
+
// Try to find filename in query parameters
|
|
172
|
+
const params = new URLSearchParams(urlObj.search);
|
|
173
|
+
const fileNameParam = params.get('fileName') || params.get('filename');
|
|
174
|
+
if (fileNameParam) {
|
|
175
|
+
return decodeURIComponent(fileNameParam);
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
exports.DownloadManager = DownloadManager;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface DownloadOptions {
|
|
2
|
+
url: string;
|
|
3
|
+
username: string;
|
|
4
|
+
password: string;
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
output?: string;
|
|
7
|
+
allowSelfSigned?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface DownloadResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
filepath: string;
|
|
12
|
+
size: number;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class Downloader {
|
|
16
|
+
private readonly logger;
|
|
17
|
+
private readonly http;
|
|
18
|
+
private readonly auth;
|
|
19
|
+
constructor(options: {
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
username: string;
|
|
22
|
+
password: string;
|
|
23
|
+
allowSelfSigned?: boolean;
|
|
24
|
+
});
|
|
25
|
+
download(options: DownloadOptions): Promise<DownloadResult>;
|
|
26
|
+
private extractFilenameFromUrl;
|
|
27
|
+
private pathExists;
|
|
28
|
+
private formatSize;
|
|
29
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.Downloader = void 0;
|
|
40
|
+
const fs_1 = require("fs");
|
|
41
|
+
const promises_1 = require("fs/promises");
|
|
42
|
+
const path_1 = __importDefault(require("path"));
|
|
43
|
+
const promises_2 = require("stream/promises");
|
|
44
|
+
const http_client_1 = require("./http/http-client");
|
|
45
|
+
const auth_service_1 = require("./auth/auth-service");
|
|
46
|
+
const logger_1 = require("./core/logger");
|
|
47
|
+
class Downloader {
|
|
48
|
+
constructor(options) {
|
|
49
|
+
this.logger = new logger_1.Logger('Downloader');
|
|
50
|
+
this.http = new http_client_1.HttpClient({
|
|
51
|
+
baseUrl: options.baseUrl,
|
|
52
|
+
allowSelfSigned: options.allowSelfSigned ?? true,
|
|
53
|
+
timeoutMs: 60000
|
|
54
|
+
});
|
|
55
|
+
this.auth = new auth_service_1.AuthService(this.http, {
|
|
56
|
+
baseUrl: options.baseUrl,
|
|
57
|
+
username: options.username,
|
|
58
|
+
password: options.password
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async download(options) {
|
|
62
|
+
try {
|
|
63
|
+
// Ensure authenticated before downloading
|
|
64
|
+
await this.auth.ensureAuthenticated();
|
|
65
|
+
this.logger.info('Authentication successful, starting download');
|
|
66
|
+
// Determine output path
|
|
67
|
+
let outputPath;
|
|
68
|
+
if (options.output) {
|
|
69
|
+
// If output is a directory, use filename from URL
|
|
70
|
+
const stat = await this.pathExists(options.output);
|
|
71
|
+
if (stat === 'dir') {
|
|
72
|
+
const filename = this.extractFilenameFromUrl(options.url) || 'download';
|
|
73
|
+
outputPath = path_1.default.join(options.output, filename);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Create parent directory if needed
|
|
77
|
+
const dir = path_1.default.dirname(options.output);
|
|
78
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
79
|
+
outputPath = options.output;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Use current directory with filename from URL
|
|
84
|
+
const filename = this.extractFilenameFromUrl(options.url) || 'download';
|
|
85
|
+
outputPath = path_1.default.join(process.cwd(), filename);
|
|
86
|
+
}
|
|
87
|
+
// Ensure parent directory exists
|
|
88
|
+
const dir = path_1.default.dirname(outputPath);
|
|
89
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
90
|
+
this.logger.info(`Downloading: ${options.url} -> ${outputPath}`);
|
|
91
|
+
// Perform streaming download
|
|
92
|
+
const response = await this.http.get(options.url, {
|
|
93
|
+
responseType: 'stream'
|
|
94
|
+
});
|
|
95
|
+
// Save file
|
|
96
|
+
const writer = (0, fs_1.createWriteStream)(outputPath);
|
|
97
|
+
await (0, promises_2.pipeline)(response.data, writer);
|
|
98
|
+
// Get file size
|
|
99
|
+
const stats = await Promise.resolve().then(() => __importStar(require('fs'))).then(fs => fs.promises.stat(outputPath));
|
|
100
|
+
this.logger.info(`Download complete: ${outputPath} (${this.formatSize(stats.size)})`);
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
filepath: outputPath,
|
|
104
|
+
size: stats.size
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
109
|
+
this.logger.error(`Download failed: ${errorMsg}`);
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
filepath: options.output || options.url,
|
|
113
|
+
size: 0,
|
|
114
|
+
error: errorMsg
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
extractFilenameFromUrl(url) {
|
|
119
|
+
try {
|
|
120
|
+
// Parse URL to extract filename
|
|
121
|
+
const urlObj = new URL(url);
|
|
122
|
+
const pathname = urlObj.pathname;
|
|
123
|
+
// Handle typical Confluence attachment URLs
|
|
124
|
+
// e.g., /download/attachments/12345/filename.pdf?api=v2
|
|
125
|
+
const parts = pathname.split('/');
|
|
126
|
+
const lastPart = parts[parts.length - 1];
|
|
127
|
+
// Remove query parameters
|
|
128
|
+
const filename = lastPart.split('?')[0];
|
|
129
|
+
// Handle versioning parameter like filename.pdf?version=1
|
|
130
|
+
if (filename && filename !== '' && filename !== 'attachments') {
|
|
131
|
+
return decodeURIComponent(filename);
|
|
132
|
+
}
|
|
133
|
+
// Try to find filename in query parameters
|
|
134
|
+
const params = new URLSearchParams(urlObj.search);
|
|
135
|
+
const fileNameParam = params.get('fileName') || params.get('filename');
|
|
136
|
+
if (fileNameParam) {
|
|
137
|
+
return decodeURIComponent(fileNameParam);
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async pathExists(p) {
|
|
146
|
+
try {
|
|
147
|
+
const stat = await Promise.resolve().then(() => __importStar(require('fs'))).then(fs => fs.promises.stat(p));
|
|
148
|
+
return stat.isDirectory() ? 'dir' : 'file';
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
formatSize(bytes) {
|
|
155
|
+
if (bytes === 0)
|
|
156
|
+
return '0 B';
|
|
157
|
+
const k = 1024;
|
|
158
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
159
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
160
|
+
return `${(bytes / Math.pow(k, i)).toFixed(i === 0 ? 0 : 2)} ${sizes[i]}`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.Downloader = Downloader;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
|
+
export interface HttpClientOptions {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
allowSelfSigned: boolean;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class HttpClient {
|
|
8
|
+
private readonly options;
|
|
9
|
+
private readonly client;
|
|
10
|
+
private readonly logger;
|
|
11
|
+
private cookieStore;
|
|
12
|
+
constructor(options: HttpClientOptions);
|
|
13
|
+
private setupInterceptors;
|
|
14
|
+
private extractCookies;
|
|
15
|
+
private normalizeError;
|
|
16
|
+
get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
17
|
+
post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
18
|
+
getCookies(): string[];
|
|
19
|
+
setCookies(cookies: string[]): void;
|
|
20
|
+
clearCookies(): void;
|
|
21
|
+
private addCookieHeader;
|
|
22
|
+
setHeader(name: string, value: string): void;
|
|
23
|
+
removeHeader(name: string): void;
|
|
24
|
+
getBaseURL(): string;
|
|
25
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
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.HttpClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const https_1 = __importDefault(require("https"));
|
|
9
|
+
const logger_1 = require("../core/logger");
|
|
10
|
+
const errors_1 = require("../core/errors");
|
|
11
|
+
class HttpClient {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
this.logger = new logger_1.Logger('HttpClient');
|
|
15
|
+
this.cookieStore = new Map();
|
|
16
|
+
// 设置环境变量来忽略证书验证(必须在创建axios实例之前)
|
|
17
|
+
this.logger.info(`HTTP Client config: baseUrl=${options.baseUrl}, allowSelfSigned=${options.allowSelfSigned}`);
|
|
18
|
+
if (options.baseUrl.startsWith('https://') && options.allowSelfSigned) {
|
|
19
|
+
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
|
|
20
|
+
this.logger.warn('SSL certificate verification disabled for self-signed certificates');
|
|
21
|
+
}
|
|
22
|
+
// 配置axios实例
|
|
23
|
+
const axiosConfig = {
|
|
24
|
+
baseURL: options.baseUrl,
|
|
25
|
+
timeout: options.timeoutMs ?? 30000,
|
|
26
|
+
withCredentials: true,
|
|
27
|
+
maxRedirects: 5,
|
|
28
|
+
validateStatus: (status) => status >= 200 && status < 400,
|
|
29
|
+
headers: {
|
|
30
|
+
'User-Agent': 'Wiki-Download-Tool/1.0.0',
|
|
31
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
32
|
+
'Accept-Language': 'en-US,en;q=0.5',
|
|
33
|
+
'Accept-Encoding': 'gzip, deflate',
|
|
34
|
+
'Connection': 'keep-alive',
|
|
35
|
+
'Upgrade-Insecure-Requests': '1'
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
// 如果需要支持自签名证书,使用自定义https agent
|
|
39
|
+
if (options.baseUrl.startsWith('https://') && options.allowSelfSigned) {
|
|
40
|
+
axiosConfig.httpsAgent = new https_1.default.Agent({
|
|
41
|
+
rejectUnauthorized: false
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
this.client = axios_1.default.create(axiosConfig);
|
|
45
|
+
this.setupInterceptors();
|
|
46
|
+
}
|
|
47
|
+
setupInterceptors() {
|
|
48
|
+
this.client.interceptors.request.use((config) => {
|
|
49
|
+
this.logger.debug(`HTTP ${config.method?.toUpperCase()} ${config.url}`);
|
|
50
|
+
this.addCookieHeader(config);
|
|
51
|
+
return config;
|
|
52
|
+
});
|
|
53
|
+
this.client.interceptors.response.use((response) => {
|
|
54
|
+
this.logger.debug(`HTTP ${response.status} ${response.config.url}`);
|
|
55
|
+
this.extractCookies(response);
|
|
56
|
+
return response;
|
|
57
|
+
}, (error) => {
|
|
58
|
+
throw this.normalizeError(error);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
extractCookies(response) {
|
|
62
|
+
const setCookieHeader = response.headers['set-cookie'];
|
|
63
|
+
if (setCookieHeader) {
|
|
64
|
+
const cookies = Array.isArray(setCookieHeader) ? setCookieHeader : [setCookieHeader];
|
|
65
|
+
cookies.forEach(cookie => {
|
|
66
|
+
const cookieParts = cookie.split(';');
|
|
67
|
+
if (cookieParts.length > 0) {
|
|
68
|
+
const cookiePart = cookieParts[0];
|
|
69
|
+
if (cookiePart) {
|
|
70
|
+
const [name, value] = cookiePart.split('=');
|
|
71
|
+
if (name && value) {
|
|
72
|
+
this.cookieStore.set(name.trim(), `${name.trim()}=${value}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
normalizeError(error) {
|
|
80
|
+
this.logger.error('HTTP Error details:', {
|
|
81
|
+
message: error.message,
|
|
82
|
+
code: error.code,
|
|
83
|
+
status: error.response?.status,
|
|
84
|
+
statusText: error.response?.statusText,
|
|
85
|
+
url: error.config?.url,
|
|
86
|
+
responseData: error.response?.data ? String(error.response.data).substring(0, 500) : undefined
|
|
87
|
+
});
|
|
88
|
+
if (error.response) {
|
|
89
|
+
const status = error.response.status;
|
|
90
|
+
const statusText = error.response.statusText;
|
|
91
|
+
switch (status) {
|
|
92
|
+
case 401:
|
|
93
|
+
return new errors_1.WikiError(errors_1.ErrorType.AUTHENTICATION, 'Authentication failed. Please check your credentials.', error);
|
|
94
|
+
case 403:
|
|
95
|
+
return new errors_1.WikiError(errors_1.ErrorType.ACCESS_DENIED, 'Access denied. You may not have permission to access this resource.', error);
|
|
96
|
+
case 404:
|
|
97
|
+
return new errors_1.WikiError(errors_1.ErrorType.NOT_FOUND, 'The requested page or resource was not found.', error);
|
|
98
|
+
default:
|
|
99
|
+
return new errors_1.WikiError(errors_1.ErrorType.NETWORK, `HTTP Error ${status}: ${statusText}`, error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (error.request) {
|
|
103
|
+
return new errors_1.WikiError(errors_1.ErrorType.NETWORK, 'Network error: Unable to reach the server. Please check your internet connection.', error);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return new errors_1.WikiError(errors_1.ErrorType.UNKNOWN, `Request setup error: ${error.message}`, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async get(url, config) {
|
|
110
|
+
try {
|
|
111
|
+
return await this.client.get(url, config);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw this.normalizeError(error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async post(url, data, config) {
|
|
118
|
+
try {
|
|
119
|
+
return await this.client.post(url, data, config);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
throw this.normalizeError(error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
getCookies() {
|
|
126
|
+
try {
|
|
127
|
+
return Array.from(this.cookieStore.values());
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
this.logger.error('Error getting cookies:', error);
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
setCookies(cookies) {
|
|
135
|
+
try {
|
|
136
|
+
cookies.forEach(cookie => {
|
|
137
|
+
const [name, value] = cookie.split('=');
|
|
138
|
+
if (name && value) {
|
|
139
|
+
this.cookieStore.set(name.trim(), `${name.trim()}=${value}`);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
this.logger.error('Error setting cookies:', error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
clearCookies() {
|
|
148
|
+
try {
|
|
149
|
+
this.cookieStore.clear();
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
this.logger.error('Error clearing cookies:', error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
addCookieHeader(config) {
|
|
156
|
+
const cookies = this.getCookies();
|
|
157
|
+
if (cookies.length > 0) {
|
|
158
|
+
config.headers = config.headers || {};
|
|
159
|
+
config.headers['Cookie'] = cookies.join('; ');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
setHeader(name, value) {
|
|
163
|
+
this.client.defaults.headers.common[name] = value;
|
|
164
|
+
}
|
|
165
|
+
removeHeader(name) {
|
|
166
|
+
delete this.client.defaults.headers.common[name];
|
|
167
|
+
}
|
|
168
|
+
getBaseURL() {
|
|
169
|
+
return this.options.baseUrl;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
exports.HttpClient = HttpClient;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Downloader, DownloadOptions, DownloadResult } from './downloader';
|
|
2
|
+
export { HttpClient, HttpClientOptions } from './http/http-client';
|
|
3
|
+
export { AuthService, AuthConfig } from './auth/auth-service';
|
|
4
|
+
export { Logger, LogLevelName } from './core/logger';
|
|
5
|
+
export { WikiError, ErrorType } from './core/errors';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ErrorType = exports.WikiError = exports.Logger = exports.AuthService = exports.HttpClient = exports.Downloader = void 0;
|
|
4
|
+
var downloader_1 = require("./downloader");
|
|
5
|
+
Object.defineProperty(exports, "Downloader", { enumerable: true, get: function () { return downloader_1.Downloader; } });
|
|
6
|
+
var http_client_1 = require("./http/http-client");
|
|
7
|
+
Object.defineProperty(exports, "HttpClient", { enumerable: true, get: function () { return http_client_1.HttpClient; } });
|
|
8
|
+
var auth_service_1 = require("./auth/auth-service");
|
|
9
|
+
Object.defineProperty(exports, "AuthService", { enumerable: true, get: function () { return auth_service_1.AuthService; } });
|
|
10
|
+
var logger_1 = require("./core/logger");
|
|
11
|
+
Object.defineProperty(exports, "Logger", { enumerable: true, get: function () { return logger_1.Logger; } });
|
|
12
|
+
var errors_1 = require("./core/errors");
|
|
13
|
+
Object.defineProperty(exports, "WikiError", { enumerable: true, get: function () { return errors_1.WikiError; } });
|
|
14
|
+
Object.defineProperty(exports, "ErrorType", { enumerable: true, get: function () { return errors_1.ErrorType; } });
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface InstallOptions {
|
|
2
|
+
source: string;
|
|
3
|
+
skillsDir?: string;
|
|
4
|
+
username?: string;
|
|
5
|
+
password?: string;
|
|
6
|
+
allowSelfSigned?: boolean;
|
|
7
|
+
force?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface InstallResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
skillName?: string;
|
|
12
|
+
skillPath?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class InstallService {
|
|
16
|
+
private readonly logger;
|
|
17
|
+
private readonly sourceDetector;
|
|
18
|
+
private readonly downloadManager;
|
|
19
|
+
private readonly extractor;
|
|
20
|
+
private readonly validator;
|
|
21
|
+
private readonly skillsManager;
|
|
22
|
+
/**
|
|
23
|
+
* Install a skill from a source (URL or local file)
|
|
24
|
+
*/
|
|
25
|
+
install(options: InstallOptions): Promise<InstallResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Find the skill directory in the extracted files
|
|
28
|
+
* The archive may contain a single root directory or have SKILL.md at root
|
|
29
|
+
*/
|
|
30
|
+
private findSkillDirectory;
|
|
31
|
+
/**
|
|
32
|
+
* Move a directory from one location to another
|
|
33
|
+
* Handles cross-device moves by copying and then deleting
|
|
34
|
+
*/
|
|
35
|
+
private moveDirectory;
|
|
36
|
+
/**
|
|
37
|
+
* Recursively copy a directory
|
|
38
|
+
*/
|
|
39
|
+
private copyDirectory;
|
|
40
|
+
}
|