skyloom 1.4.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/.github/workflows/ci.yml +36 -0
- package/CONVERSION_PLAN.md +191 -0
- package/README.md +67 -0
- package/dist/agents/dew.d.ts +15 -0
- package/dist/agents/dew.d.ts.map +1 -0
- package/dist/agents/dew.js +74 -0
- package/dist/agents/dew.js.map +1 -0
- package/dist/agents/fair.d.ts +15 -0
- package/dist/agents/fair.d.ts.map +1 -0
- package/dist/agents/fair.js +106 -0
- package/dist/agents/fair.js.map +1 -0
- package/dist/agents/fog.d.ts +15 -0
- package/dist/agents/fog.d.ts.map +1 -0
- package/dist/agents/fog.js +52 -0
- package/dist/agents/fog.js.map +1 -0
- package/dist/agents/frost.d.ts +15 -0
- package/dist/agents/frost.d.ts.map +1 -0
- package/dist/agents/frost.js +54 -0
- package/dist/agents/frost.js.map +1 -0
- package/dist/agents/rain.d.ts +15 -0
- package/dist/agents/rain.d.ts.map +1 -0
- package/dist/agents/rain.js +54 -0
- package/dist/agents/rain.js.map +1 -0
- package/dist/agents/snow.d.ts +27 -0
- package/dist/agents/snow.d.ts.map +1 -0
- package/dist/agents/snow.js +226 -0
- package/dist/agents/snow.js.map +1 -0
- package/dist/cli/main.d.ts +7 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +402 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/mode.d.ts +17 -0
- package/dist/cli/mode.d.ts.map +1 -0
- package/dist/cli/mode.js +56 -0
- package/dist/cli/mode.js.map +1 -0
- package/dist/core/agent.d.ts +174 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +1332 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/agent_helpers.d.ts +51 -0
- package/dist/core/agent_helpers.d.ts.map +1 -0
- package/dist/core/agent_helpers.js +477 -0
- package/dist/core/agent_helpers.js.map +1 -0
- package/dist/core/bus.d.ts +99 -0
- package/dist/core/bus.d.ts.map +1 -0
- package/dist/core/bus.js +191 -0
- package/dist/core/bus.js.map +1 -0
- package/dist/core/cache.d.ts +63 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +121 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/checkpoint.d.ts +19 -0
- package/dist/core/checkpoint.d.ts.map +1 -0
- package/dist/core/checkpoint.js +120 -0
- package/dist/core/checkpoint.js.map +1 -0
- package/dist/core/circuit_breaker.d.ts +46 -0
- package/dist/core/circuit_breaker.d.ts.map +1 -0
- package/dist/core/circuit_breaker.js +99 -0
- package/dist/core/circuit_breaker.js.map +1 -0
- package/dist/core/config.d.ts +97 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +281 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/constants.d.ts +78 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/constants.js +84 -0
- package/dist/core/constants.js.map +1 -0
- package/dist/core/factory.d.ts +63 -0
- package/dist/core/factory.d.ts.map +1 -0
- package/dist/core/factory.js +537 -0
- package/dist/core/factory.js.map +1 -0
- package/dist/core/icons.d.ts +28 -0
- package/dist/core/icons.d.ts.map +1 -0
- package/dist/core/icons.js +86 -0
- package/dist/core/icons.js.map +1 -0
- package/dist/core/index.d.ts +29 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +54 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/llm.d.ts +121 -0
- package/dist/core/llm.d.ts.map +1 -0
- package/dist/core/llm.js +532 -0
- package/dist/core/llm.js.map +1 -0
- package/dist/core/logger.d.ts +57 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +122 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/mcp.d.ts +190 -0
- package/dist/core/mcp.d.ts.map +1 -0
- package/dist/core/mcp.js +822 -0
- package/dist/core/mcp.js.map +1 -0
- package/dist/core/mcp_server.d.ts +26 -0
- package/dist/core/mcp_server.d.ts.map +1 -0
- package/dist/core/mcp_server.js +211 -0
- package/dist/core/mcp_server.js.map +1 -0
- package/dist/core/memory.d.ts +190 -0
- package/dist/core/memory.d.ts.map +1 -0
- package/dist/core/memory.js +988 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/middleware.d.ts +114 -0
- package/dist/core/middleware.d.ts.map +1 -0
- package/dist/core/middleware.js +248 -0
- package/dist/core/middleware.js.map +1 -0
- package/dist/core/pipelines.d.ts +87 -0
- package/dist/core/pipelines.d.ts.map +1 -0
- package/dist/core/pipelines.js +301 -0
- package/dist/core/pipelines.js.map +1 -0
- package/dist/core/profile.d.ts +23 -0
- package/dist/core/profile.d.ts.map +1 -0
- package/dist/core/profile.js +289 -0
- package/dist/core/profile.js.map +1 -0
- package/dist/core/router.d.ts +24 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/router.js +111 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/schemas.d.ts +82 -0
- package/dist/core/schemas.d.ts.map +1 -0
- package/dist/core/schemas.js +200 -0
- package/dist/core/schemas.js.map +1 -0
- package/dist/core/semantic.d.ts +92 -0
- package/dist/core/semantic.d.ts.map +1 -0
- package/dist/core/semantic.js +175 -0
- package/dist/core/semantic.js.map +1 -0
- package/dist/core/skill.d.ts +68 -0
- package/dist/core/skill.d.ts.map +1 -0
- package/dist/core/skill.js +350 -0
- package/dist/core/skill.js.map +1 -0
- package/dist/core/tool.d.ts +99 -0
- package/dist/core/tool.d.ts.map +1 -0
- package/dist/core/tool.js +341 -0
- package/dist/core/tool.js.map +1 -0
- package/dist/core/tool_router.d.ts +29 -0
- package/dist/core/tool_router.d.ts.map +1 -0
- package/dist/core/tool_router.js +172 -0
- package/dist/core/tool_router.js.map +1 -0
- package/dist/core/workspace.d.ts +48 -0
- package/dist/core/workspace.d.ts.map +1 -0
- package/dist/core/workspace.js +179 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/plugins/loader.d.ts +17 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +96 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/skills/loader.d.ts +9 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +78 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/tools/builtin.d.ts +10 -0
- package/dist/tools/builtin.d.ts.map +1 -0
- package/dist/tools/builtin.js +414 -0
- package/dist/tools/builtin.js.map +1 -0
- package/dist/tools/computer.d.ts +12 -0
- package/dist/tools/computer.d.ts.map +1 -0
- package/dist/tools/computer.js +326 -0
- package/dist/tools/computer.js.map +1 -0
- package/dist/tools/delegate.d.ts +10 -0
- package/dist/tools/delegate.d.ts.map +1 -0
- package/dist/tools/delegate.js +45 -0
- package/dist/tools/delegate.js.map +1 -0
- package/dist/web/server.d.ts +5 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +647 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/tts.d.ts +33 -0
- package/dist/web/tts.d.ts.map +1 -0
- package/dist/web/tts.js +69 -0
- package/dist/web/tts.js.map +1 -0
- package/package.json +60 -0
- package/scripts/install.js +48 -0
- package/scripts/link.js +10 -0
- package/setup.bat +79 -0
- package/skill-test-ty2fOA/test.md +10 -0
- package/src/agents/dew.ts +70 -0
- package/src/agents/fair.ts +102 -0
- package/src/agents/fog.ts +48 -0
- package/src/agents/frost.ts +50 -0
- package/src/agents/rain.ts +50 -0
- package/src/agents/snow.ts +239 -0
- package/src/cli/main.ts +405 -0
- package/src/cli/mode.ts +58 -0
- package/src/core/agent.ts +1506 -0
- package/src/core/agent_helpers.ts +461 -0
- package/src/core/bus.ts +221 -0
- package/src/core/cache.ts +153 -0
- package/src/core/checkpoint.ts +94 -0
- package/src/core/circuit_breaker.ts +119 -0
- package/src/core/config.ts +341 -0
- package/src/core/constants.ts +95 -0
- package/src/core/factory.ts +627 -0
- package/src/core/icons.ts +53 -0
- package/src/core/index.ts +31 -0
- package/src/core/llm.ts +724 -0
- package/src/core/logger.ts +144 -0
- package/src/core/mcp.ts +953 -0
- package/src/core/mcp_server.ts +176 -0
- package/src/core/memory.ts +1169 -0
- package/src/core/middleware.ts +350 -0
- package/src/core/pipelines.ts +424 -0
- package/src/core/profile.ts +255 -0
- package/src/core/router.ts +124 -0
- package/src/core/schemas.ts +282 -0
- package/src/core/semantic.ts +211 -0
- package/src/core/skill.ts +342 -0
- package/src/core/tool.ts +427 -0
- package/src/core/tool_router.ts +193 -0
- package/src/core/workspace.ts +150 -0
- package/src/plugins/loader.ts +66 -0
- package/src/skills/loader.ts +46 -0
- package/src/sql.js.d.ts +29 -0
- package/src/tools/builtin.ts +382 -0
- package/src/tools/computer.ts +269 -0
- package/src/tools/delegate.ts +49 -0
- package/src/web/server.ts +634 -0
- package/src/web/tts.ts +93 -0
- package/tests/bus.test.ts +121 -0
- package/tests/icons.test.ts +45 -0
- package/tests/router.test.ts +86 -0
- package/tests/schemas.test.ts +51 -0
- package/tests/semantic.test.ts +83 -0
- package/tests/setup.ts +10 -0
- package/tests/skill.test.ts +172 -0
- package/tests/tool.test.ts +108 -0
- package/tests/tool_router.test.ts +71 -0
- package/tsconfig.json +37 -0
- package/vitest.config.ts +17 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill system — Anthropic-compatible composable capability modules.
|
|
3
|
+
*
|
|
4
|
+
* Skills use Markdown + YAML frontmatter format. When activated, a skill
|
|
5
|
+
* injects its system prompt and can register custom handler tools.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { parse as parseYaml } from 'yaml';
|
|
11
|
+
|
|
12
|
+
// Tuning for lazy skill loading
|
|
13
|
+
const SKILL_BODY_LITE_THRESHOLD = 2000;
|
|
14
|
+
const SKILL_BODY_LITE_MAX_CHARS = 1500;
|
|
15
|
+
|
|
16
|
+
// Claude Code -> sky tool-name aliases
|
|
17
|
+
const CLAUDE_TOOL_ALIASES: Record<string, string> = {
|
|
18
|
+
read: 'read_file',
|
|
19
|
+
write: 'write_file',
|
|
20
|
+
edit: 'edit_file',
|
|
21
|
+
multiedit: 'edit_file',
|
|
22
|
+
delete: 'delete_file',
|
|
23
|
+
bash: 'run_bash',
|
|
24
|
+
shell: 'run_bash',
|
|
25
|
+
grep: 'grep',
|
|
26
|
+
glob: 'file_search',
|
|
27
|
+
search: 'code_search',
|
|
28
|
+
websearch: 'web_search',
|
|
29
|
+
webfetch: 'fetch_page',
|
|
30
|
+
ls: 'list_directory',
|
|
31
|
+
tree: 'tree',
|
|
32
|
+
fetch: 'http_get',
|
|
33
|
+
taskdone: 'task_done',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A composable capability module for an agent.
|
|
38
|
+
*/
|
|
39
|
+
export class Skill {
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
systemPrompt: string = '';
|
|
43
|
+
requiredTools: string[] = [];
|
|
44
|
+
tools: any[] = [];
|
|
45
|
+
handler: ((agent: any, toolRegistry: any) => any[]) | null = null;
|
|
46
|
+
model: string | null = null;
|
|
47
|
+
temperature: number | null = null;
|
|
48
|
+
maxTokens: number | null = null;
|
|
49
|
+
triggers: string[] = [];
|
|
50
|
+
resourceDir: string | null = null;
|
|
51
|
+
license: string | null = null;
|
|
52
|
+
allowedTools: string[] | null = null;
|
|
53
|
+
sourcePath: string | null = null;
|
|
54
|
+
bodyTruncated: boolean = false;
|
|
55
|
+
metadata: Record<string, any> = {};
|
|
56
|
+
|
|
57
|
+
constructor(config: Partial<SkillConfig>) {
|
|
58
|
+
this.name = config.name || '';
|
|
59
|
+
this.description = config.description || '';
|
|
60
|
+
this.systemPrompt = config.systemPrompt || '';
|
|
61
|
+
this.requiredTools = config.requiredTools || [];
|
|
62
|
+
this.tools = config.tools || [];
|
|
63
|
+
this.handler = config.handler || null;
|
|
64
|
+
this.model = config.model || null;
|
|
65
|
+
this.temperature = config.temperature ?? null;
|
|
66
|
+
this.maxTokens = config.maxTokens ?? null;
|
|
67
|
+
this.triggers = config.triggers || [];
|
|
68
|
+
this.resourceDir = config.resourceDir || null;
|
|
69
|
+
this.license = config.license || null;
|
|
70
|
+
this.allowedTools = config.allowedTools || null;
|
|
71
|
+
this.sourcePath = config.sourcePath || null;
|
|
72
|
+
this.bodyTruncated = config.bodyTruncated || false;
|
|
73
|
+
this.metadata = config.metadata || {};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Load a skill from a Markdown file with YAML frontmatter.
|
|
78
|
+
*/
|
|
79
|
+
static fromMarkdown(filePath: string): Skill | null {
|
|
80
|
+
const p = path.resolve(filePath);
|
|
81
|
+
let text: string;
|
|
82
|
+
try {
|
|
83
|
+
text = fs.readFileSync(p, 'utf-8');
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const parsed = parseFrontmatter(text);
|
|
89
|
+
if (!parsed) return null;
|
|
90
|
+
|
|
91
|
+
const { fm, body } = parsed;
|
|
92
|
+
const name = (fm.name as string) || path.basename(p, '.md');
|
|
93
|
+
const description = (fm.description as string) || '';
|
|
94
|
+
|
|
95
|
+
const toolsRaw = fm.tools;
|
|
96
|
+
const requiredTools: string[] = Array.isArray(toolsRaw)
|
|
97
|
+
? toolsRaw.filter((t: any) => typeof t === 'string')
|
|
98
|
+
: [];
|
|
99
|
+
|
|
100
|
+
// Config overrides
|
|
101
|
+
const model = fm.model as string | undefined;
|
|
102
|
+
const temperature = fm.temperature as number | undefined;
|
|
103
|
+
const maxTokens = (fm.maxTokens ?? fm.max_tokens) as number | undefined;
|
|
104
|
+
|
|
105
|
+
// Triggers
|
|
106
|
+
const triggersRaw = fm.triggers;
|
|
107
|
+
const triggers: string[] = Array.isArray(triggersRaw)
|
|
108
|
+
? triggersRaw.filter((t: any) => typeof t === 'string')
|
|
109
|
+
: [];
|
|
110
|
+
|
|
111
|
+
// Auto-derive triggers from description if not specified
|
|
112
|
+
const finalTriggers = triggers.length > 0 ? triggers
|
|
113
|
+
: (description ? deriveTriggersFromDescription(description) : []);
|
|
114
|
+
|
|
115
|
+
// License and allowed-tools
|
|
116
|
+
const licenseRaw = fm.license as string | undefined;
|
|
117
|
+
const license = licenseRaw?.trim() || null;
|
|
118
|
+
|
|
119
|
+
const allowedRaw = fm['allowed-tools'] ?? fm.allowed_tools;
|
|
120
|
+
let allowedTools: string[] | null = null;
|
|
121
|
+
if (Array.isArray(allowedRaw)) {
|
|
122
|
+
allowedTools = allowedRaw.filter((t: any) => typeof t === 'string');
|
|
123
|
+
if (allowedTools.length === 0) allowedTools = null;
|
|
124
|
+
} else if (typeof allowedRaw === 'string' && allowedRaw.trim()) {
|
|
125
|
+
allowedTools = allowedRaw.split(',').map((t: string) => t.trim()).filter(Boolean);
|
|
126
|
+
}
|
|
127
|
+
if (allowedTools) {
|
|
128
|
+
allowedTools = allowedTools.map(t => normalizeClaudeToolName(t));
|
|
129
|
+
// Dedupe preserving order
|
|
130
|
+
const seen = new Set<string>();
|
|
131
|
+
const deduped: string[] = [];
|
|
132
|
+
for (const t of allowedTools) {
|
|
133
|
+
if (!seen.has(t)) { seen.add(t); deduped.push(t); }
|
|
134
|
+
}
|
|
135
|
+
allowedTools = deduped;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Preserve metadata fields
|
|
139
|
+
const knownKeys = new Set([
|
|
140
|
+
'name', 'description', 'tools', 'model', 'temperature', 'maxTokens', 'max_tokens',
|
|
141
|
+
'triggers', 'license', 'allowed-tools', 'allowed_tools',
|
|
142
|
+
]);
|
|
143
|
+
const extraMetadata: Record<string, any> = {};
|
|
144
|
+
for (const [k, v] of Object.entries(fm)) {
|
|
145
|
+
if (!knownKeys.has(k) && !k.startsWith('_')) {
|
|
146
|
+
extraMetadata[k] = v;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Lazy-load large skill bodies
|
|
151
|
+
let bodyStripped = body.trim();
|
|
152
|
+
let bodyTruncated = false;
|
|
153
|
+
if (bodyStripped.length > SKILL_BODY_LITE_THRESHOLD) {
|
|
154
|
+
bodyStripped = extractSkillHead(bodyStripped, SKILL_BODY_LITE_MAX_CHARS);
|
|
155
|
+
bodyTruncated = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return new Skill({
|
|
159
|
+
name,
|
|
160
|
+
description,
|
|
161
|
+
systemPrompt: bodyStripped,
|
|
162
|
+
requiredTools,
|
|
163
|
+
model: typeof model === 'string' ? model : null,
|
|
164
|
+
temperature: typeof temperature === 'number' ? temperature : null,
|
|
165
|
+
maxTokens: typeof maxTokens === 'number' ? maxTokens : null,
|
|
166
|
+
triggers: finalTriggers,
|
|
167
|
+
license,
|
|
168
|
+
allowedTools,
|
|
169
|
+
sourcePath: p,
|
|
170
|
+
bodyTruncated,
|
|
171
|
+
metadata: extraMetadata,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
interface SkillConfig {
|
|
177
|
+
name: string;
|
|
178
|
+
description: string;
|
|
179
|
+
systemPrompt?: string;
|
|
180
|
+
requiredTools?: string[];
|
|
181
|
+
tools?: any[];
|
|
182
|
+
handler?: ((agent: any, toolRegistry: any) => any[]) | null;
|
|
183
|
+
model?: string | null;
|
|
184
|
+
temperature?: number | null;
|
|
185
|
+
maxTokens?: number | null;
|
|
186
|
+
triggers?: string[];
|
|
187
|
+
resourceDir?: string | null;
|
|
188
|
+
license?: string | null;
|
|
189
|
+
allowedTools?: string[] | null;
|
|
190
|
+
sourcePath?: string | null;
|
|
191
|
+
bodyTruncated?: boolean;
|
|
192
|
+
metadata?: Record<string, any>;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Parse YAML frontmatter from markdown text.
|
|
197
|
+
* Returns { fm, body } or null.
|
|
198
|
+
*/
|
|
199
|
+
function parseFrontmatter(text: string): { fm: Record<string, any>; body: string } | null {
|
|
200
|
+
const match = text.match(/^---\s*\n(.*?)\n---\s*\n?(.*)/s);
|
|
201
|
+
if (!match) return null;
|
|
202
|
+
try {
|
|
203
|
+
const fm = parseYaml(match[1]) || {};
|
|
204
|
+
return { fm, body: match[2] };
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Normalize a Claude Code tool name into sky's registry name.
|
|
212
|
+
*/
|
|
213
|
+
function normalizeClaudeToolName(raw: string): string {
|
|
214
|
+
let s = raw.trim();
|
|
215
|
+
if (!s) return s;
|
|
216
|
+
// Strip permission scoping: Bash(ls *) -> Bash
|
|
217
|
+
const paren = s.indexOf('(');
|
|
218
|
+
if (paren > 0) s = s.slice(0, paren).trim();
|
|
219
|
+
// Check if it's already a valid sky name
|
|
220
|
+
const aliasValues = new Set(Object.values(CLAUDE_TOOL_ALIASES));
|
|
221
|
+
if (aliasValues.has(s)) return s;
|
|
222
|
+
return CLAUDE_TOOL_ALIASES[s.toLowerCase()] ?? s;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Extract the head of a SKILL.md body — title plus first major section.
|
|
227
|
+
*/
|
|
228
|
+
function extractSkillHead(body: string, maxChars: number): string {
|
|
229
|
+
const out: string[] = [];
|
|
230
|
+
let charCount = 0;
|
|
231
|
+
let h2Count = 0;
|
|
232
|
+
for (const line of body.split('\n')) {
|
|
233
|
+
const isH2 = line.startsWith('## ') && !line.startsWith('### ');
|
|
234
|
+
if (isH2) {
|
|
235
|
+
h2Count++;
|
|
236
|
+
if (h2Count > 1) break;
|
|
237
|
+
}
|
|
238
|
+
if (out.length > 0 && charCount + line.length + 1 > maxChars) break;
|
|
239
|
+
out.push(line);
|
|
240
|
+
charCount += line.length + 1;
|
|
241
|
+
}
|
|
242
|
+
return out.join('\n').trimEnd();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Patterns for auto-deriving triggers
|
|
246
|
+
const TRIGGER_QUOTED = /["'"'""]([^"'""\n]{1,40})["'""']/g;
|
|
247
|
+
const TRIGGER_EXT = /(?<![A-Za-z0-9])\.[A-Za-z0-9]{2,6}\b/g;
|
|
248
|
+
const TRIGGER_STRIP = " \t,.;:!?,。、;:!?、。";
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Pull candidate trigger phrases out of a skill description.
|
|
252
|
+
*/
|
|
253
|
+
function deriveTriggersFromDescription(description: string): string[] {
|
|
254
|
+
const raw: string[] = [];
|
|
255
|
+
let m: RegExpExecArray | null;
|
|
256
|
+
while ((m = TRIGGER_QUOTED.exec(description)) !== null) {
|
|
257
|
+
raw.push(m[1]);
|
|
258
|
+
}
|
|
259
|
+
while ((m = TRIGGER_EXT.exec(description)) !== null) {
|
|
260
|
+
raw.push(m[0]);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const seen = new Set<string>();
|
|
264
|
+
const out: string[] = [];
|
|
265
|
+
for (let token of raw) {
|
|
266
|
+
token = token.replace(/[ \t,.;:!?,。、;:!?、。]+$/, '').replace(/^[ \t]+/, '');
|
|
267
|
+
if (!token || token.length < 2) continue;
|
|
268
|
+
const key = token.toLowerCase();
|
|
269
|
+
if (seen.has(key)) continue;
|
|
270
|
+
seen.add(key);
|
|
271
|
+
out.push(token);
|
|
272
|
+
if (out.length >= 12) break;
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Central registry for all available skills.
|
|
279
|
+
*/
|
|
280
|
+
export class SkillRegistry {
|
|
281
|
+
private _skills: Map<string, Skill> = new Map();
|
|
282
|
+
|
|
283
|
+
register(skill: Skill): void {
|
|
284
|
+
this._skills.set(skill.name, skill);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
get(name: string): Skill | undefined {
|
|
288
|
+
return this._skills.get(name);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
getSkills(names?: string[]): Skill[] {
|
|
292
|
+
if (!names) return Array.from(this._skills.values());
|
|
293
|
+
return names.map(n => this._skills.get(n)).filter(Boolean) as Skill[];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
listNames(): string[] {
|
|
297
|
+
return Array.from(this._skills.keys());
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
merge(other: SkillRegistry): void {
|
|
301
|
+
for (const [name, skill] of other._skills) {
|
|
302
|
+
this._skills.set(name, skill);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Load all .md skill files from a directory (Anthropic format).
|
|
308
|
+
*/
|
|
309
|
+
loadSkillsFromDirectory(directory: string): Skill[] {
|
|
310
|
+
const loaded: Skill[] = [];
|
|
311
|
+
let dirPath: string;
|
|
312
|
+
try {
|
|
313
|
+
dirPath = path.resolve(directory.replace(/^~/, process.env.HOME || process.env.USERPROFILE || ''));
|
|
314
|
+
} catch {
|
|
315
|
+
return loaded;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!fs.existsSync(dirPath)) return loaded;
|
|
319
|
+
|
|
320
|
+
let entries: string[];
|
|
321
|
+
try {
|
|
322
|
+
entries = fs.readdirSync(dirPath);
|
|
323
|
+
} catch {
|
|
324
|
+
return loaded;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
for (const entry of entries.sort()) {
|
|
328
|
+
if (entry.startsWith('_') || entry.startsWith('.') || !entry.endsWith('.md')) continue;
|
|
329
|
+
const fullPath = path.join(dirPath, entry);
|
|
330
|
+
const skill = Skill.fromMarkdown(fullPath);
|
|
331
|
+
if (skill) {
|
|
332
|
+
skill.resourceDir = dirPath;
|
|
333
|
+
this.register(skill);
|
|
334
|
+
loaded.push(skill);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return loaded;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Global skill registry singleton
|
|
342
|
+
export const globalSkillRegistry = new SkillRegistry();
|