@poolzin/pool-bot 2026.3.7 → 2026.3.9
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/CHANGELOG.md +16 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +302 -0
- package/dist/agents/skills/security.js +217 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/lazy-commands.example.js +113 -0
- package/dist/cli/lazy-commands.js +329 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.skills.js +4 -0
- package/dist/config/config.js +1 -0
- package/dist/config/secrets-integration.js +88 -0
- package/dist/context-engine/index.js +33 -0
- package/dist/context-engine/legacy.js +181 -0
- package/dist/context-engine/registry.js +86 -0
- package/dist/context-engine/summarizing.js +293 -0
- package/dist/context-engine/types.js +7 -0
- package/dist/infra/abort-pattern.js +106 -0
- package/dist/infra/retry.js +94 -0
- package/dist/secrets/index.js +28 -0
- package/dist/secrets/resolver.js +185 -0
- package/dist/secrets/runtime.js +142 -0
- package/dist/secrets/types.js +11 -0
- package/dist/security/dangerous-tools.js +80 -0
- package/dist/security/types.js +12 -0
- package/dist/skills/commands.js +351 -0
- package/dist/skills/index.js +167 -0
- package/dist/skills/loader.js +282 -0
- package/dist/skills/parser.js +461 -0
- package/dist/skills/registry.js +397 -0
- package/dist/skills/security.js +318 -0
- package/dist/skills/types.js +21 -0
- package/dist/test-utils/index.js +219 -0
- package/dist/tui/index.js +595 -0
- package/docs/INTEGRATION_PLAN.md +475 -0
- package/docs/INTEGRATION_SUMMARY.md +215 -0
- package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
- package/docs/integrations/INTEGRATION_PLAN.md +424 -0
- package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
- package/docs/integrations/XYOPS_PLAN.md +978 -0
- package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
- package/docs/skills/SKILL.md +524 -0
- package/docs/skills.md +405 -0
- package/package.json +1 -1
- package/skills/example-skill/SKILL.md +195 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills registry
|
|
3
|
+
* Manages skill discovery, storage, and lifecycle
|
|
4
|
+
*
|
|
5
|
+
* @module skills/registry
|
|
6
|
+
*/
|
|
7
|
+
import { readdir } from "node:fs/promises";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { EventEmitter } from "node:events";
|
|
10
|
+
import { SkillError, } from "./types.js";
|
|
11
|
+
import { parseSkill, isSkillFile } from "./parser.js";
|
|
12
|
+
import { scanSkill } from "./security.js";
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Default Configuration
|
|
15
|
+
// ============================================================================
|
|
16
|
+
const DEFAULT_CONFIG = {
|
|
17
|
+
localPaths: [],
|
|
18
|
+
autoScan: true,
|
|
19
|
+
strictSecurity: false,
|
|
20
|
+
cacheTtlMs: 5 * 60 * 1000, // 5 minutes
|
|
21
|
+
};
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Skills Registry
|
|
24
|
+
// ============================================================================
|
|
25
|
+
export class SkillsRegistry extends EventEmitter {
|
|
26
|
+
skills = new Map();
|
|
27
|
+
config;
|
|
28
|
+
scanCache = new Map();
|
|
29
|
+
constructor(config = {}) {
|
|
30
|
+
super();
|
|
31
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
32
|
+
}
|
|
33
|
+
// ========================================================================
|
|
34
|
+
// Event Handling
|
|
35
|
+
// ========================================================================
|
|
36
|
+
emitEvent(type, skillId, data) {
|
|
37
|
+
const event = {
|
|
38
|
+
type,
|
|
39
|
+
skillId,
|
|
40
|
+
timestamp: new Date(),
|
|
41
|
+
data,
|
|
42
|
+
};
|
|
43
|
+
this.emit(type, event);
|
|
44
|
+
}
|
|
45
|
+
onSkillEvent(type, handler) {
|
|
46
|
+
this.on(type, handler);
|
|
47
|
+
}
|
|
48
|
+
// ========================================================================
|
|
49
|
+
// Skill Discovery
|
|
50
|
+
// ========================================================================
|
|
51
|
+
/**
|
|
52
|
+
* Scan all configured paths for skills
|
|
53
|
+
*/
|
|
54
|
+
async scan() {
|
|
55
|
+
const errors = [];
|
|
56
|
+
let loaded = 0;
|
|
57
|
+
for (const path of this.config.localPaths) {
|
|
58
|
+
try {
|
|
59
|
+
const result = await this.scanPath(path);
|
|
60
|
+
loaded += result.loaded;
|
|
61
|
+
errors.push(...result.errors);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
errors.push(error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { loaded, errors };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Scan a single path for skills
|
|
71
|
+
*/
|
|
72
|
+
async scanPath(basePath) {
|
|
73
|
+
const errors = [];
|
|
74
|
+
let loaded = 0;
|
|
75
|
+
try {
|
|
76
|
+
const entries = await readdir(basePath, { withFileTypes: true });
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
if (!entry.isDirectory())
|
|
79
|
+
continue;
|
|
80
|
+
const skillPath = join(basePath.toString(), entry.name, "SKILL.md");
|
|
81
|
+
try {
|
|
82
|
+
if (await isSkillFile(skillPath)) {
|
|
83
|
+
const skill = await parseSkill(skillPath);
|
|
84
|
+
// Run security scan if auto-scan enabled
|
|
85
|
+
if (this.config.autoScan) {
|
|
86
|
+
skill.securityReport = await scanSkill(skill);
|
|
87
|
+
skill.verification = skill.securityReport.result;
|
|
88
|
+
// Auto-disable if strict security and failed
|
|
89
|
+
if (this.config.strictSecurity &&
|
|
90
|
+
skill.verification === "failed") {
|
|
91
|
+
skill.enabled = false;
|
|
92
|
+
skill.status = "disabled";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.skills.set(skill.metadata.id, skill);
|
|
96
|
+
this.emitEvent("skill:loaded", skill.metadata.id);
|
|
97
|
+
loaded++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
errors.push(new SkillError("LOAD_ERROR", `Failed to load skill from ${skillPath}: ${error}`, entry.name, error));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
errors.push(error);
|
|
107
|
+
}
|
|
108
|
+
return { loaded, errors };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Watch for skill changes (placeholder for future implementation)
|
|
112
|
+
*/
|
|
113
|
+
async watch() {
|
|
114
|
+
// TODO: Implement file watching for hot-reload
|
|
115
|
+
// This would use fs.watch or chokidar
|
|
116
|
+
throw new SkillError("CONFIG_ERROR", "Watch mode not yet implemented");
|
|
117
|
+
}
|
|
118
|
+
// ========================================================================
|
|
119
|
+
// Skill CRUD
|
|
120
|
+
// ========================================================================
|
|
121
|
+
/**
|
|
122
|
+
* Get a skill by ID
|
|
123
|
+
*/
|
|
124
|
+
get(id) {
|
|
125
|
+
return this.skills.get(id);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get all skills
|
|
129
|
+
*/
|
|
130
|
+
getAll() {
|
|
131
|
+
return Array.from(this.skills.values());
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get skill reference (lightweight)
|
|
135
|
+
*/
|
|
136
|
+
getRef(id) {
|
|
137
|
+
const skill = this.skills.get(id);
|
|
138
|
+
if (!skill)
|
|
139
|
+
return undefined;
|
|
140
|
+
return {
|
|
141
|
+
id: skill.metadata.id,
|
|
142
|
+
name: skill.metadata.name,
|
|
143
|
+
description: skill.metadata.description,
|
|
144
|
+
category: skill.metadata.category,
|
|
145
|
+
tags: skill.metadata.tags,
|
|
146
|
+
version: skill.metadata.version,
|
|
147
|
+
status: skill.status,
|
|
148
|
+
verification: skill.verification,
|
|
149
|
+
enabled: skill.enabled,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get all skill references
|
|
154
|
+
*/
|
|
155
|
+
getAllRefs() {
|
|
156
|
+
return this.getAll().map((skill) => ({
|
|
157
|
+
id: skill.metadata.id,
|
|
158
|
+
name: skill.metadata.name,
|
|
159
|
+
description: skill.metadata.description,
|
|
160
|
+
category: skill.metadata.category,
|
|
161
|
+
tags: skill.metadata.tags,
|
|
162
|
+
version: skill.metadata.version,
|
|
163
|
+
status: skill.status,
|
|
164
|
+
verification: skill.verification,
|
|
165
|
+
enabled: skill.enabled,
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Add or update a skill
|
|
170
|
+
*/
|
|
171
|
+
async add(skill) {
|
|
172
|
+
const exists = this.skills.has(skill.metadata.id);
|
|
173
|
+
this.skills.set(skill.metadata.id, skill);
|
|
174
|
+
this.emitEvent(exists ? "skill:updated" : "skill:loaded", skill.metadata.id);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Remove a skill
|
|
178
|
+
*/
|
|
179
|
+
remove(id) {
|
|
180
|
+
const existed = this.skills.delete(id);
|
|
181
|
+
if (existed) {
|
|
182
|
+
this.emitEvent("skill:unloaded", id);
|
|
183
|
+
}
|
|
184
|
+
return existed;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Check if skill exists
|
|
188
|
+
*/
|
|
189
|
+
has(id) {
|
|
190
|
+
return this.skills.has(id);
|
|
191
|
+
}
|
|
192
|
+
// ========================================================================
|
|
193
|
+
// Skill State Management
|
|
194
|
+
// ========================================================================
|
|
195
|
+
/**
|
|
196
|
+
* Enable a skill
|
|
197
|
+
*/
|
|
198
|
+
enable(id) {
|
|
199
|
+
const skill = this.skills.get(id);
|
|
200
|
+
if (!skill)
|
|
201
|
+
return false;
|
|
202
|
+
if (skill.verification === "failed" && this.config.strictSecurity) {
|
|
203
|
+
throw new SkillError("SECURITY_VIOLATION", `Cannot enable skill ${id}: failed security scan`, id);
|
|
204
|
+
}
|
|
205
|
+
skill.enabled = true;
|
|
206
|
+
skill.status = "installed";
|
|
207
|
+
this.emitEvent("skill:enabled", id);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Disable a skill
|
|
212
|
+
*/
|
|
213
|
+
disable(id) {
|
|
214
|
+
const skill = this.skills.get(id);
|
|
215
|
+
if (!skill)
|
|
216
|
+
return false;
|
|
217
|
+
skill.enabled = false;
|
|
218
|
+
skill.status = "disabled";
|
|
219
|
+
this.emitEvent("skill:disabled", id);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Toggle skill enabled state
|
|
224
|
+
*/
|
|
225
|
+
toggle(id) {
|
|
226
|
+
const skill = this.skills.get(id);
|
|
227
|
+
if (!skill)
|
|
228
|
+
return false;
|
|
229
|
+
return skill.enabled ? this.disable(id) : this.enable(id);
|
|
230
|
+
}
|
|
231
|
+
// ========================================================================
|
|
232
|
+
// Search
|
|
233
|
+
// ========================================================================
|
|
234
|
+
/**
|
|
235
|
+
* Search skills with filters
|
|
236
|
+
*/
|
|
237
|
+
search(filters = {}, page = 1, pageSize = 20) {
|
|
238
|
+
let results = this.getAll();
|
|
239
|
+
// Apply filters
|
|
240
|
+
if (filters.category) {
|
|
241
|
+
results = results.filter((s) => s.metadata.category === filters.category);
|
|
242
|
+
}
|
|
243
|
+
if (filters.tags?.length) {
|
|
244
|
+
results = results.filter((s) => filters.tags.some((tag) => s.metadata.tags.includes(tag)));
|
|
245
|
+
}
|
|
246
|
+
if (filters.status) {
|
|
247
|
+
results = results.filter((s) => s.status === filters.status);
|
|
248
|
+
}
|
|
249
|
+
if (filters.verification) {
|
|
250
|
+
results = results.filter((s) => s.verification === filters.verification);
|
|
251
|
+
}
|
|
252
|
+
if (filters.enabledOnly) {
|
|
253
|
+
results = results.filter((s) => s.enabled);
|
|
254
|
+
}
|
|
255
|
+
if (filters.query) {
|
|
256
|
+
const query = filters.query.toLowerCase();
|
|
257
|
+
results = results.filter((s) => s.metadata.name.toLowerCase().includes(query) ||
|
|
258
|
+
s.metadata.description.toLowerCase().includes(query) ||
|
|
259
|
+
s.metadata.tags.some((t) => t.toLowerCase().includes(query)));
|
|
260
|
+
}
|
|
261
|
+
// Pagination
|
|
262
|
+
const total = results.length;
|
|
263
|
+
const start = (page - 1) * pageSize;
|
|
264
|
+
const paginated = results.slice(start, start + pageSize);
|
|
265
|
+
return {
|
|
266
|
+
skills: paginated.map((s) => ({
|
|
267
|
+
id: s.metadata.id,
|
|
268
|
+
name: s.metadata.name,
|
|
269
|
+
description: s.metadata.description,
|
|
270
|
+
category: s.metadata.category,
|
|
271
|
+
tags: s.metadata.tags,
|
|
272
|
+
version: s.metadata.version,
|
|
273
|
+
status: s.status,
|
|
274
|
+
verification: s.verification,
|
|
275
|
+
enabled: s.enabled,
|
|
276
|
+
})),
|
|
277
|
+
total,
|
|
278
|
+
page,
|
|
279
|
+
pageSize,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Find skills by tag
|
|
284
|
+
*/
|
|
285
|
+
findByTag(tag) {
|
|
286
|
+
return this.getAllRefs().filter((s) => s.tags.includes(tag));
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Find skills by category
|
|
290
|
+
*/
|
|
291
|
+
findByCategory(category) {
|
|
292
|
+
return this.getAllRefs().filter((s) => s.category === category);
|
|
293
|
+
}
|
|
294
|
+
// ========================================================================
|
|
295
|
+
// Statistics
|
|
296
|
+
// ========================================================================
|
|
297
|
+
/**
|
|
298
|
+
* Get registry statistics
|
|
299
|
+
*/
|
|
300
|
+
getStats() {
|
|
301
|
+
const skills = this.getAll();
|
|
302
|
+
const byCategory = {};
|
|
303
|
+
const byVerification = {};
|
|
304
|
+
for (const skill of skills) {
|
|
305
|
+
// Count by category
|
|
306
|
+
byCategory[skill.metadata.category] =
|
|
307
|
+
(byCategory[skill.metadata.category] || 0) + 1;
|
|
308
|
+
// Count by verification
|
|
309
|
+
byVerification[skill.verification] =
|
|
310
|
+
(byVerification[skill.verification] || 0) + 1;
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
total: skills.length,
|
|
314
|
+
enabled: skills.filter((s) => s.enabled).length,
|
|
315
|
+
disabled: skills.filter((s) => !s.enabled).length,
|
|
316
|
+
byCategory,
|
|
317
|
+
byVerification,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// ========================================================================
|
|
321
|
+
// Import/Export
|
|
322
|
+
// ========================================================================
|
|
323
|
+
/**
|
|
324
|
+
* Export all skills to JSON
|
|
325
|
+
*/
|
|
326
|
+
export() {
|
|
327
|
+
const data = this.getAll().map((skill) => ({
|
|
328
|
+
metadata: skill.metadata,
|
|
329
|
+
status: skill.status,
|
|
330
|
+
verification: skill.verification,
|
|
331
|
+
enabled: skill.enabled,
|
|
332
|
+
userConfig: skill.userConfig,
|
|
333
|
+
}));
|
|
334
|
+
return JSON.stringify(data, null, 2);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Import skills from JSON
|
|
338
|
+
*/
|
|
339
|
+
async import(json) {
|
|
340
|
+
const data = JSON.parse(json);
|
|
341
|
+
for (const partial of data) {
|
|
342
|
+
if (!partial.metadata?.id)
|
|
343
|
+
continue;
|
|
344
|
+
// Try to reload from source
|
|
345
|
+
const existing = this.skills.get(partial.metadata.id);
|
|
346
|
+
if (existing) {
|
|
347
|
+
// Update state only
|
|
348
|
+
if (partial.enabled !== undefined) {
|
|
349
|
+
existing.enabled = partial.enabled;
|
|
350
|
+
}
|
|
351
|
+
if (partial.userConfig) {
|
|
352
|
+
existing.userConfig = partial.userConfig;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// ========================================================================
|
|
358
|
+
// Cleanup
|
|
359
|
+
// ========================================================================
|
|
360
|
+
/**
|
|
361
|
+
* Clear all skills
|
|
362
|
+
*/
|
|
363
|
+
clear() {
|
|
364
|
+
for (const id of this.skills.keys()) {
|
|
365
|
+
this.emitEvent("skill:unloaded", id);
|
|
366
|
+
}
|
|
367
|
+
this.skills.clear();
|
|
368
|
+
this.scanCache.clear();
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Dispose of registry resources
|
|
372
|
+
*/
|
|
373
|
+
dispose() {
|
|
374
|
+
this.clear();
|
|
375
|
+
this.removeAllListeners();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// ============================================================================
|
|
379
|
+
// Singleton Instance
|
|
380
|
+
// ============================================================================
|
|
381
|
+
let globalRegistry = null;
|
|
382
|
+
/**
|
|
383
|
+
* Get or create global registry instance
|
|
384
|
+
*/
|
|
385
|
+
export function getRegistry(config) {
|
|
386
|
+
if (!globalRegistry) {
|
|
387
|
+
globalRegistry = new SkillsRegistry(config);
|
|
388
|
+
}
|
|
389
|
+
return globalRegistry;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Reset global registry (useful for testing)
|
|
393
|
+
*/
|
|
394
|
+
export function resetRegistry() {
|
|
395
|
+
globalRegistry?.dispose();
|
|
396
|
+
globalRegistry = null;
|
|
397
|
+
}
|