@sstar/skill-install 1.0.0 → 1.1.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/README.md +122 -122
- package/dist/cli.js +371 -12
- package/dist/core/errors.d.ts +1 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/logger.js +1 -1
- package/dist/core/package-detector.d.ts +44 -0
- package/dist/core/package-detector.js +299 -0
- package/dist/http/http-client.js +1 -2
- package/dist/installer/install-service.d.ts +67 -5
- package/dist/installer/install-service.js +265 -56
- package/dist/plugins/plugin-manager.d.ts +95 -0
- package/dist/plugins/plugin-manager.js +475 -0
- package/dist/plugins/plugin-validator.d.ts +35 -0
- package/dist/plugins/plugin-validator.js +151 -0
- package/dist/plugins/types.d.ts +197 -0
- package/dist/plugins/types.js +16 -0
- package/dist/wiki/index.d.ts +3 -0
- package/dist/wiki/index.js +9 -0
- package/dist/wiki/skill-selector.d.ts +13 -0
- package/dist/wiki/skill-selector.js +104 -0
- package/dist/wiki/wiki-parser.d.ts +26 -0
- package/dist/wiki/wiki-parser.js +105 -0
- package/dist/wiki/wiki-searcher.d.ts +33 -0
- package/dist/wiki/wiki-searcher.js +98 -0
- package/package.json +1 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin and Marketplace type definitions for Claude Code
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Supported package types
|
|
6
|
+
*/
|
|
7
|
+
export declare enum PackageType {
|
|
8
|
+
SKILL = "skill",
|
|
9
|
+
PLUGIN = "plugin",
|
|
10
|
+
MARKETPLACE = "marketplace",
|
|
11
|
+
UNKNOWN = "unknown"
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Plugin metadata from plugin.json
|
|
15
|
+
*/
|
|
16
|
+
export interface PluginMetadata {
|
|
17
|
+
name: string;
|
|
18
|
+
version?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
author?: AuthorInfo;
|
|
21
|
+
homepage?: string;
|
|
22
|
+
repository?: string;
|
|
23
|
+
license?: string;
|
|
24
|
+
keywords?: string[];
|
|
25
|
+
commands?: string | string[];
|
|
26
|
+
agents?: string | string[];
|
|
27
|
+
hooks?: string | object;
|
|
28
|
+
mcpServers?: string | object;
|
|
29
|
+
lspServers?: string | object;
|
|
30
|
+
claudeCode?: {
|
|
31
|
+
minVersion?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Author information
|
|
36
|
+
*/
|
|
37
|
+
export interface AuthorInfo {
|
|
38
|
+
name: string;
|
|
39
|
+
email?: string;
|
|
40
|
+
url?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Marketplace metadata from marketplace.json
|
|
44
|
+
*/
|
|
45
|
+
export interface MarketplaceMetadata {
|
|
46
|
+
name: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
version?: string;
|
|
49
|
+
owner: {
|
|
50
|
+
name: string;
|
|
51
|
+
email?: string;
|
|
52
|
+
url?: string;
|
|
53
|
+
};
|
|
54
|
+
plugins: PluginDefinition[];
|
|
55
|
+
metadata?: {
|
|
56
|
+
description?: string;
|
|
57
|
+
version?: string;
|
|
58
|
+
pluginRoot?: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Plugin definition in marketplace.json
|
|
63
|
+
*/
|
|
64
|
+
export interface PluginDefinition {
|
|
65
|
+
name: string;
|
|
66
|
+
source: string | PluginSource;
|
|
67
|
+
description?: string;
|
|
68
|
+
version?: string;
|
|
69
|
+
author?: AuthorInfo;
|
|
70
|
+
homepage?: string;
|
|
71
|
+
repository?: string;
|
|
72
|
+
license?: string;
|
|
73
|
+
keywords?: string[];
|
|
74
|
+
category?: string;
|
|
75
|
+
tags?: string[];
|
|
76
|
+
commands?: string | string[];
|
|
77
|
+
agents?: string | string[];
|
|
78
|
+
hooks?: string | object;
|
|
79
|
+
mcpServers?: string | object;
|
|
80
|
+
lspServers?: string | object;
|
|
81
|
+
strict?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Plugin source reference (matches official Claude Code schema)
|
|
85
|
+
*/
|
|
86
|
+
export interface PluginSource {
|
|
87
|
+
source: 'url' | 'github' | 'git' | 'npm' | 'file' | 'directory';
|
|
88
|
+
repo?: string;
|
|
89
|
+
url?: string;
|
|
90
|
+
path?: string;
|
|
91
|
+
ref?: string;
|
|
92
|
+
sha?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Detected plugin information
|
|
96
|
+
*/
|
|
97
|
+
export interface PluginInfo {
|
|
98
|
+
name: string;
|
|
99
|
+
path: string;
|
|
100
|
+
version?: string;
|
|
101
|
+
source: 'plugin.json' | 'marketplace' | 'scanned' | 'standalone';
|
|
102
|
+
metadata: PluginMetadata | PluginDefinition;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Detected skill information
|
|
106
|
+
*/
|
|
107
|
+
export interface SkillInfo {
|
|
108
|
+
name: string;
|
|
109
|
+
path: string;
|
|
110
|
+
description: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Package structure after detection
|
|
114
|
+
*/
|
|
115
|
+
export interface PackageStructure {
|
|
116
|
+
type: PackageType;
|
|
117
|
+
plugins: PluginInfo[];
|
|
118
|
+
skills: SkillInfo[];
|
|
119
|
+
marketplace?: MarketplaceMetadata | null;
|
|
120
|
+
marketplaceAsPlugin?: PluginMetadata | null;
|
|
121
|
+
hasMarketplace: boolean;
|
|
122
|
+
hasPluginAtRoot: boolean;
|
|
123
|
+
hasPluginInClaudeDir: boolean;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Installation result types
|
|
127
|
+
*/
|
|
128
|
+
export interface BaseInstallResult {
|
|
129
|
+
type: 'marketplace' | 'plugin' | 'skill';
|
|
130
|
+
success: boolean;
|
|
131
|
+
path: string;
|
|
132
|
+
}
|
|
133
|
+
export interface MarketplaceInstallResult extends BaseInstallResult {
|
|
134
|
+
type: 'marketplace';
|
|
135
|
+
marketplace: string;
|
|
136
|
+
installedPlugins?: string[];
|
|
137
|
+
pluginAsPluginInstalled?: boolean;
|
|
138
|
+
}
|
|
139
|
+
export interface PluginInstallResult extends BaseInstallResult {
|
|
140
|
+
type: 'plugin';
|
|
141
|
+
plugin: string;
|
|
142
|
+
version?: string;
|
|
143
|
+
marketplace?: string;
|
|
144
|
+
error?: string;
|
|
145
|
+
}
|
|
146
|
+
export interface SkillInstallResult extends BaseInstallResult {
|
|
147
|
+
type: 'skill';
|
|
148
|
+
skill: string;
|
|
149
|
+
description?: string;
|
|
150
|
+
}
|
|
151
|
+
export type InstallResult = MarketplaceInstallResult | PluginInstallResult | SkillInstallResult;
|
|
152
|
+
/**
|
|
153
|
+
* Marketplace info in known_marketplaces.json
|
|
154
|
+
*/
|
|
155
|
+
export interface KnownMarketplace {
|
|
156
|
+
source: PluginSource;
|
|
157
|
+
installLocation: string;
|
|
158
|
+
lastUpdated: string;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Installed plugin info in installed_plugins.json
|
|
162
|
+
*/
|
|
163
|
+
export interface InstalledPluginEntry {
|
|
164
|
+
name: string;
|
|
165
|
+
scope: 'user' | 'local';
|
|
166
|
+
installPath: string;
|
|
167
|
+
version: string;
|
|
168
|
+
installedAt: string;
|
|
169
|
+
lastUpdated?: string;
|
|
170
|
+
isLocal: boolean;
|
|
171
|
+
projectPath?: string;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* known_marketplaces.json structure
|
|
175
|
+
*/
|
|
176
|
+
export interface KnownMarketplacesJson {
|
|
177
|
+
[marketplaceName: string]: KnownMarketplace;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* installed_plugins.json structure
|
|
181
|
+
*/
|
|
182
|
+
export interface InstalledPluginsJson {
|
|
183
|
+
version: 2;
|
|
184
|
+
plugins: {
|
|
185
|
+
[pluginKey: string]: InstalledPluginEntry[];
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Plugin list for user selection
|
|
190
|
+
*/
|
|
191
|
+
export interface SelectablePlugin {
|
|
192
|
+
name: string;
|
|
193
|
+
version?: string;
|
|
194
|
+
description?: string;
|
|
195
|
+
path: string;
|
|
196
|
+
fromMarketplace?: string;
|
|
197
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Plugin and Marketplace type definitions for Claude Code
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PackageType = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Supported package types
|
|
9
|
+
*/
|
|
10
|
+
var PackageType;
|
|
11
|
+
(function (PackageType) {
|
|
12
|
+
PackageType["SKILL"] = "skill";
|
|
13
|
+
PackageType["PLUGIN"] = "plugin";
|
|
14
|
+
PackageType["MARKETPLACE"] = "marketplace";
|
|
15
|
+
PackageType["UNKNOWN"] = "unknown";
|
|
16
|
+
})(PackageType || (exports.PackageType = PackageType = {}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SkillSelector = exports.WikiParser = exports.WikiSearcher = void 0;
|
|
4
|
+
var wiki_searcher_1 = require("./wiki-searcher");
|
|
5
|
+
Object.defineProperty(exports, "WikiSearcher", { enumerable: true, get: function () { return wiki_searcher_1.WikiSearcher; } });
|
|
6
|
+
var wiki_parser_1 = require("./wiki-parser");
|
|
7
|
+
Object.defineProperty(exports, "WikiParser", { enumerable: true, get: function () { return wiki_parser_1.WikiParser; } });
|
|
8
|
+
var skill_selector_1 = require("./skill-selector");
|
|
9
|
+
Object.defineProperty(exports, "SkillSelector", { enumerable: true, get: function () { return skill_selector_1.SkillSelector; } });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ParsedSkillItem } from './wiki-parser';
|
|
2
|
+
export declare class SkillSelector {
|
|
3
|
+
private readonly logger;
|
|
4
|
+
/**
|
|
5
|
+
* Display skill list and wait for user selection
|
|
6
|
+
* Returns the download URL of selected skill, or null if cancelled
|
|
7
|
+
*/
|
|
8
|
+
displayAndSelect(items: ParsedSkillItem[]): Promise<string | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Prompt user to select a skill from the list
|
|
11
|
+
*/
|
|
12
|
+
private promptChoice;
|
|
13
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
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.SkillSelector = void 0;
|
|
7
|
+
const readline_1 = __importDefault(require("readline"));
|
|
8
|
+
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
|
+
class SkillSelector {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.logger = new logger_1.Logger('SkillSelector');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Display skill list and wait for user selection
|
|
29
|
+
* Returns the download URL of selected skill, or null if cancelled
|
|
30
|
+
*/
|
|
31
|
+
async displayAndSelect(items) {
|
|
32
|
+
if (items.length === 0) {
|
|
33
|
+
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
|
+
}
|
|
53
|
+
}
|
|
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
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.SkillSelector = SkillSelector;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { HttpClient } from '../http/http-client';
|
|
2
|
+
import { WikiSkillItem } from './wiki-searcher';
|
|
3
|
+
export interface ParsedSkillItem extends WikiSkillItem {
|
|
4
|
+
description: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class WikiParser {
|
|
7
|
+
private httpClient;
|
|
8
|
+
private readonly logger;
|
|
9
|
+
constructor(httpClient: HttpClient);
|
|
10
|
+
/**
|
|
11
|
+
* Get attachment comment/description from the attachment metadata
|
|
12
|
+
*/
|
|
13
|
+
getAttachmentComments(attachmentId: string): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Get actual replies/comments on the attachment
|
|
16
|
+
*/
|
|
17
|
+
private getAttachmentReplies;
|
|
18
|
+
/**
|
|
19
|
+
* Parse all skill items and fetch their comments
|
|
20
|
+
*/
|
|
21
|
+
parseSkillItems(items: WikiSkillItem[]): Promise<ParsedSkillItem[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Extract plain text description from HTML comments
|
|
24
|
+
*/
|
|
25
|
+
private extractDescription;
|
|
26
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WikiParser = void 0;
|
|
4
|
+
const logger_1 = require("../core/logger");
|
|
5
|
+
class WikiParser {
|
|
6
|
+
constructor(httpClient) {
|
|
7
|
+
this.httpClient = httpClient;
|
|
8
|
+
this.logger = new logger_1.Logger('WikiParser');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get attachment comment/description from the attachment metadata
|
|
12
|
+
*/
|
|
13
|
+
async getAttachmentComments(attachmentId) {
|
|
14
|
+
try {
|
|
15
|
+
this.logger.info(`Fetching attachment details for ID: ${attachmentId}`);
|
|
16
|
+
// Get attachment details with metadata and extensions
|
|
17
|
+
const response = await this.httpClient.get(`/rest/api/content/${attachmentId}?expand=metadata,extensions`);
|
|
18
|
+
this.logger.debug(`Attachment API response status: ${response.status}`);
|
|
19
|
+
// Try to get comment from metadata or extensions
|
|
20
|
+
const comment = response.data?.metadata?.comment ||
|
|
21
|
+
response.data?.extensions?.comment ||
|
|
22
|
+
'';
|
|
23
|
+
if (comment) {
|
|
24
|
+
this.logger.info(`Found comment for attachment ${attachmentId}: ${comment}`);
|
|
25
|
+
return comment;
|
|
26
|
+
}
|
|
27
|
+
this.logger.debug(`No comment found in attachment metadata for: ${attachmentId}`);
|
|
28
|
+
// Fallback: try to get actual comments on the attachment
|
|
29
|
+
return await this.getAttachmentReplies(attachmentId);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
this.logger.error(`Failed to fetch attachment details for ${attachmentId}: ${error.message}`);
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get actual replies/comments on the attachment
|
|
38
|
+
*/
|
|
39
|
+
async getAttachmentReplies(attachmentId) {
|
|
40
|
+
try {
|
|
41
|
+
const expandParams = 'body.view,body.export_view,body.storage';
|
|
42
|
+
const response = await this.httpClient.get(`/rest/api/content/${attachmentId}/child/comment?expand=${expandParams}`);
|
|
43
|
+
if (!response.data?.results?.length) {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
const comments = response.data.results
|
|
47
|
+
.map((c) => {
|
|
48
|
+
return c.body?.view?.value ||
|
|
49
|
+
c.body?.export_view?.value ||
|
|
50
|
+
c.body?.storage?.value ||
|
|
51
|
+
'';
|
|
52
|
+
})
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join('\n---\n');
|
|
55
|
+
this.logger.info(`Found ${response.data.results.length} comment(s) for attachment: ${attachmentId}`);
|
|
56
|
+
return comments;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
this.logger.debug(`Failed to fetch attachment replies: ${error.message}`);
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parse all skill items and fetch their comments
|
|
65
|
+
*/
|
|
66
|
+
async parseSkillItems(items) {
|
|
67
|
+
const results = [];
|
|
68
|
+
this.logger.info(`Parsing ${items.length} skill item(s)...`);
|
|
69
|
+
for (const item of items) {
|
|
70
|
+
const comments = await this.getAttachmentComments(item.id);
|
|
71
|
+
results.push({
|
|
72
|
+
...item,
|
|
73
|
+
description: this.extractDescription(comments)
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Extract plain text description from HTML comments
|
|
80
|
+
*/
|
|
81
|
+
extractDescription(htmlComments) {
|
|
82
|
+
if (!htmlComments || htmlComments.trim() === '') {
|
|
83
|
+
return 'No description available.';
|
|
84
|
+
}
|
|
85
|
+
// Decode HTML entities
|
|
86
|
+
let text = htmlComments
|
|
87
|
+
.replace(/ /g, ' ')
|
|
88
|
+
.replace(/&/g, '&')
|
|
89
|
+
.replace(/</g, '<')
|
|
90
|
+
.replace(/>/g, '>')
|
|
91
|
+
.replace(/"/g, '"')
|
|
92
|
+
.replace(/'/g, "'")
|
|
93
|
+
.replace(/'/g, "'");
|
|
94
|
+
// Remove HTML tags
|
|
95
|
+
text = text.replace(/<[^>]*>/g, '');
|
|
96
|
+
// Clean up whitespace
|
|
97
|
+
text = text.replace(/\s+/g, ' ').trim();
|
|
98
|
+
// Limit to 200 characters
|
|
99
|
+
if (text.length > 200) {
|
|
100
|
+
text = text.substring(0, 197) + '...';
|
|
101
|
+
}
|
|
102
|
+
return text || 'No description available.';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.WikiParser = WikiParser;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { HttpClient } from '../http/http-client';
|
|
2
|
+
export interface WikiSkillItem {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
downloadUrl: string;
|
|
6
|
+
containerId: string;
|
|
7
|
+
pageUrl: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SearchOptions {
|
|
10
|
+
username: string;
|
|
11
|
+
password: string;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
allowSelfSigned?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare class WikiSearcher {
|
|
16
|
+
private readonly logger;
|
|
17
|
+
private httpClient;
|
|
18
|
+
private authService;
|
|
19
|
+
get httpClientInstance(): HttpClient;
|
|
20
|
+
constructor();
|
|
21
|
+
/**
|
|
22
|
+
* Search for attachments with agent_skill tag
|
|
23
|
+
*/
|
|
24
|
+
search(options: SearchOptions): Promise<WikiSkillItem[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Filter search results by file extension
|
|
27
|
+
*/
|
|
28
|
+
private filterByExtension;
|
|
29
|
+
/**
|
|
30
|
+
* Build full download URL from API response
|
|
31
|
+
*/
|
|
32
|
+
private buildDownloadUrl;
|
|
33
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WikiSearcher = void 0;
|
|
4
|
+
const http_client_1 = require("../http/http-client");
|
|
5
|
+
const auth_service_1 = require("../auth/auth-service");
|
|
6
|
+
const logger_1 = require("../core/logger");
|
|
7
|
+
const errors_1 = require("../core/errors");
|
|
8
|
+
const ALLOWED_EXTENSIONS = ['.tar.gz', '.tgz', '.zip'];
|
|
9
|
+
class WikiSearcher {
|
|
10
|
+
get httpClientInstance() {
|
|
11
|
+
return this.httpClient;
|
|
12
|
+
}
|
|
13
|
+
constructor() {
|
|
14
|
+
this.logger = new logger_1.Logger('WikiSearcher');
|
|
15
|
+
// Will be initialized in search method
|
|
16
|
+
this.httpClient = null;
|
|
17
|
+
this.authService = null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Search for attachments with agent_skill tag
|
|
21
|
+
*/
|
|
22
|
+
async search(options) {
|
|
23
|
+
// Determine base URL from environment or use default
|
|
24
|
+
const baseUrl = options.baseUrl || process.env.WIKI_BASE_URL || '';
|
|
25
|
+
if (!baseUrl) {
|
|
26
|
+
throw new errors_1.WikiError(errors_1.ErrorType.INVALID_INPUT, 'Wiki base URL is required. Set WIKI_BASE_URL environment variable or use --wiki-url option.');
|
|
27
|
+
}
|
|
28
|
+
// Initialize HTTP client and auth service
|
|
29
|
+
this.httpClient = new http_client_1.HttpClient({
|
|
30
|
+
baseUrl,
|
|
31
|
+
allowSelfSigned: options.allowSelfSigned ?? true
|
|
32
|
+
});
|
|
33
|
+
this.authService = new auth_service_1.AuthService(this.httpClient, {
|
|
34
|
+
baseUrl,
|
|
35
|
+
username: options.username,
|
|
36
|
+
password: options.password
|
|
37
|
+
});
|
|
38
|
+
// Authenticate
|
|
39
|
+
this.logger.info('Authenticating to Wiki...');
|
|
40
|
+
await this.authService.ensureAuthenticated();
|
|
41
|
+
this.logger.info('Authentication successful');
|
|
42
|
+
// Search for attachments with agent_skill label
|
|
43
|
+
this.logger.info('Searching for attachments with agent_skill tag...');
|
|
44
|
+
const response = await this.httpClient.get('/rest/api/content/search?cql=type=attachment+and+label=agent_skill&expand=container,version');
|
|
45
|
+
if (!response.data?.results) {
|
|
46
|
+
this.logger.warn('No results found');
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
// Filter by allowed extensions
|
|
50
|
+
const filtered = this.filterByExtension(response.data.results, ALLOWED_EXTENSIONS);
|
|
51
|
+
this.logger.info(`Found ${filtered.length} skill(s) matching criteria`);
|
|
52
|
+
return filtered;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Filter search results by file extension
|
|
56
|
+
*/
|
|
57
|
+
filterByExtension(results, extensions) {
|
|
58
|
+
const filtered = [];
|
|
59
|
+
if (!results) {
|
|
60
|
+
return filtered;
|
|
61
|
+
}
|
|
62
|
+
for (const item of results) {
|
|
63
|
+
const filename = item.title || '';
|
|
64
|
+
// Check if filename ends with any allowed extension
|
|
65
|
+
const hasAllowedExtension = extensions.some(ext => filename.endsWith(ext));
|
|
66
|
+
if (hasAllowedExtension) {
|
|
67
|
+
const baseUrl = this.httpClient.getBaseURL();
|
|
68
|
+
filtered.push({
|
|
69
|
+
id: item.id,
|
|
70
|
+
name: filename,
|
|
71
|
+
downloadUrl: this.buildDownloadUrl(baseUrl, item),
|
|
72
|
+
containerId: item.container?.id || '',
|
|
73
|
+
pageUrl: item._links?.webui ? `${baseUrl}${item._links.webui}` : ''
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return filtered;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build full download URL from API response
|
|
81
|
+
*/
|
|
82
|
+
buildDownloadUrl(baseUrl, item) {
|
|
83
|
+
// Try to use download link from API
|
|
84
|
+
if (item._links?.download) {
|
|
85
|
+
const downloadPath = item._links.download;
|
|
86
|
+
if (downloadPath.startsWith('http')) {
|
|
87
|
+
return downloadPath;
|
|
88
|
+
}
|
|
89
|
+
return `${baseUrl}${downloadPath}`;
|
|
90
|
+
}
|
|
91
|
+
// Fallback: construct URL from attachment info
|
|
92
|
+
if (item.container?.id && item.id) {
|
|
93
|
+
return `${baseUrl}/download/attachments/${item.container.id}/${encodeURIComponent(item.title)}`;
|
|
94
|
+
}
|
|
95
|
+
throw new errors_1.WikiError(errors_1.ErrorType.INVALID_INPUT, `Cannot build download URL for attachment: ${item.title}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.WikiSearcher = WikiSearcher;
|
package/package.json
CHANGED