@poolzin/pool-bot 2026.3.7 → 2026.3.10
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 +40 -0
- package/README.md +147 -69
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +251 -0
- package/dist/agents/skills/security.js +211 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/cron-cli/register.cron-dashboard.js +339 -0
- package/dist/cli/cron-cli/register.js +2 -0
- package/dist/cli/errors.js +187 -0
- package/dist/cli/lazy-commands.example.js +113 -0
- package/dist/cli/lazy-commands.js +329 -0
- package/dist/cli/program/command-registry.js +26 -0
- package/dist/cli/program/register.maintenance.js +21 -0
- package/dist/cli/program/register.skills.js +4 -0
- package/dist/cli/program/register.subclis.js +9 -0
- package/dist/cli/swarm-cli/register.js +8 -0
- package/dist/cli/swarm-cli/register.swarm-status.js +488 -0
- package/dist/cli/telemetry-cli/register.js +10 -0
- package/dist/cli/telemetry-cli/register.telemetry-alerts.js +176 -0
- package/dist/cli/telemetry-cli/register.telemetry-metrics.js +323 -0
- package/dist/cli/telemetry-cli/register.telemetry-status.js +179 -0
- package/dist/commands/doctor-checks.js +498 -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 +179 -0
- package/dist/context-engine/registry.js +86 -0
- package/dist/context-engine/summarizing.js +290 -0
- package/dist/context-engine/types.js +7 -0
- package/dist/cron/service/timer.js +18 -0
- package/dist/gateway/protocol/index.js +5 -2
- package/dist/gateway/protocol/schema/error-codes.js +1 -0
- package/dist/gateway/protocol/schema/swarm.js +80 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-close.js +4 -0
- package/dist/gateway/server-constants.js +1 -0
- package/dist/gateway/server-cron.js +29 -0
- package/dist/gateway/server-maintenance.js +35 -2
- package/dist/gateway/server-methods/swarm.js +58 -0
- package/dist/gateway/server-methods/telemetry.js +71 -0
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +9 -2
- package/dist/gateway/server.impl.js +33 -16
- package/dist/infra/abort-pattern.js +106 -0
- package/dist/infra/retry.js +96 -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 +333 -0
- package/dist/skills/index.js +164 -0
- package/dist/skills/loader.js +282 -0
- package/dist/skills/parser.js +446 -0
- package/dist/skills/registry.js +394 -0
- package/dist/skills/security.js +312 -0
- package/dist/skills/types.js +21 -0
- package/dist/swarm/service.js +247 -0
- package/dist/telemetry/alert-engine.js +258 -0
- package/dist/telemetry/cron-instrumentation.js +49 -0
- package/dist/telemetry/gateway-instrumentation.js +80 -0
- package/dist/telemetry/instrumentation.js +66 -0
- package/dist/telemetry/service.js +345 -0
- package/dist/test-utils/index.js +219 -0
- package/dist/tui/components/assistant-message.js +6 -2
- package/dist/tui/components/hyperlink-markdown.js +32 -0
- package/dist/tui/components/searchable-select-list.js +12 -1
- package/dist/tui/components/user-message.js +6 -2
- package/dist/tui/index.js +611 -0
- package/dist/tui/theme/theme-detection.js +226 -0
- package/dist/tui/tui-command-handlers.js +20 -0
- package/dist/tui/tui-formatters.js +4 -3
- package/dist/tui/utils/ctrl-c-handler.js +67 -0
- package/dist/tui/utils/osc8-hyperlinks.js +208 -0
- package/dist/tui/utils/safe-stop.js +180 -0
- package/dist/tui/utils/session-key-utils.js +81 -0
- package/dist/tui/utils/text-sanitization.js +284 -0
- package/dist/utils/lru-cache.js +116 -0
- package/dist/utils/performance.js +199 -0
- package/dist/utils/retry.js +240 -0
- package/docs/INTEGRATION_PLAN.md +475 -0
- package/docs/INTEGRATION_SUMMARY.md +215 -0
- package/docs/MELHORIAS_IMPLEMENTADAS.md +228 -0
- package/docs/MELHORIAS_PROFISSIONAIS.md +282 -0
- package/docs/PLANO_ACAO_TUI.md +357 -0
- package/docs/PROGRESSO_TUI.md +66 -0
- package/docs/RELATORIO_FINAL.md +217 -0
- package/docs/diagnostico-shell-completion.md +265 -0
- package/docs/features/advanced-memory.md +585 -0
- package/docs/features/discord-components-v2.md +277 -0
- package/docs/features/swarm.md +100 -0
- package/docs/features/telemetry.md +284 -0
- package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
- package/docs/integrations/INTEGRATION_PLAN.md +744 -0
- package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
- package/docs/integrations/XYOPS_PLAN.md +978 -0
- package/docs/models/provider-infrastructure.md +400 -0
- package/docs/security/exec-approvals.md +294 -0
- package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
- package/docs/skills/SKILL.md +524 -0
- package/docs/skills.md +405 -0
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/hexstrike-bridge/README.md +119 -0
- package/extensions/hexstrike-bridge/index.test.ts +247 -0
- package/extensions/hexstrike-bridge/index.ts +487 -0
- package/extensions/hexstrike-bridge/package.json +17 -0
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mcp-server/index.ts +14 -0
- package/extensions/mcp-server/package.json +11 -0
- package/extensions/mcp-server/src/service.ts +540 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +8 -1
- package/skills/example-skill/SKILL.md +195 -0
|
@@ -0,0 +1,394 @@
|
|
|
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 && skill.verification === "failed") {
|
|
90
|
+
skill.enabled = false;
|
|
91
|
+
skill.status = "disabled";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
this.skills.set(skill.metadata.id, skill);
|
|
95
|
+
this.emitEvent("skill:loaded", skill.metadata.id);
|
|
96
|
+
loaded++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
errors.push(new SkillError("LOAD_ERROR", `Failed to load skill from ${skillPath}: ${error}`, entry.name, error));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
errors.push(error);
|
|
106
|
+
}
|
|
107
|
+
return { loaded, errors };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Watch for skill changes (placeholder for future implementation)
|
|
111
|
+
*/
|
|
112
|
+
async watch() {
|
|
113
|
+
// TODO: Implement file watching for hot-reload
|
|
114
|
+
// This would use fs.watch or chokidar
|
|
115
|
+
throw new SkillError("CONFIG_ERROR", "Watch mode not yet implemented");
|
|
116
|
+
}
|
|
117
|
+
// ========================================================================
|
|
118
|
+
// Skill CRUD
|
|
119
|
+
// ========================================================================
|
|
120
|
+
/**
|
|
121
|
+
* Get a skill by ID
|
|
122
|
+
*/
|
|
123
|
+
get(id) {
|
|
124
|
+
return this.skills.get(id);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get all skills
|
|
128
|
+
*/
|
|
129
|
+
getAll() {
|
|
130
|
+
return Array.from(this.skills.values());
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get skill reference (lightweight)
|
|
134
|
+
*/
|
|
135
|
+
getRef(id) {
|
|
136
|
+
const skill = this.skills.get(id);
|
|
137
|
+
if (!skill)
|
|
138
|
+
return undefined;
|
|
139
|
+
return {
|
|
140
|
+
id: skill.metadata.id,
|
|
141
|
+
name: skill.metadata.name,
|
|
142
|
+
description: skill.metadata.description,
|
|
143
|
+
category: skill.metadata.category,
|
|
144
|
+
tags: skill.metadata.tags,
|
|
145
|
+
version: skill.metadata.version,
|
|
146
|
+
status: skill.status,
|
|
147
|
+
verification: skill.verification,
|
|
148
|
+
enabled: skill.enabled,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get all skill references
|
|
153
|
+
*/
|
|
154
|
+
getAllRefs() {
|
|
155
|
+
return this.getAll().map((skill) => ({
|
|
156
|
+
id: skill.metadata.id,
|
|
157
|
+
name: skill.metadata.name,
|
|
158
|
+
description: skill.metadata.description,
|
|
159
|
+
category: skill.metadata.category,
|
|
160
|
+
tags: skill.metadata.tags,
|
|
161
|
+
version: skill.metadata.version,
|
|
162
|
+
status: skill.status,
|
|
163
|
+
verification: skill.verification,
|
|
164
|
+
enabled: skill.enabled,
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Add or update a skill
|
|
169
|
+
*/
|
|
170
|
+
async add(skill) {
|
|
171
|
+
const exists = this.skills.has(skill.metadata.id);
|
|
172
|
+
this.skills.set(skill.metadata.id, skill);
|
|
173
|
+
this.emitEvent(exists ? "skill:updated" : "skill:loaded", skill.metadata.id);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Remove a skill
|
|
177
|
+
*/
|
|
178
|
+
remove(id) {
|
|
179
|
+
const existed = this.skills.delete(id);
|
|
180
|
+
if (existed) {
|
|
181
|
+
this.emitEvent("skill:unloaded", id);
|
|
182
|
+
}
|
|
183
|
+
return existed;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if skill exists
|
|
187
|
+
*/
|
|
188
|
+
has(id) {
|
|
189
|
+
return this.skills.has(id);
|
|
190
|
+
}
|
|
191
|
+
// ========================================================================
|
|
192
|
+
// Skill State Management
|
|
193
|
+
// ========================================================================
|
|
194
|
+
/**
|
|
195
|
+
* Enable a skill
|
|
196
|
+
*/
|
|
197
|
+
enable(id) {
|
|
198
|
+
const skill = this.skills.get(id);
|
|
199
|
+
if (!skill)
|
|
200
|
+
return false;
|
|
201
|
+
if (skill.verification === "failed" && this.config.strictSecurity) {
|
|
202
|
+
throw new SkillError("SECURITY_VIOLATION", `Cannot enable skill ${id}: failed security scan`, id);
|
|
203
|
+
}
|
|
204
|
+
skill.enabled = true;
|
|
205
|
+
skill.status = "installed";
|
|
206
|
+
this.emitEvent("skill:enabled", id);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Disable a skill
|
|
211
|
+
*/
|
|
212
|
+
disable(id) {
|
|
213
|
+
const skill = this.skills.get(id);
|
|
214
|
+
if (!skill)
|
|
215
|
+
return false;
|
|
216
|
+
skill.enabled = false;
|
|
217
|
+
skill.status = "disabled";
|
|
218
|
+
this.emitEvent("skill:disabled", id);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Toggle skill enabled state
|
|
223
|
+
*/
|
|
224
|
+
toggle(id) {
|
|
225
|
+
const skill = this.skills.get(id);
|
|
226
|
+
if (!skill)
|
|
227
|
+
return false;
|
|
228
|
+
return skill.enabled ? this.disable(id) : this.enable(id);
|
|
229
|
+
}
|
|
230
|
+
// ========================================================================
|
|
231
|
+
// Search
|
|
232
|
+
// ========================================================================
|
|
233
|
+
/**
|
|
234
|
+
* Search skills with filters
|
|
235
|
+
*/
|
|
236
|
+
search(filters = {}, page = 1, pageSize = 20) {
|
|
237
|
+
let results = this.getAll();
|
|
238
|
+
// Apply filters
|
|
239
|
+
if (filters.category) {
|
|
240
|
+
results = results.filter((s) => s.metadata.category === filters.category);
|
|
241
|
+
}
|
|
242
|
+
if (filters.tags?.length) {
|
|
243
|
+
results = results.filter((s) => filters.tags.some((tag) => s.metadata.tags.includes(tag)));
|
|
244
|
+
}
|
|
245
|
+
if (filters.status) {
|
|
246
|
+
results = results.filter((s) => s.status === filters.status);
|
|
247
|
+
}
|
|
248
|
+
if (filters.verification) {
|
|
249
|
+
results = results.filter((s) => s.verification === filters.verification);
|
|
250
|
+
}
|
|
251
|
+
if (filters.enabledOnly) {
|
|
252
|
+
results = results.filter((s) => s.enabled);
|
|
253
|
+
}
|
|
254
|
+
if (filters.query) {
|
|
255
|
+
const query = filters.query.toLowerCase();
|
|
256
|
+
results = results.filter((s) => s.metadata.name.toLowerCase().includes(query) ||
|
|
257
|
+
s.metadata.description.toLowerCase().includes(query) ||
|
|
258
|
+
s.metadata.tags.some((t) => t.toLowerCase().includes(query)));
|
|
259
|
+
}
|
|
260
|
+
// Pagination
|
|
261
|
+
const total = results.length;
|
|
262
|
+
const start = (page - 1) * pageSize;
|
|
263
|
+
const paginated = results.slice(start, start + pageSize);
|
|
264
|
+
return {
|
|
265
|
+
skills: paginated.map((s) => ({
|
|
266
|
+
id: s.metadata.id,
|
|
267
|
+
name: s.metadata.name,
|
|
268
|
+
description: s.metadata.description,
|
|
269
|
+
category: s.metadata.category,
|
|
270
|
+
tags: s.metadata.tags,
|
|
271
|
+
version: s.metadata.version,
|
|
272
|
+
status: s.status,
|
|
273
|
+
verification: s.verification,
|
|
274
|
+
enabled: s.enabled,
|
|
275
|
+
})),
|
|
276
|
+
total,
|
|
277
|
+
page,
|
|
278
|
+
pageSize,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Find skills by tag
|
|
283
|
+
*/
|
|
284
|
+
findByTag(tag) {
|
|
285
|
+
return this.getAllRefs().filter((s) => s.tags.includes(tag));
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Find skills by category
|
|
289
|
+
*/
|
|
290
|
+
findByCategory(category) {
|
|
291
|
+
return this.getAllRefs().filter((s) => s.category === category);
|
|
292
|
+
}
|
|
293
|
+
// ========================================================================
|
|
294
|
+
// Statistics
|
|
295
|
+
// ========================================================================
|
|
296
|
+
/**
|
|
297
|
+
* Get registry statistics
|
|
298
|
+
*/
|
|
299
|
+
getStats() {
|
|
300
|
+
const skills = this.getAll();
|
|
301
|
+
const byCategory = {};
|
|
302
|
+
const byVerification = {};
|
|
303
|
+
for (const skill of skills) {
|
|
304
|
+
// Count by category
|
|
305
|
+
byCategory[skill.metadata.category] = (byCategory[skill.metadata.category] || 0) + 1;
|
|
306
|
+
// Count by verification
|
|
307
|
+
byVerification[skill.verification] = (byVerification[skill.verification] || 0) + 1;
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
total: skills.length,
|
|
311
|
+
enabled: skills.filter((s) => s.enabled).length,
|
|
312
|
+
disabled: skills.filter((s) => !s.enabled).length,
|
|
313
|
+
byCategory,
|
|
314
|
+
byVerification,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
// ========================================================================
|
|
318
|
+
// Import/Export
|
|
319
|
+
// ========================================================================
|
|
320
|
+
/**
|
|
321
|
+
* Export all skills to JSON
|
|
322
|
+
*/
|
|
323
|
+
export() {
|
|
324
|
+
const data = this.getAll().map((skill) => ({
|
|
325
|
+
metadata: skill.metadata,
|
|
326
|
+
status: skill.status,
|
|
327
|
+
verification: skill.verification,
|
|
328
|
+
enabled: skill.enabled,
|
|
329
|
+
userConfig: skill.userConfig,
|
|
330
|
+
}));
|
|
331
|
+
return JSON.stringify(data, null, 2);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Import skills from JSON
|
|
335
|
+
*/
|
|
336
|
+
async import(json) {
|
|
337
|
+
const data = JSON.parse(json);
|
|
338
|
+
for (const partial of data) {
|
|
339
|
+
if (!partial.metadata?.id)
|
|
340
|
+
continue;
|
|
341
|
+
// Try to reload from source
|
|
342
|
+
const existing = this.skills.get(partial.metadata.id);
|
|
343
|
+
if (existing) {
|
|
344
|
+
// Update state only
|
|
345
|
+
if (partial.enabled !== undefined) {
|
|
346
|
+
existing.enabled = partial.enabled;
|
|
347
|
+
}
|
|
348
|
+
if (partial.userConfig) {
|
|
349
|
+
existing.userConfig = partial.userConfig;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// ========================================================================
|
|
355
|
+
// Cleanup
|
|
356
|
+
// ========================================================================
|
|
357
|
+
/**
|
|
358
|
+
* Clear all skills
|
|
359
|
+
*/
|
|
360
|
+
clear() {
|
|
361
|
+
for (const id of this.skills.keys()) {
|
|
362
|
+
this.emitEvent("skill:unloaded", id);
|
|
363
|
+
}
|
|
364
|
+
this.skills.clear();
|
|
365
|
+
this.scanCache.clear();
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Dispose of registry resources
|
|
369
|
+
*/
|
|
370
|
+
dispose() {
|
|
371
|
+
this.clear();
|
|
372
|
+
this.removeAllListeners();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// ============================================================================
|
|
376
|
+
// Singleton Instance
|
|
377
|
+
// ============================================================================
|
|
378
|
+
let globalRegistry = null;
|
|
379
|
+
/**
|
|
380
|
+
* Get or create global registry instance
|
|
381
|
+
*/
|
|
382
|
+
export function getRegistry(config) {
|
|
383
|
+
if (!globalRegistry) {
|
|
384
|
+
globalRegistry = new SkillsRegistry(config);
|
|
385
|
+
}
|
|
386
|
+
return globalRegistry;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Reset global registry (useful for testing)
|
|
390
|
+
*/
|
|
391
|
+
export function resetRegistry() {
|
|
392
|
+
globalRegistry?.dispose();
|
|
393
|
+
globalRegistry = null;
|
|
394
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill security scanner
|
|
3
|
+
* Detects potential security issues in skill files
|
|
4
|
+
*
|
|
5
|
+
* @module skills/security
|
|
6
|
+
*/
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Constants
|
|
9
|
+
// ============================================================================
|
|
10
|
+
const SCANNER_VERSION = "1.0.0";
|
|
11
|
+
// Patterns that indicate potential security issues
|
|
12
|
+
const PATTERNS = [
|
|
13
|
+
// Prompt injection attempts
|
|
14
|
+
{
|
|
15
|
+
type: "prompt_injection",
|
|
16
|
+
severity: "critical",
|
|
17
|
+
pattern: /ignore\s+(?:previous|above|prior)|disregard\s+(?:instructions?|prompt)|system\s*:\s*you\s+are|new\s+instructions?\s*:/i,
|
|
18
|
+
description: "Potential prompt injection attempt detected",
|
|
19
|
+
remediation: "Review skill content for malicious instruction overrides",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
type: "prompt_injection",
|
|
23
|
+
severity: "high",
|
|
24
|
+
pattern: /\[\s*system\s*\]|\(\s*system\s*\)|\{\s*system\s*\}|\bDAN\b|do\s+anything\s+now/i,
|
|
25
|
+
description: "Suspicious system role reference",
|
|
26
|
+
remediation: "Verify skill doesn't attempt to override system behavior",
|
|
27
|
+
},
|
|
28
|
+
// Command injection
|
|
29
|
+
{
|
|
30
|
+
type: "command_injection",
|
|
31
|
+
severity: "critical",
|
|
32
|
+
pattern: /(?:bash|sh|zsh|cmd|powershell)\s+-c\s+["']|exec\s*\(|eval\s*\(|system\s*\(/i,
|
|
33
|
+
description: "Potential command injection pattern",
|
|
34
|
+
remediation: "Avoid executing arbitrary shell commands from skill content",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: "command_injection",
|
|
38
|
+
severity: "high",
|
|
39
|
+
pattern: /`[^`]*(?:rm|del|format|mkfs|dd|wget|curl|fetch)[^`]*`|\$\([^)]*(?:rm|del|wget|curl)[^)]*\)/i,
|
|
40
|
+
description: "Dangerous command in template literal",
|
|
41
|
+
remediation: "Review shell command usage for safety",
|
|
42
|
+
},
|
|
43
|
+
// Path traversal
|
|
44
|
+
{
|
|
45
|
+
type: "path_traversal",
|
|
46
|
+
severity: "high",
|
|
47
|
+
pattern: /\.\.[/\\]|\.\.%2f|\.\.%5c|%2e%2e[/\\]/i,
|
|
48
|
+
description: "Path traversal attempt detected",
|
|
49
|
+
remediation: "Validate and sanitize all file paths",
|
|
50
|
+
},
|
|
51
|
+
// Suspicious patterns
|
|
52
|
+
{
|
|
53
|
+
type: "suspicious_pattern",
|
|
54
|
+
severity: "medium",
|
|
55
|
+
pattern: /(?:password|secret|token|key|credential)\s*=\s*["'][^"']{8,}["']/i,
|
|
56
|
+
description: "Hardcoded credential-like pattern",
|
|
57
|
+
remediation: "Use environment variables or secure secret storage",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: "suspicious_pattern",
|
|
61
|
+
severity: "medium",
|
|
62
|
+
pattern: /base64\s*\(\s*["'][^"']{20,}["']\s*\)|atob\s*\(|btoa\s*\(/i,
|
|
63
|
+
description: "Suspicious encoding/decoding pattern",
|
|
64
|
+
remediation: "Verify encoding is not used to obfuscate malicious content",
|
|
65
|
+
},
|
|
66
|
+
// External dependencies
|
|
67
|
+
{
|
|
68
|
+
type: "external_dependency",
|
|
69
|
+
severity: "low",
|
|
70
|
+
pattern: /(?:npm|pip|gem|cargo|go\s+get)\s+install/i,
|
|
71
|
+
description: "External package installation mentioned",
|
|
72
|
+
remediation: "Verify all external dependencies are trustworthy",
|
|
73
|
+
},
|
|
74
|
+
// Permission requests
|
|
75
|
+
{
|
|
76
|
+
type: "permission_request",
|
|
77
|
+
severity: "medium",
|
|
78
|
+
pattern: /(?:sudo|administrator|root|elevated|privileged)\s*(?:access|permission|required|needed)/i,
|
|
79
|
+
description: "Elevated permissions mentioned",
|
|
80
|
+
remediation: "Ensure elevated permissions are truly necessary",
|
|
81
|
+
},
|
|
82
|
+
// Network access
|
|
83
|
+
{
|
|
84
|
+
type: "network_access",
|
|
85
|
+
severity: "low",
|
|
86
|
+
pattern: /(?:http|https|ftp|ssh|telnet):\/\/|(?:fetch|axios|request|curl)\s*\(/i,
|
|
87
|
+
description: "Network access pattern detected",
|
|
88
|
+
remediation: "Verify all network requests are legitimate",
|
|
89
|
+
},
|
|
90
|
+
// File system access
|
|
91
|
+
{
|
|
92
|
+
type: "file_system_access",
|
|
93
|
+
severity: "low",
|
|
94
|
+
pattern: /(?:fs\.|file|readFile|writeFile|appendFile|unlink|mkdir|rmdir)\s*\(/i,
|
|
95
|
+
description: "File system access pattern detected",
|
|
96
|
+
remediation: "Validate file operations are scoped appropriately",
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
// Content limits
|
|
100
|
+
const MAX_CONTENT_LENGTH = 1024 * 1024; // 1MB
|
|
101
|
+
const MAX_LINE_LENGTH = 1000;
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Scanner Implementation
|
|
104
|
+
// ============================================================================
|
|
105
|
+
/**
|
|
106
|
+
* Scan skill content for security issues
|
|
107
|
+
*/
|
|
108
|
+
export async function scanSkill(skill) {
|
|
109
|
+
const startTime = Date.now();
|
|
110
|
+
const findings = [];
|
|
111
|
+
// Combine all content for scanning
|
|
112
|
+
const contentToScan = [
|
|
113
|
+
skill.metadata.name,
|
|
114
|
+
skill.metadata.description,
|
|
115
|
+
skill.content.usage,
|
|
116
|
+
skill.content.quickstart,
|
|
117
|
+
skill.content.configuration,
|
|
118
|
+
skill.content.environment,
|
|
119
|
+
skill.content.examples,
|
|
120
|
+
skill.content.api,
|
|
121
|
+
skill.content.troubleshooting,
|
|
122
|
+
]
|
|
123
|
+
.filter(Boolean)
|
|
124
|
+
.join("\n\n");
|
|
125
|
+
// Check content size
|
|
126
|
+
if (contentToScan.length > MAX_CONTENT_LENGTH) {
|
|
127
|
+
findings.push({
|
|
128
|
+
type: "suspicious_pattern",
|
|
129
|
+
severity: "medium",
|
|
130
|
+
description: `Skill content exceeds maximum size (${contentToScan.length} > ${MAX_CONTENT_LENGTH})`,
|
|
131
|
+
location: "content",
|
|
132
|
+
remediation: "Consider splitting large skills into smaller modules",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Scan line by line for better location reporting
|
|
136
|
+
const lines = contentToScan.split("\n");
|
|
137
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
138
|
+
const line = lines[lineNum];
|
|
139
|
+
// Check line length
|
|
140
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
141
|
+
findings.push({
|
|
142
|
+
type: "suspicious_pattern",
|
|
143
|
+
severity: "low",
|
|
144
|
+
description: `Line ${lineNum + 1} exceeds maximum length`,
|
|
145
|
+
location: `line:${lineNum + 1}`,
|
|
146
|
+
remediation: "Break long lines for readability and safety",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
// Check against patterns
|
|
150
|
+
for (const { type, severity, pattern, description, remediation } of PATTERNS) {
|
|
151
|
+
if (pattern.test(line)) {
|
|
152
|
+
findings.push({
|
|
153
|
+
type,
|
|
154
|
+
severity,
|
|
155
|
+
description,
|
|
156
|
+
location: `line:${lineNum + 1}`,
|
|
157
|
+
remediation,
|
|
158
|
+
evidence: line.trim().slice(0, 100), // First 100 chars
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Check linked files
|
|
164
|
+
for (const linkedFile of skill.linkedFiles) {
|
|
165
|
+
// Flag executable files
|
|
166
|
+
if (/\.(exe|bat|cmd|sh|bin|app)$/i.test(linkedFile.path)) {
|
|
167
|
+
findings.push({
|
|
168
|
+
type: "suspicious_pattern",
|
|
169
|
+
severity: "high",
|
|
170
|
+
description: `Linked executable file detected: ${linkedFile.path}`,
|
|
171
|
+
location: `linked:${linkedFile.path}`,
|
|
172
|
+
remediation: "Review executable content before execution",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// Flag scripts
|
|
176
|
+
if (/\.(js|ts|py|rb|pl|php)$/i.test(linkedFile.path)) {
|
|
177
|
+
findings.push({
|
|
178
|
+
type: "external_dependency",
|
|
179
|
+
severity: "low",
|
|
180
|
+
description: `Linked script file: ${linkedFile.path}`,
|
|
181
|
+
location: `linked:${linkedFile.path}`,
|
|
182
|
+
remediation: "Review script content for safety",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Determine overall result
|
|
187
|
+
const hasCritical = findings.some((f) => f.severity === "critical");
|
|
188
|
+
const hasHigh = findings.some((f) => f.severity === "high");
|
|
189
|
+
const hasWarnings = findings.some((f) => f.severity === "medium" || f.severity === "low");
|
|
190
|
+
let result;
|
|
191
|
+
if (hasCritical) {
|
|
192
|
+
result = "failed";
|
|
193
|
+
}
|
|
194
|
+
else if (hasHigh) {
|
|
195
|
+
result = "failed";
|
|
196
|
+
}
|
|
197
|
+
else if (hasWarnings) {
|
|
198
|
+
result = "warning";
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
result = "verified";
|
|
202
|
+
}
|
|
203
|
+
const durationMs = Date.now() - startTime;
|
|
204
|
+
return {
|
|
205
|
+
scannedAt: new Date(),
|
|
206
|
+
result,
|
|
207
|
+
findings,
|
|
208
|
+
durationMs,
|
|
209
|
+
scannerVersion: SCANNER_VERSION,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Quick scan for critical issues only
|
|
214
|
+
*/
|
|
215
|
+
export async function quickSecurityCheck(skill) {
|
|
216
|
+
const criticalPatterns = PATTERNS.filter((p) => p.severity === "critical" || p.severity === "high");
|
|
217
|
+
const contentToScan = [
|
|
218
|
+
skill.metadata.name,
|
|
219
|
+
skill.metadata.description,
|
|
220
|
+
skill.content.usage,
|
|
221
|
+
skill.content.quickstart,
|
|
222
|
+
]
|
|
223
|
+
.filter(Boolean)
|
|
224
|
+
.join("\n");
|
|
225
|
+
for (const { pattern } of criticalPatterns) {
|
|
226
|
+
if (pattern.test(contentToScan)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get security summary for display
|
|
234
|
+
*/
|
|
235
|
+
export function getSecuritySummary(report) {
|
|
236
|
+
const counts = {
|
|
237
|
+
critical: 0,
|
|
238
|
+
high: 0,
|
|
239
|
+
medium: 0,
|
|
240
|
+
low: 0,
|
|
241
|
+
info: 0,
|
|
242
|
+
};
|
|
243
|
+
for (const finding of report.findings) {
|
|
244
|
+
counts[finding.severity]++;
|
|
245
|
+
}
|
|
246
|
+
let status;
|
|
247
|
+
let color;
|
|
248
|
+
let summary;
|
|
249
|
+
switch (report.result) {
|
|
250
|
+
case "verified":
|
|
251
|
+
status = "✓ Verified";
|
|
252
|
+
color = "green";
|
|
253
|
+
summary = "No security issues found";
|
|
254
|
+
break;
|
|
255
|
+
case "warning":
|
|
256
|
+
status = "⚠ Warning";
|
|
257
|
+
color = "yellow";
|
|
258
|
+
summary = `${counts.medium + counts.low} warning(s) found`;
|
|
259
|
+
break;
|
|
260
|
+
case "failed":
|
|
261
|
+
status = "✗ Failed";
|
|
262
|
+
color = "red";
|
|
263
|
+
summary = `${counts.critical + counts.high} critical issue(s) found`;
|
|
264
|
+
break;
|
|
265
|
+
default:
|
|
266
|
+
status = "? Unverified";
|
|
267
|
+
color = "gray";
|
|
268
|
+
summary = "Not yet scanned";
|
|
269
|
+
}
|
|
270
|
+
return { status, color, summary, counts };
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Format security findings for display
|
|
274
|
+
*/
|
|
275
|
+
export function formatFindings(findings) {
|
|
276
|
+
if (findings.length === 0) {
|
|
277
|
+
return ["No security issues found."];
|
|
278
|
+
}
|
|
279
|
+
const lines = [];
|
|
280
|
+
// Group by severity
|
|
281
|
+
const bySeverity = {
|
|
282
|
+
critical: [],
|
|
283
|
+
high: [],
|
|
284
|
+
medium: [],
|
|
285
|
+
low: [],
|
|
286
|
+
info: [],
|
|
287
|
+
};
|
|
288
|
+
for (const finding of findings) {
|
|
289
|
+
bySeverity[finding.severity].push(finding);
|
|
290
|
+
}
|
|
291
|
+
for (const severity of ["critical", "high", "medium", "low", "info"]) {
|
|
292
|
+
const group = bySeverity[severity];
|
|
293
|
+
if (group.length === 0)
|
|
294
|
+
continue;
|
|
295
|
+
lines.push(`\n${severity.toUpperCase()} (${group.length}):`);
|
|
296
|
+
for (const finding of group) {
|
|
297
|
+
lines.push(` [${finding.type}] ${finding.description}`);
|
|
298
|
+
lines.push(` Location: ${finding.location}`);
|
|
299
|
+
if (finding.evidence) {
|
|
300
|
+
lines.push(` Evidence: ${finding.evidence}`);
|
|
301
|
+
}
|
|
302
|
+
if (finding.remediation) {
|
|
303
|
+
lines.push(` Fix: ${finding.remediation}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return lines;
|
|
308
|
+
}
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// Export patterns for testing
|
|
311
|
+
// ============================================================================
|
|
312
|
+
export { PATTERNS };
|