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.
Files changed (225) hide show
  1. package/.github/workflows/ci.yml +36 -0
  2. package/CONVERSION_PLAN.md +191 -0
  3. package/README.md +67 -0
  4. package/dist/agents/dew.d.ts +15 -0
  5. package/dist/agents/dew.d.ts.map +1 -0
  6. package/dist/agents/dew.js +74 -0
  7. package/dist/agents/dew.js.map +1 -0
  8. package/dist/agents/fair.d.ts +15 -0
  9. package/dist/agents/fair.d.ts.map +1 -0
  10. package/dist/agents/fair.js +106 -0
  11. package/dist/agents/fair.js.map +1 -0
  12. package/dist/agents/fog.d.ts +15 -0
  13. package/dist/agents/fog.d.ts.map +1 -0
  14. package/dist/agents/fog.js +52 -0
  15. package/dist/agents/fog.js.map +1 -0
  16. package/dist/agents/frost.d.ts +15 -0
  17. package/dist/agents/frost.d.ts.map +1 -0
  18. package/dist/agents/frost.js +54 -0
  19. package/dist/agents/frost.js.map +1 -0
  20. package/dist/agents/rain.d.ts +15 -0
  21. package/dist/agents/rain.d.ts.map +1 -0
  22. package/dist/agents/rain.js +54 -0
  23. package/dist/agents/rain.js.map +1 -0
  24. package/dist/agents/snow.d.ts +27 -0
  25. package/dist/agents/snow.d.ts.map +1 -0
  26. package/dist/agents/snow.js +226 -0
  27. package/dist/agents/snow.js.map +1 -0
  28. package/dist/cli/main.d.ts +7 -0
  29. package/dist/cli/main.d.ts.map +1 -0
  30. package/dist/cli/main.js +402 -0
  31. package/dist/cli/main.js.map +1 -0
  32. package/dist/cli/mode.d.ts +17 -0
  33. package/dist/cli/mode.d.ts.map +1 -0
  34. package/dist/cli/mode.js +56 -0
  35. package/dist/cli/mode.js.map +1 -0
  36. package/dist/core/agent.d.ts +174 -0
  37. package/dist/core/agent.d.ts.map +1 -0
  38. package/dist/core/agent.js +1332 -0
  39. package/dist/core/agent.js.map +1 -0
  40. package/dist/core/agent_helpers.d.ts +51 -0
  41. package/dist/core/agent_helpers.d.ts.map +1 -0
  42. package/dist/core/agent_helpers.js +477 -0
  43. package/dist/core/agent_helpers.js.map +1 -0
  44. package/dist/core/bus.d.ts +99 -0
  45. package/dist/core/bus.d.ts.map +1 -0
  46. package/dist/core/bus.js +191 -0
  47. package/dist/core/bus.js.map +1 -0
  48. package/dist/core/cache.d.ts +63 -0
  49. package/dist/core/cache.d.ts.map +1 -0
  50. package/dist/core/cache.js +121 -0
  51. package/dist/core/cache.js.map +1 -0
  52. package/dist/core/checkpoint.d.ts +19 -0
  53. package/dist/core/checkpoint.d.ts.map +1 -0
  54. package/dist/core/checkpoint.js +120 -0
  55. package/dist/core/checkpoint.js.map +1 -0
  56. package/dist/core/circuit_breaker.d.ts +46 -0
  57. package/dist/core/circuit_breaker.d.ts.map +1 -0
  58. package/dist/core/circuit_breaker.js +99 -0
  59. package/dist/core/circuit_breaker.js.map +1 -0
  60. package/dist/core/config.d.ts +97 -0
  61. package/dist/core/config.d.ts.map +1 -0
  62. package/dist/core/config.js +281 -0
  63. package/dist/core/config.js.map +1 -0
  64. package/dist/core/constants.d.ts +78 -0
  65. package/dist/core/constants.d.ts.map +1 -0
  66. package/dist/core/constants.js +84 -0
  67. package/dist/core/constants.js.map +1 -0
  68. package/dist/core/factory.d.ts +63 -0
  69. package/dist/core/factory.d.ts.map +1 -0
  70. package/dist/core/factory.js +537 -0
  71. package/dist/core/factory.js.map +1 -0
  72. package/dist/core/icons.d.ts +28 -0
  73. package/dist/core/icons.d.ts.map +1 -0
  74. package/dist/core/icons.js +86 -0
  75. package/dist/core/icons.js.map +1 -0
  76. package/dist/core/index.d.ts +29 -0
  77. package/dist/core/index.d.ts.map +1 -0
  78. package/dist/core/index.js +54 -0
  79. package/dist/core/index.js.map +1 -0
  80. package/dist/core/llm.d.ts +121 -0
  81. package/dist/core/llm.d.ts.map +1 -0
  82. package/dist/core/llm.js +532 -0
  83. package/dist/core/llm.js.map +1 -0
  84. package/dist/core/logger.d.ts +57 -0
  85. package/dist/core/logger.d.ts.map +1 -0
  86. package/dist/core/logger.js +122 -0
  87. package/dist/core/logger.js.map +1 -0
  88. package/dist/core/mcp.d.ts +190 -0
  89. package/dist/core/mcp.d.ts.map +1 -0
  90. package/dist/core/mcp.js +822 -0
  91. package/dist/core/mcp.js.map +1 -0
  92. package/dist/core/mcp_server.d.ts +26 -0
  93. package/dist/core/mcp_server.d.ts.map +1 -0
  94. package/dist/core/mcp_server.js +211 -0
  95. package/dist/core/mcp_server.js.map +1 -0
  96. package/dist/core/memory.d.ts +190 -0
  97. package/dist/core/memory.d.ts.map +1 -0
  98. package/dist/core/memory.js +988 -0
  99. package/dist/core/memory.js.map +1 -0
  100. package/dist/core/middleware.d.ts +114 -0
  101. package/dist/core/middleware.d.ts.map +1 -0
  102. package/dist/core/middleware.js +248 -0
  103. package/dist/core/middleware.js.map +1 -0
  104. package/dist/core/pipelines.d.ts +87 -0
  105. package/dist/core/pipelines.d.ts.map +1 -0
  106. package/dist/core/pipelines.js +301 -0
  107. package/dist/core/pipelines.js.map +1 -0
  108. package/dist/core/profile.d.ts +23 -0
  109. package/dist/core/profile.d.ts.map +1 -0
  110. package/dist/core/profile.js +289 -0
  111. package/dist/core/profile.js.map +1 -0
  112. package/dist/core/router.d.ts +24 -0
  113. package/dist/core/router.d.ts.map +1 -0
  114. package/dist/core/router.js +111 -0
  115. package/dist/core/router.js.map +1 -0
  116. package/dist/core/schemas.d.ts +82 -0
  117. package/dist/core/schemas.d.ts.map +1 -0
  118. package/dist/core/schemas.js +200 -0
  119. package/dist/core/schemas.js.map +1 -0
  120. package/dist/core/semantic.d.ts +92 -0
  121. package/dist/core/semantic.d.ts.map +1 -0
  122. package/dist/core/semantic.js +175 -0
  123. package/dist/core/semantic.js.map +1 -0
  124. package/dist/core/skill.d.ts +68 -0
  125. package/dist/core/skill.d.ts.map +1 -0
  126. package/dist/core/skill.js +350 -0
  127. package/dist/core/skill.js.map +1 -0
  128. package/dist/core/tool.d.ts +99 -0
  129. package/dist/core/tool.d.ts.map +1 -0
  130. package/dist/core/tool.js +341 -0
  131. package/dist/core/tool.js.map +1 -0
  132. package/dist/core/tool_router.d.ts +29 -0
  133. package/dist/core/tool_router.d.ts.map +1 -0
  134. package/dist/core/tool_router.js +172 -0
  135. package/dist/core/tool_router.js.map +1 -0
  136. package/dist/core/workspace.d.ts +48 -0
  137. package/dist/core/workspace.d.ts.map +1 -0
  138. package/dist/core/workspace.js +179 -0
  139. package/dist/core/workspace.js.map +1 -0
  140. package/dist/plugins/loader.d.ts +17 -0
  141. package/dist/plugins/loader.d.ts.map +1 -0
  142. package/dist/plugins/loader.js +96 -0
  143. package/dist/plugins/loader.js.map +1 -0
  144. package/dist/skills/loader.d.ts +9 -0
  145. package/dist/skills/loader.d.ts.map +1 -0
  146. package/dist/skills/loader.js +78 -0
  147. package/dist/skills/loader.js.map +1 -0
  148. package/dist/tools/builtin.d.ts +10 -0
  149. package/dist/tools/builtin.d.ts.map +1 -0
  150. package/dist/tools/builtin.js +414 -0
  151. package/dist/tools/builtin.js.map +1 -0
  152. package/dist/tools/computer.d.ts +12 -0
  153. package/dist/tools/computer.d.ts.map +1 -0
  154. package/dist/tools/computer.js +326 -0
  155. package/dist/tools/computer.js.map +1 -0
  156. package/dist/tools/delegate.d.ts +10 -0
  157. package/dist/tools/delegate.d.ts.map +1 -0
  158. package/dist/tools/delegate.js +45 -0
  159. package/dist/tools/delegate.js.map +1 -0
  160. package/dist/web/server.d.ts +5 -0
  161. package/dist/web/server.d.ts.map +1 -0
  162. package/dist/web/server.js +647 -0
  163. package/dist/web/server.js.map +1 -0
  164. package/dist/web/tts.d.ts +33 -0
  165. package/dist/web/tts.d.ts.map +1 -0
  166. package/dist/web/tts.js +69 -0
  167. package/dist/web/tts.js.map +1 -0
  168. package/package.json +60 -0
  169. package/scripts/install.js +48 -0
  170. package/scripts/link.js +10 -0
  171. package/setup.bat +79 -0
  172. package/skill-test-ty2fOA/test.md +10 -0
  173. package/src/agents/dew.ts +70 -0
  174. package/src/agents/fair.ts +102 -0
  175. package/src/agents/fog.ts +48 -0
  176. package/src/agents/frost.ts +50 -0
  177. package/src/agents/rain.ts +50 -0
  178. package/src/agents/snow.ts +239 -0
  179. package/src/cli/main.ts +405 -0
  180. package/src/cli/mode.ts +58 -0
  181. package/src/core/agent.ts +1506 -0
  182. package/src/core/agent_helpers.ts +461 -0
  183. package/src/core/bus.ts +221 -0
  184. package/src/core/cache.ts +153 -0
  185. package/src/core/checkpoint.ts +94 -0
  186. package/src/core/circuit_breaker.ts +119 -0
  187. package/src/core/config.ts +341 -0
  188. package/src/core/constants.ts +95 -0
  189. package/src/core/factory.ts +627 -0
  190. package/src/core/icons.ts +53 -0
  191. package/src/core/index.ts +31 -0
  192. package/src/core/llm.ts +724 -0
  193. package/src/core/logger.ts +144 -0
  194. package/src/core/mcp.ts +953 -0
  195. package/src/core/mcp_server.ts +176 -0
  196. package/src/core/memory.ts +1169 -0
  197. package/src/core/middleware.ts +350 -0
  198. package/src/core/pipelines.ts +424 -0
  199. package/src/core/profile.ts +255 -0
  200. package/src/core/router.ts +124 -0
  201. package/src/core/schemas.ts +282 -0
  202. package/src/core/semantic.ts +211 -0
  203. package/src/core/skill.ts +342 -0
  204. package/src/core/tool.ts +427 -0
  205. package/src/core/tool_router.ts +193 -0
  206. package/src/core/workspace.ts +150 -0
  207. package/src/plugins/loader.ts +66 -0
  208. package/src/skills/loader.ts +46 -0
  209. package/src/sql.js.d.ts +29 -0
  210. package/src/tools/builtin.ts +382 -0
  211. package/src/tools/computer.ts +269 -0
  212. package/src/tools/delegate.ts +49 -0
  213. package/src/web/server.ts +634 -0
  214. package/src/web/tts.ts +93 -0
  215. package/tests/bus.test.ts +121 -0
  216. package/tests/icons.test.ts +45 -0
  217. package/tests/router.test.ts +86 -0
  218. package/tests/schemas.test.ts +51 -0
  219. package/tests/semantic.test.ts +83 -0
  220. package/tests/setup.ts +10 -0
  221. package/tests/skill.test.ts +172 -0
  222. package/tests/tool.test.ts +108 -0
  223. package/tests/tool_router.test.ts +71 -0
  224. package/tsconfig.json +37 -0
  225. 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();