opencode-lazy-loader 1.0.0 → 1.0.2

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 (38) hide show
  1. package/README.md +81 -3
  2. package/dist/__tests__/normalize-command.test.d.ts +2 -0
  3. package/dist/__tests__/normalize-command.test.d.ts.map +1 -0
  4. package/dist/__tests__/normalize-command.test.js +187 -0
  5. package/dist/__tests__/normalize-command.test.js.map +1 -0
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +63 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/skill-loader.d.ts +27 -0
  11. package/dist/skill-loader.d.ts.map +1 -0
  12. package/dist/skill-loader.js +197 -0
  13. package/dist/skill-loader.js.map +1 -0
  14. package/dist/skill-mcp-manager.d.ts +26 -0
  15. package/dist/skill-mcp-manager.d.ts.map +1 -0
  16. package/dist/skill-mcp-manager.js +279 -0
  17. package/dist/skill-mcp-manager.js.map +1 -0
  18. package/dist/tools/skill-mcp.d.ts +13 -0
  19. package/dist/tools/skill-mcp.d.ts.map +1 -0
  20. package/dist/tools/skill-mcp.js +162 -0
  21. package/dist/tools/skill-mcp.js.map +1 -0
  22. package/dist/tools/skill.d.ts +13 -0
  23. package/dist/tools/skill.d.ts.map +1 -0
  24. package/dist/tools/skill.js +160 -0
  25. package/dist/tools/skill.js.map +1 -0
  26. package/dist/types.d.ts +88 -0
  27. package/dist/types.d.ts.map +1 -0
  28. package/dist/types.js +2 -0
  29. package/dist/types.js.map +1 -0
  30. package/dist/utils/env-vars.d.ts +18 -0
  31. package/dist/utils/env-vars.d.ts.map +1 -0
  32. package/dist/utils/env-vars.js +100 -0
  33. package/dist/utils/env-vars.js.map +1 -0
  34. package/dist/utils/frontmatter.d.ts +10 -0
  35. package/dist/utils/frontmatter.d.ts.map +1 -0
  36. package/dist/utils/frontmatter.js +44 -0
  37. package/dist/utils/frontmatter.js.map +1 -0
  38. package/package.json +18 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-mcp-manager.d.ts","sourceRoot":"","sources":["../src/skill-mcp-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAU5E,MAAM,WAAW,eAAe;IAC9B,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChF,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IACvE,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IAC3E,WAAW,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IACzE,QAAQ,CACN,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAA;IACnB,YAAY,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACrF,SAAS,CACP,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,OAAO,CAAC,CAAA;IACnB,mBAAmB,IAAI,MAAM,EAAE,CAAA;IAC/B,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAA;CAC1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAoUvD"}
@@ -0,0 +1,279 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ import { expandEnvVarsInObject, createCleanMcpEnvironment, normalizeCommand, normalizeEnv } from './utils/env-vars.js';
4
+ /**
5
+ * Create a SkillMcpManager instance
6
+ *
7
+ * Features:
8
+ * - Connection pooling keyed by session/skill/server
9
+ * - Lazy connection creation
10
+ * - Idle cleanup after 5 minutes
11
+ * - Session/process cleanup
12
+ */
13
+ export function createSkillMcpManager() {
14
+ const clients = new Map();
15
+ const pendingConnections = new Map();
16
+ let cleanupRegistered = false;
17
+ let cleanupInterval = null;
18
+ const IDLE_TIMEOUT = 5 * 60 * 1000; // 5 minutes
19
+ const getClientKey = (info) => {
20
+ return `${info.sessionID}:${info.skillName}:${info.serverName}`;
21
+ };
22
+ const registerProcessCleanup = () => {
23
+ if (cleanupRegistered) {
24
+ return;
25
+ }
26
+ cleanupRegistered = true;
27
+ const cleanup = async () => {
28
+ for (const [, managed] of clients) {
29
+ try {
30
+ await managed.client.close();
31
+ }
32
+ catch {
33
+ // Ignore cleanup errors
34
+ }
35
+ try {
36
+ await managed.transport.close();
37
+ }
38
+ catch {
39
+ // Ignore cleanup errors
40
+ }
41
+ }
42
+ clients.clear();
43
+ pendingConnections.clear();
44
+ };
45
+ process.on('SIGINT', async () => {
46
+ await cleanup();
47
+ process.exit(0);
48
+ });
49
+ process.on('SIGTERM', async () => {
50
+ await cleanup();
51
+ process.exit(0);
52
+ });
53
+ if (process.platform === 'win32') {
54
+ process.on('SIGBREAK', async () => {
55
+ await cleanup();
56
+ process.exit(0);
57
+ });
58
+ }
59
+ };
60
+ const createClient = async (info, config) => {
61
+ const key = getClientKey(info);
62
+ if (!config.command) {
63
+ throw new Error(`MCP server "${info.serverName}" is missing required 'command' field.\n\n` +
64
+ `The MCP configuration in skill "${info.skillName}" must specify a command to execute.\n\n` +
65
+ `Supported formats:\n` +
66
+ ` Format A (array): command: ["npx", "-y", "@some/mcp-server"]\n` +
67
+ ` Format B (string): command: "npx", args: ["-y", "@some/mcp-server"]`);
68
+ }
69
+ const { command, args } = normalizeCommand(config);
70
+ const { env } = normalizeEnv(config);
71
+ const mergedEnv = createCleanMcpEnvironment(env);
72
+ registerProcessCleanup();
73
+ const transport = new StdioClientTransport({
74
+ command,
75
+ args,
76
+ env: mergedEnv,
77
+ stderr: 'ignore'
78
+ });
79
+ const client = new Client({ name: `skill-mcp-${info.skillName}-${info.serverName}`, version: '1.0.0' }, { capabilities: {} });
80
+ try {
81
+ await client.connect(transport);
82
+ }
83
+ catch (error) {
84
+ try {
85
+ await transport.close();
86
+ }
87
+ catch {
88
+ // Ignore cleanup errors
89
+ }
90
+ const errorMessage = error instanceof Error ? error.message : String(error);
91
+ throw new Error(`Failed to connect to MCP server "${info.serverName}".\n\n` +
92
+ `Command: ${command} ${args.join(' ')}\n` +
93
+ `Reason: ${errorMessage}\n\n` +
94
+ `Hints:\n` +
95
+ ` - Ensure the command is installed and available in PATH\n` +
96
+ ` - Check if the MCP server package exists\n` +
97
+ ` - Verify the args are correct for this server`);
98
+ }
99
+ clients.set(key, {
100
+ client,
101
+ transport,
102
+ skillName: info.skillName,
103
+ lastUsedAt: Date.now()
104
+ });
105
+ startCleanupTimer();
106
+ return client;
107
+ };
108
+ const getOrCreateClient = async (info, config) => {
109
+ const key = getClientKey(info);
110
+ const existing = clients.get(key);
111
+ if (existing) {
112
+ existing.lastUsedAt = Date.now();
113
+ return existing.client;
114
+ }
115
+ const pending = pendingConnections.get(key);
116
+ if (pending) {
117
+ return pending;
118
+ }
119
+ const expandedConfig = expandEnvVarsInObject(config);
120
+ const connectionPromise = createClient(info, expandedConfig);
121
+ pendingConnections.set(key, connectionPromise);
122
+ try {
123
+ const client = await connectionPromise;
124
+ return client;
125
+ }
126
+ finally {
127
+ pendingConnections.delete(key);
128
+ }
129
+ };
130
+ const disconnectSession = async (sessionID) => {
131
+ for (const [key, managed] of clients.entries()) {
132
+ if (key.startsWith(`${sessionID}:`)) {
133
+ clients.delete(key);
134
+ try {
135
+ await managed.client.close();
136
+ }
137
+ catch {
138
+ // Ignore cleanup errors
139
+ }
140
+ try {
141
+ await managed.transport.close();
142
+ }
143
+ catch {
144
+ // Ignore cleanup errors
145
+ }
146
+ }
147
+ }
148
+ };
149
+ const disconnectAll = async () => {
150
+ stopCleanupTimer();
151
+ const allClients = Array.from(clients.values());
152
+ clients.clear();
153
+ for (const managed of allClients) {
154
+ try {
155
+ await managed.client.close();
156
+ }
157
+ catch {
158
+ // Ignore cleanup errors
159
+ }
160
+ try {
161
+ await managed.transport.close();
162
+ }
163
+ catch {
164
+ // Ignore cleanup errors
165
+ }
166
+ }
167
+ };
168
+ const startCleanupTimer = () => {
169
+ if (cleanupInterval) {
170
+ return;
171
+ }
172
+ cleanupInterval = setInterval(() => {
173
+ cleanupIdleClients();
174
+ }, 60000);
175
+ cleanupInterval.unref();
176
+ };
177
+ const stopCleanupTimer = () => {
178
+ if (cleanupInterval) {
179
+ clearInterval(cleanupInterval);
180
+ cleanupInterval = null;
181
+ }
182
+ };
183
+ const cleanupIdleClients = async () => {
184
+ const now = Date.now();
185
+ for (const [key, managed] of clients) {
186
+ if (now - managed.lastUsedAt > IDLE_TIMEOUT) {
187
+ clients.delete(key);
188
+ try {
189
+ await managed.client.close();
190
+ }
191
+ catch {
192
+ // Ignore cleanup errors
193
+ }
194
+ try {
195
+ await managed.transport.close();
196
+ }
197
+ catch {
198
+ // Ignore cleanup errors
199
+ }
200
+ }
201
+ }
202
+ };
203
+ const getOrCreateClientWithRetry = async (info, config) => {
204
+ try {
205
+ return await getOrCreateClient(info, config);
206
+ }
207
+ catch (error) {
208
+ const key = getClientKey(info);
209
+ const existing = clients.get(key);
210
+ if (existing) {
211
+ clients.delete(key);
212
+ try {
213
+ await existing.client.close();
214
+ }
215
+ catch {
216
+ // Ignore
217
+ }
218
+ try {
219
+ await existing.transport.close();
220
+ }
221
+ catch {
222
+ // Ignore
223
+ }
224
+ return await getOrCreateClient(info, config);
225
+ }
226
+ throw error;
227
+ }
228
+ };
229
+ const listTools = async (info, context) => {
230
+ const client = await getOrCreateClientWithRetry(info, context.config);
231
+ const result = await client.listTools();
232
+ return result.tools;
233
+ };
234
+ const listResources = async (info, context) => {
235
+ const client = await getOrCreateClientWithRetry(info, context.config);
236
+ const result = await client.listResources();
237
+ return result.resources;
238
+ };
239
+ const listPrompts = async (info, context) => {
240
+ const client = await getOrCreateClientWithRetry(info, context.config);
241
+ const result = await client.listPrompts();
242
+ return result.prompts;
243
+ };
244
+ const callTool = async (info, context, name, args) => {
245
+ const client = await getOrCreateClientWithRetry(info, context.config);
246
+ const result = await client.callTool({ name, arguments: args });
247
+ return result.content;
248
+ };
249
+ const readResource = async (info, context, uri) => {
250
+ const client = await getOrCreateClientWithRetry(info, context.config);
251
+ const result = await client.readResource({ uri });
252
+ return result.contents;
253
+ };
254
+ const getPrompt = async (info, context, name, args) => {
255
+ const client = await getOrCreateClientWithRetry(info, context.config);
256
+ const result = await client.getPrompt({ name, arguments: args });
257
+ return result.messages;
258
+ };
259
+ const getConnectedServers = () => {
260
+ return Array.from(clients.keys());
261
+ };
262
+ const isConnected = (info) => {
263
+ return clients.has(getClientKey(info));
264
+ };
265
+ return {
266
+ getOrCreateClient,
267
+ disconnectSession,
268
+ disconnectAll,
269
+ listTools,
270
+ listResources,
271
+ listPrompts,
272
+ callTool,
273
+ readResource,
274
+ getPrompt,
275
+ getConnectedServers,
276
+ isConnected
277
+ };
278
+ }
279
+ //# sourceMappingURL=skill-mcp-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-mcp-manager.js","sourceRoot":"","sources":["../src/skill-mcp-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ import type { LoadedSkill } from '../types.js';
3
+ import type { SkillMcpManager } from '../skill-mcp-manager.js';
4
+ export interface CreateSkillMcpToolOptions {
5
+ manager: SkillMcpManager;
6
+ getLoadedSkills: () => LoadedSkill[];
7
+ getSessionID: () => string;
8
+ }
9
+ /**
10
+ * Create the skill_mcp tool
11
+ */
12
+ export declare function createSkillMcpTool(options: CreateSkillMcpToolOptions): ToolDefinition;
13
+ //# sourceMappingURL=skill-mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-mcp.d.ts","sourceRoot":"","sources":["../../src/tools/skill-mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACpE,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,aAAa,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAqI9D,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,eAAe,CAAA;IACxB,eAAe,EAAE,MAAM,WAAW,EAAE,CAAA;IACpC,YAAY,EAAE,MAAM,MAAM,CAAA;CAC3B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,cAAc,CAoErF"}
@@ -0,0 +1,162 @@
1
+ import { tool } from '@opencode-ai/plugin/tool';
2
+ const SKILL_MCP_DESCRIPTION = `Invoke MCP server operations from skill-embedded MCPs. Requires mcp_name plus exactly one of: tool_name, resource_name, or prompt_name.`;
3
+ /**
4
+ * Validate that exactly one operation parameter is provided
5
+ */
6
+ function validateOperationParams(args) {
7
+ const operations = [];
8
+ if (args.tool_name) {
9
+ operations.push({ type: 'tool', name: args.tool_name });
10
+ }
11
+ if (args.resource_name) {
12
+ operations.push({ type: 'resource', name: args.resource_name });
13
+ }
14
+ if (args.prompt_name) {
15
+ operations.push({ type: 'prompt', name: args.prompt_name });
16
+ }
17
+ if (operations.length === 0) {
18
+ throw new Error(`Missing operation. Exactly one of tool_name, resource_name, or prompt_name must be specified.\n\n` +
19
+ `Examples:\n` +
20
+ ` skill_mcp(mcp_name="sqlite", tool_name="query", arguments='{"sql": "SELECT * FROM users"}')\n` +
21
+ ` skill_mcp(mcp_name="memory", resource_name="memory://notes")\n` +
22
+ ` skill_mcp(mcp_name="helper", prompt_name="summarize", arguments='{"text": "..."}')`);
23
+ }
24
+ if (operations.length > 1) {
25
+ const provided = [
26
+ args.tool_name && `tool_name="${args.tool_name}"`,
27
+ args.resource_name && `resource_name="${args.resource_name}"`,
28
+ args.prompt_name && `prompt_name="${args.prompt_name}"`
29
+ ].filter(Boolean).join(', ');
30
+ throw new Error(`Multiple operations specified. Exactly one of tool_name, resource_name, or prompt_name must be provided.\n\n` +
31
+ `Received: ${provided}\n\n` +
32
+ `Use separate calls for each operation.`);
33
+ }
34
+ return operations[0];
35
+ }
36
+ /**
37
+ * Find an MCP server configuration by name across all skills
38
+ */
39
+ function findMcpServer(mcpName, skills) {
40
+ for (const skill of skills) {
41
+ if (skill.mcpConfig && mcpName in skill.mcpConfig) {
42
+ return { skill, config: skill.mcpConfig[mcpName] };
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Format available MCPs for error message
49
+ */
50
+ function formatAvailableMcps(skills) {
51
+ const mcps = [];
52
+ for (const skill of skills) {
53
+ if (skill.mcpConfig) {
54
+ for (const serverName of Object.keys(skill.mcpConfig)) {
55
+ mcps.push(` - "${serverName}" from skill "${skill.name}"`);
56
+ }
57
+ }
58
+ }
59
+ return mcps.length > 0 ? mcps.join('\n') : ' (none found)';
60
+ }
61
+ /**
62
+ * Parse JSON arguments string
63
+ */
64
+ function parseArguments(argsJson) {
65
+ if (!argsJson) {
66
+ return {};
67
+ }
68
+ try {
69
+ const parsed = JSON.parse(argsJson);
70
+ if (typeof parsed !== 'object' || parsed === null) {
71
+ throw new Error('Arguments must be a JSON object');
72
+ }
73
+ return parsed;
74
+ }
75
+ catch (error) {
76
+ const errorMessage = error instanceof Error ? error.message : String(error);
77
+ throw new Error(`Invalid arguments JSON: ${errorMessage}\n\n` +
78
+ `Expected a valid JSON object, e.g.: '{"key": "value"}'\n` +
79
+ `Received: ${argsJson}`);
80
+ }
81
+ }
82
+ /**
83
+ * Apply grep filter to output
84
+ */
85
+ function applyGrepFilter(output, pattern) {
86
+ if (!pattern) {
87
+ return output;
88
+ }
89
+ try {
90
+ const regex = new RegExp(pattern, 'i');
91
+ const lines = output.split('\n');
92
+ const filtered = lines.filter((line) => regex.test(line));
93
+ return filtered.length > 0
94
+ ? filtered.join('\n')
95
+ : `[grep] No lines matched pattern: ${pattern}`;
96
+ }
97
+ catch {
98
+ return output;
99
+ }
100
+ }
101
+ /**
102
+ * Create the skill_mcp tool
103
+ */
104
+ export function createSkillMcpTool(options) {
105
+ const { manager, getLoadedSkills, getSessionID } = options;
106
+ return tool({
107
+ description: SKILL_MCP_DESCRIPTION,
108
+ args: {
109
+ mcp_name: tool.schema.string().describe('Name of the MCP server from skill config'),
110
+ tool_name: tool.schema.string().optional().describe('MCP tool to call'),
111
+ resource_name: tool.schema.string().optional().describe('MCP resource URI to read'),
112
+ prompt_name: tool.schema.string().optional().describe('MCP prompt to get'),
113
+ arguments: tool.schema.string().optional().describe('JSON string of arguments'),
114
+ grep: tool.schema.string().optional().describe('Regex pattern to filter output lines (only matching lines returned)')
115
+ },
116
+ async execute(args) {
117
+ const operation = validateOperationParams(args);
118
+ const skills = getLoadedSkills();
119
+ const found = findMcpServer(args.mcp_name, skills);
120
+ if (!found) {
121
+ throw new Error(`MCP server "${args.mcp_name}" not found.\n\n` +
122
+ `Available MCP servers in loaded skills:\n` +
123
+ formatAvailableMcps(skills) + '\n\n' +
124
+ `Hint: Load the skill first using the 'skill' tool, then call skill_mcp.`);
125
+ }
126
+ const info = {
127
+ serverName: args.mcp_name,
128
+ skillName: found.skill.name,
129
+ sessionID: getSessionID()
130
+ };
131
+ const context = {
132
+ config: found.config,
133
+ skillName: found.skill.name
134
+ };
135
+ const parsedArgs = parseArguments(args.arguments);
136
+ let output;
137
+ switch (operation.type) {
138
+ case 'tool': {
139
+ const result = await manager.callTool(info, context, operation.name, parsedArgs);
140
+ output = JSON.stringify(result, null, 2);
141
+ break;
142
+ }
143
+ case 'resource': {
144
+ const result = await manager.readResource(info, context, operation.name);
145
+ output = JSON.stringify(result, null, 2);
146
+ break;
147
+ }
148
+ case 'prompt': {
149
+ const stringArgs = {};
150
+ for (const [key, value] of Object.entries(parsedArgs)) {
151
+ stringArgs[key] = String(value);
152
+ }
153
+ const result = await manager.getPrompt(info, context, operation.name, stringArgs);
154
+ output = JSON.stringify(result, null, 2);
155
+ break;
156
+ }
157
+ }
158
+ return applyGrepFilter(output, args.grep);
159
+ }
160
+ });
161
+ }
162
+ //# sourceMappingURL=skill-mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-mcp.js","sourceRoot":"","sources":["../../src/tools/skill-mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ import type { LoadedSkill } from '../types.js';
3
+ import type { SkillMcpManager } from '../skill-mcp-manager.js';
4
+ export interface CreateSkillToolOptions {
5
+ skills: LoadedSkill[];
6
+ mcpManager?: SkillMcpManager;
7
+ getSessionID?: () => string;
8
+ }
9
+ /**
10
+ * Create the skill tool
11
+ */
12
+ export declare function createSkillTool(options: CreateSkillToolOptions): ToolDefinition;
13
+ //# sourceMappingURL=skill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/tools/skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAEpE,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,aAAa,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAqK9D,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,YAAY,CAAC,EAAE,MAAM,MAAM,CAAA;CAC5B;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,cAAc,CAgD/E"}
@@ -0,0 +1,160 @@
1
+ import { tool } from '@opencode-ai/plugin/tool';
2
+ import { dirname } from 'path';
3
+ import { parseFrontmatter } from '../utils/frontmatter.js';
4
+ const TOOL_DESCRIPTION_NO_SKILLS = 'Load a skill to get detailed instructions for a specific task. No skills are currently available.';
5
+ const TOOL_DESCRIPTION_PREFIX = `Load a skill to get detailed instructions for a specific task.
6
+
7
+ Skills provide specialized knowledge and step-by-step guidance.
8
+ Use this when a task matches an available skill's description.`;
9
+ /**
10
+ * Convert loaded skill to info for display
11
+ */
12
+ function loadedSkillToInfo(skill) {
13
+ return {
14
+ name: skill.name,
15
+ description: skill.definition.description || '',
16
+ scope: skill.scope
17
+ };
18
+ }
19
+ /**
20
+ * Format skills as XML for tool description
21
+ */
22
+ function formatSkillsXml(skills) {
23
+ if (skills.length === 0)
24
+ return '';
25
+ const skillsXml = skills.map((skill) => {
26
+ return [
27
+ ' <skill>',
28
+ ` <name>${skill.name}</name>`,
29
+ ` <description>${skill.description}</description>`,
30
+ ' </skill>'
31
+ ].join('\n');
32
+ }).join('\n');
33
+ return `
34
+
35
+ <available_skills>
36
+ ${skillsXml}
37
+ </available_skills>`;
38
+ }
39
+ /**
40
+ * Extract skill body content
41
+ */
42
+ async function extractSkillBody(skill) {
43
+ if (skill.lazyContent) {
44
+ const fullTemplate = await skill.lazyContent.load();
45
+ const templateMatch = fullTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
46
+ return templateMatch ? templateMatch[1].trim() : fullTemplate;
47
+ }
48
+ if (skill.path) {
49
+ const { readFileSync } = await import('fs');
50
+ const content = readFileSync(skill.path, 'utf-8');
51
+ const { body } = parseFrontmatter(content);
52
+ return body.trim();
53
+ }
54
+ const templateMatch = skill.definition.template?.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
55
+ return templateMatch ? templateMatch[1].trim() : skill.definition.template || '';
56
+ }
57
+ /**
58
+ * Format MCP capabilities for display
59
+ */
60
+ async function formatMcpCapabilities(skill, manager, sessionID) {
61
+ if (!skill.mcpConfig || Object.keys(skill.mcpConfig).length === 0) {
62
+ return null;
63
+ }
64
+ const sections = ['', '## Available MCP Servers', ''];
65
+ for (const [serverName, config] of Object.entries(skill.mcpConfig)) {
66
+ const info = {
67
+ serverName,
68
+ skillName: skill.name,
69
+ sessionID
70
+ };
71
+ const context = {
72
+ config: config,
73
+ skillName: skill.name
74
+ };
75
+ sections.push(`### ${serverName}`);
76
+ sections.push('');
77
+ try {
78
+ const [tools, resources, prompts] = await Promise.all([
79
+ manager.listTools(info, context).catch(() => []),
80
+ manager.listResources(info, context).catch(() => []),
81
+ manager.listPrompts(info, context).catch(() => [])
82
+ ]);
83
+ if (tools.length > 0) {
84
+ sections.push('**Tools:**');
85
+ sections.push('');
86
+ for (const t of tools) {
87
+ sections.push(`#### \`${t.name}\``);
88
+ if (t.description) {
89
+ sections.push(t.description);
90
+ }
91
+ sections.push('');
92
+ sections.push('**inputSchema:**');
93
+ sections.push('```json');
94
+ sections.push(JSON.stringify(t.inputSchema, null, 2));
95
+ sections.push('```');
96
+ sections.push('');
97
+ }
98
+ }
99
+ if (resources.length > 0) {
100
+ sections.push(`**Resources**: ${resources.map((r) => r.uri).join(', ')}`);
101
+ }
102
+ if (prompts.length > 0) {
103
+ sections.push(`**Prompts**: ${prompts.map((p) => p.name).join(', ')}`);
104
+ }
105
+ if (tools.length === 0 && resources.length === 0 && prompts.length === 0) {
106
+ sections.push('*No capabilities discovered*');
107
+ }
108
+ }
109
+ catch (error) {
110
+ const errorMessage = error instanceof Error ? error.message : String(error);
111
+ sections.push(`*Failed to connect: ${errorMessage.split('\n')[0]}*`);
112
+ }
113
+ sections.push('');
114
+ sections.push(`Use \`skill_mcp\` tool with \`mcp_name="${serverName}"\` to invoke.`);
115
+ sections.push('');
116
+ }
117
+ return sections.join('\n');
118
+ }
119
+ /**
120
+ * Create the skill tool
121
+ */
122
+ export function createSkillTool(options) {
123
+ const { skills, mcpManager, getSessionID } = options;
124
+ // Build description with available skills
125
+ const skillInfos = skills.map(loadedSkillToInfo);
126
+ const description = skillInfos.length === 0
127
+ ? TOOL_DESCRIPTION_NO_SKILLS
128
+ : TOOL_DESCRIPTION_PREFIX + formatSkillsXml(skillInfos);
129
+ return tool({
130
+ description,
131
+ args: {
132
+ name: tool.schema.string().describe("The skill identifier from available_skills (e.g., 'code-review' or 'my-skill')")
133
+ },
134
+ async execute(args) {
135
+ const skill = skills.find((s) => s.name === args.name);
136
+ if (!skill) {
137
+ const available = skills.map((s) => s.name).join(', ');
138
+ throw new Error(`Skill "${args.name}" not found. Available skills: ${available || 'none'}`);
139
+ }
140
+ const body = await extractSkillBody(skill);
141
+ const dir = skill.path ? dirname(skill.path) : skill.resolvedPath || process.cwd();
142
+ const output = [
143
+ `## Skill: ${skill.name}`,
144
+ '',
145
+ `**Base directory**: ${dir}`,
146
+ '',
147
+ body
148
+ ];
149
+ // Add MCP capabilities if manager available
150
+ if (mcpManager && getSessionID && skill.mcpConfig) {
151
+ const mcpInfo = await formatMcpCapabilities(skill, mcpManager, getSessionID());
152
+ if (mcpInfo) {
153
+ output.push(mcpInfo);
154
+ }
155
+ }
156
+ return output.join('\n');
157
+ }
158
+ });
159
+ }
160
+ //# sourceMappingURL=skill.js.map