@omnidev-ai/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +31 -0
- package/src/capability/AGENTS.md +58 -0
- package/src/capability/commands.test.ts +414 -0
- package/src/capability/commands.ts +70 -0
- package/src/capability/docs.test.ts +199 -0
- package/src/capability/docs.ts +46 -0
- package/src/capability/index.ts +20 -0
- package/src/capability/loader.test.ts +815 -0
- package/src/capability/loader.ts +492 -0
- package/src/capability/registry.test.ts +473 -0
- package/src/capability/registry.ts +55 -0
- package/src/capability/rules.test.ts +145 -0
- package/src/capability/rules.ts +133 -0
- package/src/capability/skills.test.ts +316 -0
- package/src/capability/skills.ts +56 -0
- package/src/capability/sources.test.ts +338 -0
- package/src/capability/sources.ts +966 -0
- package/src/capability/subagents.test.ts +478 -0
- package/src/capability/subagents.ts +103 -0
- package/src/capability/yaml-parser.ts +81 -0
- package/src/config/AGENTS.md +46 -0
- package/src/config/capabilities.ts +82 -0
- package/src/config/env.test.ts +286 -0
- package/src/config/env.ts +96 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +282 -0
- package/src/config/loader.ts +137 -0
- package/src/config/parser.test.ts +281 -0
- package/src/config/parser.ts +55 -0
- package/src/config/profiles.test.ts +259 -0
- package/src/config/profiles.ts +75 -0
- package/src/config/provider.test.ts +79 -0
- package/src/config/provider.ts +55 -0
- package/src/debug.ts +20 -0
- package/src/gitignore/manager.test.ts +219 -0
- package/src/gitignore/manager.ts +167 -0
- package/src/index.test.ts +26 -0
- package/src/index.ts +39 -0
- package/src/mcp-json/index.ts +1 -0
- package/src/mcp-json/manager.test.ts +415 -0
- package/src/mcp-json/manager.ts +118 -0
- package/src/state/active-profile.test.ts +131 -0
- package/src/state/active-profile.ts +41 -0
- package/src/state/index.ts +2 -0
- package/src/state/manifest.test.ts +548 -0
- package/src/state/manifest.ts +164 -0
- package/src/sync.ts +213 -0
- package/src/templates/agents.test.ts +23 -0
- package/src/templates/agents.ts +14 -0
- package/src/templates/claude.test.ts +48 -0
- package/src/templates/claude.ts +122 -0
- package/src/test-utils/helpers.test.ts +196 -0
- package/src/test-utils/helpers.ts +187 -0
- package/src/test-utils/index.ts +30 -0
- package/src/test-utils/mocks.test.ts +83 -0
- package/src/test-utils/mocks.ts +101 -0
- package/src/types/capability-export.ts +234 -0
- package/src/types/index.test.ts +28 -0
- package/src/types/index.ts +270 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { validateEnv } from "../config/env";
|
|
4
|
+
import { parseCapabilityConfig } from "../config/parser";
|
|
5
|
+
import type {
|
|
6
|
+
CapabilityConfig,
|
|
7
|
+
Command,
|
|
8
|
+
Doc,
|
|
9
|
+
LoadedCapability,
|
|
10
|
+
Rule,
|
|
11
|
+
Skill,
|
|
12
|
+
Subagent,
|
|
13
|
+
} from "../types";
|
|
14
|
+
import type {
|
|
15
|
+
CommandExport,
|
|
16
|
+
DocExport,
|
|
17
|
+
SkillExport,
|
|
18
|
+
SubagentExport,
|
|
19
|
+
} from "../types/capability-export";
|
|
20
|
+
import { loadCommands } from "./commands";
|
|
21
|
+
import { loadDocs } from "./docs";
|
|
22
|
+
import { loadRules } from "./rules";
|
|
23
|
+
import { loadSkills } from "./skills";
|
|
24
|
+
import { loadSubagents } from "./subagents";
|
|
25
|
+
|
|
26
|
+
const CAPABILITIES_DIR = ".omni/capabilities";
|
|
27
|
+
const BUILTIN_CAPABILITIES_DIR = "capabilities";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reserved capability names that cannot be used.
|
|
31
|
+
* These are common package names that might conflict with imports.
|
|
32
|
+
*/
|
|
33
|
+
const RESERVED_NAMES = [
|
|
34
|
+
"fs",
|
|
35
|
+
"path",
|
|
36
|
+
"http",
|
|
37
|
+
"https",
|
|
38
|
+
"crypto",
|
|
39
|
+
"os",
|
|
40
|
+
"child_process",
|
|
41
|
+
"stream",
|
|
42
|
+
"buffer",
|
|
43
|
+
"util",
|
|
44
|
+
"events",
|
|
45
|
+
"net",
|
|
46
|
+
"url",
|
|
47
|
+
"querystring",
|
|
48
|
+
"react",
|
|
49
|
+
"vue",
|
|
50
|
+
"lodash",
|
|
51
|
+
"axios",
|
|
52
|
+
"express",
|
|
53
|
+
"typescript",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if a path is a directory (follows symlinks)
|
|
58
|
+
*/
|
|
59
|
+
function isDirectoryOrSymlink(path: string): boolean {
|
|
60
|
+
try {
|
|
61
|
+
return statSync(path).isDirectory();
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Discovers capabilities by scanning the .omni/capabilities directory.
|
|
69
|
+
* A directory is considered a capability if it contains a capability.toml file.
|
|
70
|
+
* Follows symlinks to support linked capability directories.
|
|
71
|
+
*
|
|
72
|
+
* @returns Array of capability directory paths
|
|
73
|
+
*/
|
|
74
|
+
export async function discoverCapabilities(): Promise<string[]> {
|
|
75
|
+
const capabilities: string[] = [];
|
|
76
|
+
|
|
77
|
+
// Discover built-in capabilities (from capabilities/ directory)
|
|
78
|
+
if (existsSync(BUILTIN_CAPABILITIES_DIR)) {
|
|
79
|
+
const entries = readdirSync(BUILTIN_CAPABILITIES_DIR, { withFileTypes: true });
|
|
80
|
+
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const entryPath = join(BUILTIN_CAPABILITIES_DIR, entry.name);
|
|
83
|
+
if (entry.isDirectory() || (entry.isSymbolicLink() && isDirectoryOrSymlink(entryPath))) {
|
|
84
|
+
const configPath = join(entryPath, "capability.toml");
|
|
85
|
+
if (existsSync(configPath)) {
|
|
86
|
+
capabilities.push(entryPath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Discover project-specific capabilities (from .omni/capabilities/)
|
|
93
|
+
if (existsSync(CAPABILITIES_DIR)) {
|
|
94
|
+
const entries = readdirSync(CAPABILITIES_DIR, { withFileTypes: true });
|
|
95
|
+
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
const entryPath = join(CAPABILITIES_DIR, entry.name);
|
|
98
|
+
if (entry.isDirectory() || (entry.isSymbolicLink() && isDirectoryOrSymlink(entryPath))) {
|
|
99
|
+
const configPath = join(entryPath, "capability.toml");
|
|
100
|
+
if (existsSync(configPath)) {
|
|
101
|
+
capabilities.push(entryPath);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return capabilities;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Loads and parses a capability configuration file.
|
|
112
|
+
* Validates required fields and checks for reserved names.
|
|
113
|
+
*
|
|
114
|
+
* @param capabilityPath - Path to the capability directory
|
|
115
|
+
* @returns Parsed capability configuration
|
|
116
|
+
* @throws Error if the config is invalid or uses a reserved name
|
|
117
|
+
*/
|
|
118
|
+
export async function loadCapabilityConfig(capabilityPath: string): Promise<CapabilityConfig> {
|
|
119
|
+
const configPath = join(capabilityPath, "capability.toml");
|
|
120
|
+
const content = await Bun.file(configPath).text();
|
|
121
|
+
const config = parseCapabilityConfig(content);
|
|
122
|
+
|
|
123
|
+
// Validate name is not reserved
|
|
124
|
+
if (RESERVED_NAMES.includes(config.capability.id)) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Capability name "${config.capability.id}" is reserved. Choose a different name.`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return config;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Dynamically imports capability exports from index.ts.
|
|
135
|
+
* Returns an empty object if index.ts doesn't exist.
|
|
136
|
+
*
|
|
137
|
+
* @param capabilityPath - Path to the capability directory
|
|
138
|
+
* @returns Exported module or empty object
|
|
139
|
+
* @throws Error if import fails
|
|
140
|
+
*/
|
|
141
|
+
async function importCapabilityExports(capabilityPath: string): Promise<Record<string, unknown>> {
|
|
142
|
+
const indexPath = join(capabilityPath, "index.ts");
|
|
143
|
+
|
|
144
|
+
if (!existsSync(indexPath)) {
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const absolutePath = join(process.cwd(), indexPath);
|
|
150
|
+
const module = await import(absolutePath);
|
|
151
|
+
return module;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
// Check if it's a module resolution error
|
|
154
|
+
const errorMessage = String(error);
|
|
155
|
+
if (errorMessage.includes("Cannot find module")) {
|
|
156
|
+
const match = errorMessage.match(/Cannot find module '([^']+)'/);
|
|
157
|
+
const missingModule = match ? match[1] : "unknown";
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Missing dependency '${missingModule}' for capability at ${capabilityPath}.\n` +
|
|
160
|
+
`If this is a project-specific capability, install dependencies or remove it from .omni/capabilities/`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
throw new Error(`Failed to import capability at ${capabilityPath}: ${error}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Loads type definitions from types.d.ts if it exists.
|
|
169
|
+
*
|
|
170
|
+
* @param capabilityPath - Path to the capability directory
|
|
171
|
+
* @returns Type definitions as string or undefined
|
|
172
|
+
*/
|
|
173
|
+
async function loadTypeDefinitions(capabilityPath: string): Promise<string | undefined> {
|
|
174
|
+
const typesPath = join(capabilityPath, "types.d.ts");
|
|
175
|
+
|
|
176
|
+
if (!existsSync(typesPath)) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return Bun.file(typesPath).text();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Convert programmatic skill exports to Skill objects
|
|
185
|
+
* Expects SkillExport format with skillMd (markdown with YAML frontmatter)
|
|
186
|
+
*/
|
|
187
|
+
function convertSkillExports(skillExports: unknown[], capabilityId: string): Skill[] {
|
|
188
|
+
return skillExports.map((skillExport) => {
|
|
189
|
+
const exportObj = skillExport as SkillExport;
|
|
190
|
+
const lines = exportObj.skillMd.split("\n");
|
|
191
|
+
let name = "unnamed";
|
|
192
|
+
let description = "";
|
|
193
|
+
let instructions = exportObj.skillMd;
|
|
194
|
+
|
|
195
|
+
// Simple YAML frontmatter parser
|
|
196
|
+
if (lines[0]?.trim() === "---") {
|
|
197
|
+
const endIndex = lines.findIndex((line, i) => i > 0 && line.trim() === "---");
|
|
198
|
+
if (endIndex > 0) {
|
|
199
|
+
const frontmatter = lines.slice(1, endIndex);
|
|
200
|
+
instructions = lines
|
|
201
|
+
.slice(endIndex + 1)
|
|
202
|
+
.join("\n")
|
|
203
|
+
.trim();
|
|
204
|
+
|
|
205
|
+
for (const line of frontmatter) {
|
|
206
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
207
|
+
if (match?.[1] && match[2]) {
|
|
208
|
+
const key = match[1];
|
|
209
|
+
const value = match[2];
|
|
210
|
+
if (key === "name") {
|
|
211
|
+
name = value.replace(/^["']|["']$/g, "");
|
|
212
|
+
} else if (key === "description") {
|
|
213
|
+
description = value.replace(/^["']|["']$/g, "");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
name,
|
|
222
|
+
description,
|
|
223
|
+
instructions,
|
|
224
|
+
capabilityId,
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Convert programmatic rule exports to Rule objects
|
|
231
|
+
* Expects array of string content (markdown)
|
|
232
|
+
*/
|
|
233
|
+
function convertRuleExports(ruleExports: unknown[], capabilityId: string): Rule[] {
|
|
234
|
+
return ruleExports.map((ruleExport, index) => {
|
|
235
|
+
return {
|
|
236
|
+
name: `rule-${index + 1}`,
|
|
237
|
+
content: String(ruleExport).trim(),
|
|
238
|
+
capabilityId,
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Convert programmatic doc exports to Doc objects
|
|
245
|
+
* Expects DocExport format with title and content
|
|
246
|
+
*/
|
|
247
|
+
function convertDocExports(docExports: unknown[], capabilityId: string): Doc[] {
|
|
248
|
+
return docExports.map((docExport) => {
|
|
249
|
+
const exportObj = docExport as DocExport;
|
|
250
|
+
return {
|
|
251
|
+
name: exportObj.title,
|
|
252
|
+
content: exportObj.content.trim(),
|
|
253
|
+
capabilityId,
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Convert programmatic subagent exports to Subagent objects
|
|
260
|
+
* Parses SubagentExport markdown with YAML frontmatter
|
|
261
|
+
*/
|
|
262
|
+
function convertSubagentExports(subagentExports: unknown[], capabilityId: string): Subagent[] {
|
|
263
|
+
return subagentExports.map((subagentExport) => {
|
|
264
|
+
const exportObj = subagentExport as SubagentExport;
|
|
265
|
+
const lines = exportObj.subagentMd.split("\n");
|
|
266
|
+
let name = "unnamed";
|
|
267
|
+
let description = "";
|
|
268
|
+
let systemPrompt = exportObj.subagentMd;
|
|
269
|
+
let tools: string[] | undefined;
|
|
270
|
+
let disallowedTools: string[] | undefined;
|
|
271
|
+
let model: string | undefined;
|
|
272
|
+
let permissionMode: string | undefined;
|
|
273
|
+
let skills: string[] | undefined;
|
|
274
|
+
|
|
275
|
+
// Simple YAML frontmatter parser
|
|
276
|
+
if (lines[0]?.trim() === "---") {
|
|
277
|
+
const endIndex = lines.findIndex((line, i) => i > 0 && line.trim() === "---");
|
|
278
|
+
if (endIndex > 0) {
|
|
279
|
+
const frontmatter = lines.slice(1, endIndex);
|
|
280
|
+
systemPrompt = lines
|
|
281
|
+
.slice(endIndex + 1)
|
|
282
|
+
.join("\n")
|
|
283
|
+
.trim();
|
|
284
|
+
|
|
285
|
+
for (const line of frontmatter) {
|
|
286
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
287
|
+
if (match?.[1] && match[2]) {
|
|
288
|
+
const key = match[1];
|
|
289
|
+
const value = match[2].replace(/^["']|["']$/g, "");
|
|
290
|
+
switch (key) {
|
|
291
|
+
case "name":
|
|
292
|
+
name = value;
|
|
293
|
+
break;
|
|
294
|
+
case "description":
|
|
295
|
+
description = value;
|
|
296
|
+
break;
|
|
297
|
+
case "tools":
|
|
298
|
+
tools = value.split(",").map((t) => t.trim());
|
|
299
|
+
break;
|
|
300
|
+
case "disallowedTools":
|
|
301
|
+
disallowedTools = value.split(",").map((t) => t.trim());
|
|
302
|
+
break;
|
|
303
|
+
case "model":
|
|
304
|
+
model = value;
|
|
305
|
+
break;
|
|
306
|
+
case "permissionMode":
|
|
307
|
+
permissionMode = value;
|
|
308
|
+
break;
|
|
309
|
+
case "skills":
|
|
310
|
+
skills = value.split(",").map((s) => s.trim());
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const result: Subagent = {
|
|
319
|
+
name,
|
|
320
|
+
description,
|
|
321
|
+
systemPrompt,
|
|
322
|
+
capabilityId,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
if (tools) result.tools = tools;
|
|
326
|
+
if (disallowedTools) result.disallowedTools = disallowedTools;
|
|
327
|
+
if (model) {
|
|
328
|
+
result.model = model as NonNullable<Subagent["model"]>;
|
|
329
|
+
}
|
|
330
|
+
if (permissionMode) {
|
|
331
|
+
result.permissionMode = permissionMode as NonNullable<Subagent["permissionMode"]>;
|
|
332
|
+
}
|
|
333
|
+
if (skills) result.skills = skills;
|
|
334
|
+
|
|
335
|
+
return result;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Convert programmatic command exports to Command objects
|
|
341
|
+
* Parses CommandExport markdown with YAML frontmatter
|
|
342
|
+
*/
|
|
343
|
+
function convertCommandExports(commandExports: unknown[], capabilityId: string): Command[] {
|
|
344
|
+
return commandExports.map((commandExport) => {
|
|
345
|
+
const exportObj = commandExport as CommandExport;
|
|
346
|
+
const lines = exportObj.commandMd.split("\n");
|
|
347
|
+
let name = "unnamed";
|
|
348
|
+
let description = "";
|
|
349
|
+
let prompt = exportObj.commandMd;
|
|
350
|
+
let allowedTools: string | undefined;
|
|
351
|
+
|
|
352
|
+
// Simple YAML frontmatter parser
|
|
353
|
+
if (lines[0]?.trim() === "---") {
|
|
354
|
+
const endIndex = lines.findIndex((line, i) => i > 0 && line.trim() === "---");
|
|
355
|
+
if (endIndex > 0) {
|
|
356
|
+
const frontmatter = lines.slice(1, endIndex);
|
|
357
|
+
prompt = lines
|
|
358
|
+
.slice(endIndex + 1)
|
|
359
|
+
.join("\n")
|
|
360
|
+
.trim();
|
|
361
|
+
|
|
362
|
+
for (const line of frontmatter) {
|
|
363
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
364
|
+
if (match?.[1] && match[2]) {
|
|
365
|
+
const key = match[1];
|
|
366
|
+
const value = match[2].replace(/^["']|["']$/g, "");
|
|
367
|
+
switch (key) {
|
|
368
|
+
case "name":
|
|
369
|
+
name = value;
|
|
370
|
+
break;
|
|
371
|
+
case "description":
|
|
372
|
+
description = value;
|
|
373
|
+
break;
|
|
374
|
+
case "allowedTools":
|
|
375
|
+
case "allowed-tools":
|
|
376
|
+
allowedTools = value;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const result: Command = {
|
|
385
|
+
name,
|
|
386
|
+
description,
|
|
387
|
+
prompt,
|
|
388
|
+
capabilityId,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
if (allowedTools) {
|
|
392
|
+
result.allowedTools = allowedTools;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return result;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Loads a complete capability including config, skills, rules, docs, and exports.
|
|
401
|
+
* Validates environment requirements before loading.
|
|
402
|
+
*
|
|
403
|
+
* @param capabilityPath - Path to the capability directory
|
|
404
|
+
* @param env - Environment variables to validate against
|
|
405
|
+
* @returns Fully loaded capability
|
|
406
|
+
* @throws Error if validation fails or loading errors occur
|
|
407
|
+
*/
|
|
408
|
+
export async function loadCapability(
|
|
409
|
+
capabilityPath: string,
|
|
410
|
+
env: Record<string, string>,
|
|
411
|
+
): Promise<LoadedCapability> {
|
|
412
|
+
const config = await loadCapabilityConfig(capabilityPath);
|
|
413
|
+
const id = config.capability.id;
|
|
414
|
+
|
|
415
|
+
// Validate environment
|
|
416
|
+
if (config.env) {
|
|
417
|
+
validateEnv(config.env, env, id);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Load content - programmatic takes precedence
|
|
421
|
+
const exports = await importCapabilityExports(capabilityPath);
|
|
422
|
+
|
|
423
|
+
// Check if exports contains programmatic skills/rules/docs
|
|
424
|
+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic module exports need runtime type checking
|
|
425
|
+
const exportsAny = exports as any;
|
|
426
|
+
|
|
427
|
+
const skills =
|
|
428
|
+
"skills" in exports && Array.isArray(exportsAny.skills)
|
|
429
|
+
? convertSkillExports(exportsAny.skills, id)
|
|
430
|
+
: await loadSkills(capabilityPath, id);
|
|
431
|
+
|
|
432
|
+
const rules =
|
|
433
|
+
"rules" in exports && Array.isArray(exportsAny.rules)
|
|
434
|
+
? convertRuleExports(exportsAny.rules, id)
|
|
435
|
+
: await loadRules(capabilityPath, id);
|
|
436
|
+
|
|
437
|
+
const docs =
|
|
438
|
+
"docs" in exports && Array.isArray(exportsAny.docs)
|
|
439
|
+
? convertDocExports(exportsAny.docs, id)
|
|
440
|
+
: await loadDocs(capabilityPath, id);
|
|
441
|
+
|
|
442
|
+
const subagents =
|
|
443
|
+
"subagents" in exports && Array.isArray(exportsAny.subagents)
|
|
444
|
+
? convertSubagentExports(exportsAny.subagents, id)
|
|
445
|
+
: await loadSubagents(capabilityPath, id);
|
|
446
|
+
|
|
447
|
+
const commands =
|
|
448
|
+
"commands" in exports && Array.isArray(exportsAny.commands)
|
|
449
|
+
? convertCommandExports(exportsAny.commands, id)
|
|
450
|
+
: await loadCommands(capabilityPath, id);
|
|
451
|
+
|
|
452
|
+
const typeDefinitionsFromExports =
|
|
453
|
+
"typeDefinitions" in exports && typeof exportsAny.typeDefinitions === "string"
|
|
454
|
+
? (exportsAny.typeDefinitions as string)
|
|
455
|
+
: undefined;
|
|
456
|
+
|
|
457
|
+
const typeDefinitions =
|
|
458
|
+
typeDefinitionsFromExports !== undefined
|
|
459
|
+
? typeDefinitionsFromExports
|
|
460
|
+
: await loadTypeDefinitions(capabilityPath);
|
|
461
|
+
|
|
462
|
+
// Extract gitignore patterns from exports
|
|
463
|
+
const gitignore =
|
|
464
|
+
"gitignore" in exports && Array.isArray(exportsAny.gitignore)
|
|
465
|
+
? (exportsAny.gitignore as string[])
|
|
466
|
+
: undefined;
|
|
467
|
+
|
|
468
|
+
// Build result object with explicit handling for optional typeDefinitions
|
|
469
|
+
const result: LoadedCapability = {
|
|
470
|
+
id,
|
|
471
|
+
path: capabilityPath,
|
|
472
|
+
config,
|
|
473
|
+
skills,
|
|
474
|
+
rules,
|
|
475
|
+
docs,
|
|
476
|
+
subagents,
|
|
477
|
+
commands,
|
|
478
|
+
exports,
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// Only add typeDefinitions if it exists
|
|
482
|
+
if (typeDefinitions !== undefined) {
|
|
483
|
+
result.typeDefinitions = typeDefinitions;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Only add gitignore if it exists
|
|
487
|
+
if (gitignore !== undefined) {
|
|
488
|
+
result.gitignore = gitignore;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return result;
|
|
492
|
+
}
|