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.
- package/README.md +81 -3
- package/dist/__tests__/normalize-command.test.d.ts +2 -0
- package/dist/__tests__/normalize-command.test.d.ts.map +1 -0
- package/dist/__tests__/normalize-command.test.js +187 -0
- package/dist/__tests__/normalize-command.test.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/skill-loader.d.ts +27 -0
- package/dist/skill-loader.d.ts.map +1 -0
- package/dist/skill-loader.js +197 -0
- package/dist/skill-loader.js.map +1 -0
- package/dist/skill-mcp-manager.d.ts +26 -0
- package/dist/skill-mcp-manager.d.ts.map +1 -0
- package/dist/skill-mcp-manager.js +279 -0
- package/dist/skill-mcp-manager.js.map +1 -0
- package/dist/tools/skill-mcp.d.ts +13 -0
- package/dist/tools/skill-mcp.d.ts.map +1 -0
- package/dist/tools/skill-mcp.js +162 -0
- package/dist/tools/skill-mcp.js.map +1 -0
- package/dist/tools/skill.d.ts +13 -0
- package/dist/tools/skill.d.ts.map +1 -0
- package/dist/tools/skill.js +160 -0
- package/dist/tools/skill.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/env-vars.d.ts +18 -0
- package/dist/utils/env-vars.d.ts.map +1 -0
- package/dist/utils/env-vars.js +100 -0
- package/dist/utils/env-vars.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +10 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +44 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/package.json +18 -5
package/README.md
CHANGED
|
@@ -48,6 +48,22 @@ Or install it locally:
|
|
|
48
48
|
}
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
This repo includes a working example skill. After installing the plugin, try:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
skill(name="playwright-example")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then use the embedded MCP:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
skill_mcp(mcp_name="playwright", tool_name="browser_navigate", arguments='{"url": "https://example.com"}')
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
See [`.opencode/skill/playwright-example/SKILL.md`](.opencode/skill/playwright-example/SKILL.md) for the full example.
|
|
66
|
+
|
|
51
67
|
## Usage
|
|
52
68
|
|
|
53
69
|
### 1. Create a Skill with Embedded MCP
|
|
@@ -127,15 +143,77 @@ Invokes an operation on a skill-embedded MCP server.
|
|
|
127
143
|
|
|
128
144
|
## Configuration Format
|
|
129
145
|
|
|
130
|
-
The MCP configuration supports
|
|
146
|
+
The MCP configuration supports multiple formats for compatibility with both OpenCode and oh-my-opencode:
|
|
131
147
|
|
|
132
148
|
```typescript
|
|
133
149
|
interface McpServerConfig {
|
|
134
|
-
|
|
135
|
-
|
|
150
|
+
// Command formats (both supported):
|
|
151
|
+
command: string | string[] // Array: ["npx", "-y", "@some/mcp"] or String: "npx"
|
|
152
|
+
args?: string[] // Used with string command: ["-y", "@some/mcp"]
|
|
153
|
+
|
|
154
|
+
// Environment variable formats (both supported):
|
|
155
|
+
env?: Record<string, string> | string[] // Object: { "KEY": "val" } or Array: ["KEY=val"]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Examples
|
|
160
|
+
|
|
161
|
+
**Object format for env (recommended):**
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"my-server": {
|
|
165
|
+
"command": "npx",
|
|
166
|
+
"args": ["-y", "@some/mcp-server"],
|
|
167
|
+
"env": {
|
|
168
|
+
"API_KEY": "${MY_API_KEY}",
|
|
169
|
+
"DEBUG": "true"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Array format for env (OpenCode style):**
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"my-server": {
|
|
179
|
+
"command": ["npx", "-y", "@some/mcp-server"],
|
|
180
|
+
"env": ["API_KEY=${MY_API_KEY}", "DEBUG=true"]
|
|
181
|
+
}
|
|
136
182
|
}
|
|
137
183
|
```
|
|
138
184
|
|
|
185
|
+
## Example Skill
|
|
186
|
+
|
|
187
|
+
Here's a complete example of a skill with an embedded MCP server (from [`.opencode/skill/playwright-example/SKILL.md`](.opencode/skill/playwright-example/SKILL.md)):
|
|
188
|
+
|
|
189
|
+
```markdown
|
|
190
|
+
---
|
|
191
|
+
name: playwright-example
|
|
192
|
+
description: Browser automation skill for web testing, scraping, and interaction. Use for end-to-end testing, screenshots, and browser automation tasks.
|
|
193
|
+
argument-hint: describe what you want to do (e.g., "take a screenshot of homepage", "test login flow", "fill out a form")
|
|
194
|
+
mcp:
|
|
195
|
+
playwright:
|
|
196
|
+
command: ["npx", "-y", "@playwright/mcp@latest"]
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
# Playwright Browser Automation
|
|
200
|
+
|
|
201
|
+
This skill provides browser automation capabilities via the Playwright MCP server.
|
|
202
|
+
|
|
203
|
+
## Available Operations
|
|
204
|
+
|
|
205
|
+
- **Navigation**: Navigate to URLs, go back/forward, reload pages
|
|
206
|
+
- **Screenshots**: Capture full page or element screenshots
|
|
207
|
+
- **Interactions**: Click, type, select, hover, and other user interactions
|
|
208
|
+
- **Forms**: Fill out forms, submit data, handle file uploads
|
|
209
|
+
|
|
210
|
+
## Example Tasks
|
|
211
|
+
|
|
212
|
+
- "Navigate to the login page and take a screenshot"
|
|
213
|
+
- "Fill out the registration form with test data"
|
|
214
|
+
- "Extract all product names from the catalog page"
|
|
215
|
+
```
|
|
216
|
+
|
|
139
217
|
## License
|
|
140
218
|
|
|
141
219
|
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-command.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/normalize-command.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { normalizeCommand, normalizeEnv } from '../utils/env-vars.js';
|
|
3
|
+
describe('normalizeCommand', () => {
|
|
4
|
+
describe('OpenCode format (command as array)', () => {
|
|
5
|
+
it('parses command array with executable and args', () => {
|
|
6
|
+
const config = {
|
|
7
|
+
command: ['npx', '-y', '@playwright/mcp@latest']
|
|
8
|
+
};
|
|
9
|
+
const result = normalizeCommand(config);
|
|
10
|
+
expect(result.command).toBe('npx');
|
|
11
|
+
expect(result.args).toEqual(['-y', '@playwright/mcp@latest']);
|
|
12
|
+
});
|
|
13
|
+
it('handles command array with only executable', () => {
|
|
14
|
+
const config = {
|
|
15
|
+
command: ['node']
|
|
16
|
+
};
|
|
17
|
+
const result = normalizeCommand(config);
|
|
18
|
+
expect(result.command).toBe('node');
|
|
19
|
+
expect(result.args).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
it('converts non-string array elements to strings', () => {
|
|
22
|
+
const config = {
|
|
23
|
+
command: ['node', '--port', 3000]
|
|
24
|
+
};
|
|
25
|
+
const result = normalizeCommand(config);
|
|
26
|
+
expect(result.command).toBe('node');
|
|
27
|
+
expect(result.args).toEqual(['--port', '3000']);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('oh-my-opencode format (command string + args array)', () => {
|
|
31
|
+
it('parses command string with args array', () => {
|
|
32
|
+
const config = {
|
|
33
|
+
command: 'npx',
|
|
34
|
+
args: ['-y', '@anthropic-ai/mcp-playwright']
|
|
35
|
+
};
|
|
36
|
+
const result = normalizeCommand(config);
|
|
37
|
+
expect(result.command).toBe('npx');
|
|
38
|
+
expect(result.args).toEqual(['-y', '@anthropic-ai/mcp-playwright']);
|
|
39
|
+
});
|
|
40
|
+
it('handles command string without args', () => {
|
|
41
|
+
const config = {
|
|
42
|
+
command: 'node'
|
|
43
|
+
};
|
|
44
|
+
const result = normalizeCommand(config);
|
|
45
|
+
expect(result.command).toBe('node');
|
|
46
|
+
expect(result.args).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
it('handles command string with empty args array', () => {
|
|
49
|
+
const config = {
|
|
50
|
+
command: 'python',
|
|
51
|
+
args: []
|
|
52
|
+
};
|
|
53
|
+
const result = normalizeCommand(config);
|
|
54
|
+
expect(result.command).toBe('python');
|
|
55
|
+
expect(result.args).toEqual([]);
|
|
56
|
+
});
|
|
57
|
+
it('converts non-string args to strings', () => {
|
|
58
|
+
const config = {
|
|
59
|
+
command: 'node',
|
|
60
|
+
args: ['--port', 8080]
|
|
61
|
+
};
|
|
62
|
+
const result = normalizeCommand(config);
|
|
63
|
+
expect(result.command).toBe('node');
|
|
64
|
+
expect(result.args).toEqual(['--port', '8080']);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('edge cases', () => {
|
|
68
|
+
it('throws error when command is undefined', () => {
|
|
69
|
+
const config = {};
|
|
70
|
+
expect(() => normalizeCommand(config)).toThrow('Invalid MCP command configuration: command must be a string or array');
|
|
71
|
+
});
|
|
72
|
+
it('throws error when command is empty array', () => {
|
|
73
|
+
const config = {
|
|
74
|
+
command: []
|
|
75
|
+
};
|
|
76
|
+
expect(() => normalizeCommand(config)).toThrow('Invalid MCP command configuration: command array must not be empty');
|
|
77
|
+
});
|
|
78
|
+
it('ignores args field when command is array (OpenCode format takes precedence)', () => {
|
|
79
|
+
const config = {
|
|
80
|
+
command: ['npx', '-y', '@some/package'],
|
|
81
|
+
args: ['should', 'be', 'ignored']
|
|
82
|
+
};
|
|
83
|
+
const result = normalizeCommand(config);
|
|
84
|
+
expect(result.command).toBe('npx');
|
|
85
|
+
expect(result.args).toEqual(['-y', '@some/package']);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe('normalizeEnv', () => {
|
|
90
|
+
describe('object format (oh-my-opencode style)', () => {
|
|
91
|
+
it('passes through object format unchanged', () => {
|
|
92
|
+
const config = {
|
|
93
|
+
command: 'node',
|
|
94
|
+
env: { API_KEY: 'secret', DEBUG: 'true' }
|
|
95
|
+
};
|
|
96
|
+
const result = normalizeEnv(config);
|
|
97
|
+
expect(result.env).toEqual({ API_KEY: 'secret', DEBUG: 'true' });
|
|
98
|
+
});
|
|
99
|
+
it('handles empty object', () => {
|
|
100
|
+
const config = {
|
|
101
|
+
command: 'node',
|
|
102
|
+
env: {}
|
|
103
|
+
};
|
|
104
|
+
const result = normalizeEnv(config);
|
|
105
|
+
expect(result.env).toEqual({});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('array format (OpenCode style)', () => {
|
|
109
|
+
it('converts array of KEY=value strings to object', () => {
|
|
110
|
+
const config = {
|
|
111
|
+
command: 'node',
|
|
112
|
+
env: ['API_KEY=secret', 'DEBUG=true']
|
|
113
|
+
};
|
|
114
|
+
const result = normalizeEnv(config);
|
|
115
|
+
expect(result.env).toEqual({ API_KEY: 'secret', DEBUG: 'true' });
|
|
116
|
+
});
|
|
117
|
+
it('handles values containing equals sign', () => {
|
|
118
|
+
const config = {
|
|
119
|
+
command: 'node',
|
|
120
|
+
env: ['CONNECTION_STRING=host=localhost;port=5432']
|
|
121
|
+
};
|
|
122
|
+
const result = normalizeEnv(config);
|
|
123
|
+
expect(result.env).toEqual({ CONNECTION_STRING: 'host=localhost;port=5432' });
|
|
124
|
+
});
|
|
125
|
+
it('handles empty array', () => {
|
|
126
|
+
const config = {
|
|
127
|
+
command: 'node',
|
|
128
|
+
env: []
|
|
129
|
+
};
|
|
130
|
+
const result = normalizeEnv(config);
|
|
131
|
+
expect(result.env).toEqual({});
|
|
132
|
+
});
|
|
133
|
+
it('skips malformed entries without equals sign', () => {
|
|
134
|
+
const config = {
|
|
135
|
+
command: 'node',
|
|
136
|
+
env: ['VALID=value', 'INVALID_NO_EQUALS', 'ALSO_VALID=123']
|
|
137
|
+
};
|
|
138
|
+
const result = normalizeEnv(config);
|
|
139
|
+
expect(result.env).toEqual({ VALID: 'value', ALSO_VALID: '123' });
|
|
140
|
+
});
|
|
141
|
+
it('skips entries with empty key (equals at start)', () => {
|
|
142
|
+
const config = {
|
|
143
|
+
command: 'node',
|
|
144
|
+
env: ['=value_with_empty_key', 'VALID=value']
|
|
145
|
+
};
|
|
146
|
+
const result = normalizeEnv(config);
|
|
147
|
+
expect(result.env).toEqual({ VALID: 'value' });
|
|
148
|
+
});
|
|
149
|
+
it('handles empty value after equals sign', () => {
|
|
150
|
+
const config = {
|
|
151
|
+
command: 'node',
|
|
152
|
+
env: ['EMPTY_VALUE=']
|
|
153
|
+
};
|
|
154
|
+
const result = normalizeEnv(config);
|
|
155
|
+
expect(result.env).toEqual({ EMPTY_VALUE: '' });
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('edge cases', () => {
|
|
159
|
+
it('returns empty object when env is undefined', () => {
|
|
160
|
+
const config = {
|
|
161
|
+
command: 'node'
|
|
162
|
+
};
|
|
163
|
+
const result = normalizeEnv(config);
|
|
164
|
+
expect(result.env).toEqual({});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe('backward compatibility', () => {
|
|
168
|
+
it('supports deprecated environment field', () => {
|
|
169
|
+
const config = {
|
|
170
|
+
command: 'node',
|
|
171
|
+
environment: { LEGACY_KEY: 'legacy_value' }
|
|
172
|
+
};
|
|
173
|
+
const result = normalizeEnv(config);
|
|
174
|
+
expect(result.env).toEqual({ LEGACY_KEY: 'legacy_value' });
|
|
175
|
+
});
|
|
176
|
+
it('prefers env over deprecated environment when both present', () => {
|
|
177
|
+
const config = {
|
|
178
|
+
command: 'node',
|
|
179
|
+
env: { NEW_KEY: 'new_value' },
|
|
180
|
+
environment: { OLD_KEY: 'old_value' }
|
|
181
|
+
};
|
|
182
|
+
const result = normalizeEnv(config);
|
|
183
|
+
expect(result.env).toEqual({ NEW_KEY: 'new_value' });
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
//# sourceMappingURL=normalize-command.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-command.test.js","sourceRoot":"","sources":["../../src/__tests__/normalize-command.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Plugin } from '@opencode-ai/plugin';
|
|
2
|
+
export declare const OpenCodeEmbeddedSkillMcp: Plugin;
|
|
3
|
+
export default OpenCodeEmbeddedSkillMcp;
|
|
4
|
+
export type { LoadedSkill, McpServerConfig, SkillScope } from './types.js';
|
|
5
|
+
export { discoverSkills } from './skill-loader.js';
|
|
6
|
+
export { createSkillMcpManager } from './skill-mcp-manager.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAejD,eAAO,MAAM,wBAAwB,EAAE,MAkDtC,CAAA;AAGD,eAAe,wBAAwB,CAAA;AAGvC,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createSkillMcpManager } from './skill-mcp-manager.js';
|
|
2
|
+
import { discoverSkills } from './skill-loader.js';
|
|
3
|
+
import { createSkillTool } from './tools/skill.js';
|
|
4
|
+
import { createSkillMcpTool } from './tools/skill-mcp.js';
|
|
5
|
+
function hasOhMyOpencode(plugins) {
|
|
6
|
+
return plugins.some(p => p === 'oh-my-opencode' ||
|
|
7
|
+
p === '@code-yeongyu/oh-my-opencode' ||
|
|
8
|
+
p.endsWith('/oh-my-opencode'));
|
|
9
|
+
}
|
|
10
|
+
export const OpenCodeEmbeddedSkillMcp = async ({ client }) => {
|
|
11
|
+
if (process.env.OPENCODE_LAZY_LOADER_FORCE !== '1') {
|
|
12
|
+
try {
|
|
13
|
+
const { data: config } = await client.config.get();
|
|
14
|
+
if (config?.plugin && hasOhMyOpencode(config.plugin)) {
|
|
15
|
+
console.log('[opencode-lazy-loader] oh-my-opencode detected in config, auto-disabling to avoid conflicts');
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const manager = createSkillMcpManager();
|
|
23
|
+
let loadedSkills = [];
|
|
24
|
+
let currentSessionID = null;
|
|
25
|
+
// Discover skills on initialization
|
|
26
|
+
try {
|
|
27
|
+
loadedSkills = await discoverSkills();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
loadedSkills = [];
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
// Handle session lifecycle events
|
|
34
|
+
event: async ({ event }) => {
|
|
35
|
+
if (event.type === 'session.created') {
|
|
36
|
+
currentSessionID = event.properties.info.id;
|
|
37
|
+
}
|
|
38
|
+
if (event.type === 'session.deleted' && currentSessionID) {
|
|
39
|
+
// Cleanup MCP connections for the deleted session
|
|
40
|
+
await manager.disconnectSession(currentSessionID);
|
|
41
|
+
currentSessionID = null;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
// Register tools
|
|
45
|
+
tool: {
|
|
46
|
+
skill: createSkillTool({
|
|
47
|
+
skills: loadedSkills,
|
|
48
|
+
mcpManager: manager,
|
|
49
|
+
getSessionID: () => currentSessionID || 'unknown'
|
|
50
|
+
}),
|
|
51
|
+
skill_mcp: createSkillMcpTool({
|
|
52
|
+
manager,
|
|
53
|
+
getLoadedSkills: () => loadedSkills,
|
|
54
|
+
getSessionID: () => currentSessionID || 'unknown'
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
// Default export for plugin loading
|
|
60
|
+
export default OpenCodeEmbeddedSkillMcp;
|
|
61
|
+
export { discoverSkills } from './skill-loader.js';
|
|
62
|
+
export { createSkillMcpManager } from './skill-mcp-manager.js';
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAGzD,SAAS,eAAe,CAAC,OAAiB;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CACtB,CAAC,KAAK,gBAAgB;QACtB,CAAC,KAAK,8BAA8B;QACpC,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAC9B,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,wBAAwB,GAAW,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;IACnE,IAAI,OAAO,CAAC,GAAG,CAAC,0BAA0B,KAAK,GAAG,EAAE,CAAC;QACnD,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAA;YAClD,IAAI,MAAM,EAAE,MAAM,IAAI,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBACrD,OAAO,CAAC,GAAG,CAAC,6FAA6F,CAAC,CAAA;gBAC1G,OAAO,EAAE,CAAA;YACX,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;QACT,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,qBAAqB,EAAE,CAAA;IACvC,IAAI,YAAY,GAAkB,EAAE,CAAA;IACpC,IAAI,gBAAgB,GAAkB,IAAI,CAAA;IAE1C,oCAAoC;IACpC,IAAI,CAAC;QACH,YAAY,GAAG,MAAM,cAAc,EAAE,CAAA;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,YAAY,GAAG,EAAE,CAAA;IACnB,CAAC;IAED,OAAO;QACL,kCAAkC;QAClC,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YACzB,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBACrC,gBAAgB,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAA;YAC7C,CAAC;YAED,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,IAAI,gBAAgB,EAAE,CAAC;gBACzD,kDAAkD;gBAClD,MAAM,OAAO,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAA;gBACjD,gBAAgB,GAAG,IAAI,CAAA;YACzB,CAAC;QACH,CAAC;QAED,iBAAiB;QACjB,IAAI,EAAE;YACJ,KAAK,EAAE,eAAe,CAAC;gBACrB,MAAM,EAAE,YAAY;gBACpB,UAAU,EAAE,OAAO;gBACnB,YAAY,EAAE,GAAG,EAAE,CAAC,gBAAgB,IAAI,SAAS;aAClD,CAAC;YACF,SAAS,EAAE,kBAAkB,CAAC;gBAC5B,OAAO;gBACP,eAAe,EAAE,GAAG,EAAE,CAAC,YAAY;gBACnC,YAAY,EAAE,GAAG,EAAE,CAAC,gBAAgB,IAAI,SAAS;aAClD,CAAC;SACH;KACF,CAAA;AACH,CAAC,CAAA;AAED,oCAAoC;AACpC,eAAe,wBAAwB,CAAA;AAIvC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LoadedSkill, McpServerConfig, SkillScope } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load MCP config from mcp.json file in skill directory
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadMcpJsonFromDir(skillDir: string): Promise<Record<string, McpServerConfig> | undefined>;
|
|
6
|
+
/**
|
|
7
|
+
* Load a skill from a markdown file path
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadSkillFromPath(skillPath: string, resolvedPath: string, defaultName: string, scope: SkillScope): Promise<LoadedSkill | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Load all skills from a directory
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadSkillsFromDir(skillsDir: string, scope: SkillScope): Promise<LoadedSkill[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Discover skills from opencode global directory (~/.config/opencode/skill/)
|
|
16
|
+
*/
|
|
17
|
+
export declare function discoverOpencodeGlobalSkills(): Promise<LoadedSkill[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Discover skills from opencode project directory (.opencode/skill/)
|
|
20
|
+
*/
|
|
21
|
+
export declare function discoverOpencodeProjectSkills(): Promise<LoadedSkill[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Discover all skills from both opencode locations
|
|
24
|
+
* Priority: project > global
|
|
25
|
+
*/
|
|
26
|
+
export declare function discoverSkills(): Promise<LoadedSkill[]>;
|
|
27
|
+
//# sourceMappingURL=skill-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-loader.d.ts","sourceRoot":"","sources":["../src/skill-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAe,MAAM,YAAY,CAAA;AAsBvF;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,CAAC,CA+BtD;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAsD7B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,WAAW,EAAE,CAAC,CAyDxB;AAED;;GAEG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAG3E;AAED;;GAEG;AACH,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAG5E;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAoB7D"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join, basename } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { parseFrontmatter, parseSkillMcpConfigFromFrontmatter } from './utils/frontmatter.js';
|
|
5
|
+
/**
|
|
6
|
+
* Check if a file is a markdown file
|
|
7
|
+
*/
|
|
8
|
+
function isMarkdownFile(entry) {
|
|
9
|
+
return entry.name.endsWith('.md');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve symlink to its target path
|
|
13
|
+
*/
|
|
14
|
+
async function resolveSymlinkAsync(entryPath) {
|
|
15
|
+
try {
|
|
16
|
+
const realPath = await fs.realpath(entryPath);
|
|
17
|
+
return realPath;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return entryPath;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Load MCP config from mcp.json file in skill directory
|
|
25
|
+
*/
|
|
26
|
+
export async function loadMcpJsonFromDir(skillDir) {
|
|
27
|
+
const mcpJsonPath = join(skillDir, 'mcp.json');
|
|
28
|
+
try {
|
|
29
|
+
const content = await fs.readFile(mcpJsonPath, 'utf-8');
|
|
30
|
+
const parsed = JSON.parse(content);
|
|
31
|
+
// Support { mcpServers: { ... } } format
|
|
32
|
+
if (parsed && typeof parsed === 'object' && 'mcpServers' in parsed && parsed.mcpServers) {
|
|
33
|
+
return parsed.mcpServers;
|
|
34
|
+
}
|
|
35
|
+
// Support { mcp: { ... } } format (OpenCode config style)
|
|
36
|
+
if (parsed && typeof parsed === 'object' && 'mcp' in parsed && parsed.mcp) {
|
|
37
|
+
return parsed.mcp;
|
|
38
|
+
}
|
|
39
|
+
// Support direct { serverName: { command: ... } } format
|
|
40
|
+
if (parsed && typeof parsed === 'object' && !('mcpServers' in parsed) && !('mcp' in parsed)) {
|
|
41
|
+
const hasCommandField = Object.values(parsed).some((v) => v && typeof v === 'object' && 'command' in v);
|
|
42
|
+
if (hasCommandField) {
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Load a skill from a markdown file path
|
|
54
|
+
*/
|
|
55
|
+
export async function loadSkillFromPath(skillPath, resolvedPath, defaultName, scope) {
|
|
56
|
+
try {
|
|
57
|
+
const content = await fs.readFile(skillPath, 'utf-8');
|
|
58
|
+
const { data } = parseFrontmatter(content);
|
|
59
|
+
// Load MCP config from frontmatter or mcp.json
|
|
60
|
+
const frontmatterMcp = parseSkillMcpConfigFromFrontmatter(content);
|
|
61
|
+
const mcpJsonMcp = await loadMcpJsonFromDir(resolvedPath);
|
|
62
|
+
const mcpConfig = mcpJsonMcp || frontmatterMcp; // mcp.json takes priority
|
|
63
|
+
const skillName = data.name || defaultName;
|
|
64
|
+
const originalDescription = data.description || '';
|
|
65
|
+
const formattedDescription = `(${scope} - Skill) ${originalDescription}`;
|
|
66
|
+
// Create lazy content loader
|
|
67
|
+
const lazyContent = {
|
|
68
|
+
loaded: false,
|
|
69
|
+
content: undefined,
|
|
70
|
+
load: async () => {
|
|
71
|
+
if (!lazyContent.loaded) {
|
|
72
|
+
const fileContent = await fs.readFile(skillPath, 'utf-8');
|
|
73
|
+
const { body } = parseFrontmatter(fileContent);
|
|
74
|
+
lazyContent.content = `<skill-instruction>
|
|
75
|
+
Base directory for this skill: ${resolvedPath}/
|
|
76
|
+
File references (@path) in this skill are relative to this directory.
|
|
77
|
+
|
|
78
|
+
${body.trim()}
|
|
79
|
+
</skill-instruction>
|
|
80
|
+
|
|
81
|
+
<user-request>
|
|
82
|
+
$ARGUMENTS
|
|
83
|
+
</user-request>`;
|
|
84
|
+
lazyContent.loaded = true;
|
|
85
|
+
}
|
|
86
|
+
return lazyContent.content;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
name: skillName,
|
|
91
|
+
path: skillPath,
|
|
92
|
+
resolvedPath,
|
|
93
|
+
definition: {
|
|
94
|
+
name: skillName,
|
|
95
|
+
description: formattedDescription,
|
|
96
|
+
template: ''
|
|
97
|
+
},
|
|
98
|
+
scope,
|
|
99
|
+
mcpConfig,
|
|
100
|
+
lazyContent
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Load all skills from a directory
|
|
109
|
+
*/
|
|
110
|
+
export async function loadSkillsFromDir(skillsDir, scope) {
|
|
111
|
+
const entries = await fs.readdir(skillsDir, { withFileTypes: true }).catch(() => []);
|
|
112
|
+
const skills = [];
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
// Skip hidden files
|
|
115
|
+
if (entry.name.startsWith('.')) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const entryPath = join(skillsDir, entry.name);
|
|
119
|
+
// Handle directories (skill folders)
|
|
120
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
121
|
+
const resolvedPath = await resolveSymlinkAsync(entryPath);
|
|
122
|
+
const dirName = entry.name;
|
|
123
|
+
// Try SKILL.md first
|
|
124
|
+
const skillMdPath = join(resolvedPath, 'SKILL.md');
|
|
125
|
+
try {
|
|
126
|
+
await fs.access(skillMdPath);
|
|
127
|
+
const skill = await loadSkillFromPath(skillMdPath, resolvedPath, dirName, scope);
|
|
128
|
+
if (skill) {
|
|
129
|
+
skills.push(skill);
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// SKILL.md not found, try {dirname}.md
|
|
135
|
+
}
|
|
136
|
+
// Try {dirname}.md
|
|
137
|
+
const namedSkillMdPath = join(resolvedPath, `${dirName}.md`);
|
|
138
|
+
try {
|
|
139
|
+
await fs.access(namedSkillMdPath);
|
|
140
|
+
const skill = await loadSkillFromPath(namedSkillMdPath, resolvedPath, dirName, scope);
|
|
141
|
+
if (skill) {
|
|
142
|
+
skills.push(skill);
|
|
143
|
+
}
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Named skill file not found
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// Handle standalone markdown files
|
|
152
|
+
if (isMarkdownFile(entry)) {
|
|
153
|
+
const skillName = basename(entry.name, '.md');
|
|
154
|
+
const skill = await loadSkillFromPath(entryPath, skillsDir, skillName, scope);
|
|
155
|
+
if (skill) {
|
|
156
|
+
skills.push(skill);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return skills;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Discover skills from opencode global directory (~/.config/opencode/skill/)
|
|
164
|
+
*/
|
|
165
|
+
export async function discoverOpencodeGlobalSkills() {
|
|
166
|
+
const opencodeSkillsDir = join(homedir(), '.config', 'opencode', 'skill');
|
|
167
|
+
return loadSkillsFromDir(opencodeSkillsDir, 'opencode');
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Discover skills from opencode project directory (.opencode/skill/)
|
|
171
|
+
*/
|
|
172
|
+
export async function discoverOpencodeProjectSkills() {
|
|
173
|
+
const opencodeProjectDir = join(process.cwd(), '.opencode', 'skill');
|
|
174
|
+
return loadSkillsFromDir(opencodeProjectDir, 'opencode-project');
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Discover all skills from both opencode locations
|
|
178
|
+
* Priority: project > global
|
|
179
|
+
*/
|
|
180
|
+
export async function discoverSkills() {
|
|
181
|
+
const [projectSkills, globalSkills] = await Promise.all([
|
|
182
|
+
discoverOpencodeProjectSkills(),
|
|
183
|
+
discoverOpencodeGlobalSkills()
|
|
184
|
+
]);
|
|
185
|
+
// Project skills take priority - dedupe by name
|
|
186
|
+
const skillMap = new Map();
|
|
187
|
+
// Add global skills first
|
|
188
|
+
for (const skill of globalSkills) {
|
|
189
|
+
skillMap.set(skill.name, skill);
|
|
190
|
+
}
|
|
191
|
+
// Project skills override global
|
|
192
|
+
for (const skill of projectSkills) {
|
|
193
|
+
skillMap.set(skill.name, skill);
|
|
194
|
+
}
|
|
195
|
+
return Array.from(skillMap.values());
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=skill-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-loader.js","sourceRoot":"","sources":["../src/skill-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAA;AACnC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAA;AAE5B,OAAO,EAAE,gBAAgB,EAAE,kCAAkC,EAAE,MAAM,wBAAwB,CAAA;AAE7F;;GAEG;AACH,SAAS,cAAc,CAAC,KAAuB;IAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AACnC,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,mBAAmB,CAAC,SAAiB;IAClD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;QAC7C,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAAgB;IAEhB,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;IAE9C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAA;QAE7D,yCAAyC;QACzC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,YAAY,IAAI,MAAM,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACxF,OAAO,MAAM,CAAC,UAA6C,CAAA;QAC7D,CAAC;QAED,0DAA0D;QAC1D,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,IAAI,MAAM,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;YAC1E,OAAO,MAAM,CAAC,GAAsC,CAAA;QACtD,CAAC;QAED,yDAAyD;QACzD,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,EAAE,CAAC;YAC5F,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAChD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,SAAS,IAAK,CAA6B,CACjF,CAAA;YACD,IAAI,eAAe,EAAE,CAAC;gBACpB,OAAO,MAAoD,CAAA;YAC7D,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAiB,EACjB,YAAoB,EACpB,WAAmB,EACnB,KAAiB;IAEjB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QACrD,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;QAE1C,+CAA+C;QAC/C,MAAM,cAAc,GAAG,kCAAkC,CAAC,OAAO,CAAC,CAAA;QAClE,MAAM,UAAU,GAAG,MAAM,kBAAkB,CAAC,YAAY,CAAC,CAAA;QACzD,MAAM,SAAS,GAAG,UAAU,IAAI,cAAc,CAAA,CAAC,0BAA0B;QAEzE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAA;QAC1C,MAAM,mBAAmB,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAA;QAClD,MAAM,oBAAoB,GAAG,IAAI,KAAK,aAAa,mBAAmB,EAAE,CAAA;QAExE,6BAA6B;QAC7B,MAAM,WAAW,GAAgB;YAC/B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,SAAS;YAClB,IAAI,EAAE,KAAK,IAAI,EAAE;gBACf,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;oBACxB,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;oBACzD,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAA;oBAC9C,WAAW,CAAC,OAAO,GAAG;iCACC,YAAY;;;EAG3C,IAAI,CAAC,IAAI,EAAE;;;;;gBAKG,CAAA;oBACN,WAAW,CAAC,MAAM,GAAG,IAAI,CAAA;gBAC3B,CAAC;gBACD,OAAO,WAAW,CAAC,OAAQ,CAAA;YAC7B,CAAC;SACF,CAAA;QAED,OAAO;YACL,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,SAAS;YACf,YAAY;YACZ,UAAU,EAAE;gBACV,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,oBAAoB;gBACjC,QAAQ,EAAE,EAAE;aACb;YACD,KAAK;YACL,SAAS;YACT,WAAW;SACZ,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAiB,EACjB,KAAiB;IAEjB,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IACpF,MAAM,MAAM,GAAkB,EAAE,CAAA;IAEhC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,oBAAoB;QACpB,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,SAAQ;QACV,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QAE7C,qCAAqC;QACrC,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;YAClD,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,SAAS,CAAC,CAAA;YACzD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAA;YAE1B,qBAAqB;YACrB,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;YAClD,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;gBAC5B,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;gBAChF,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBACpB,CAAC;gBACD,SAAQ;YACV,CAAC;YAAC,MAAM,CAAC;gBACP,uCAAuC;YACzC,CAAC;YAED,mBAAmB;YACnB,MAAM,gBAAgB,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,OAAO,KAAK,CAAC,CAAA;YAC5D,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAA;gBACjC,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,gBAAgB,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;gBACrF,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBACpB,CAAC;gBACD,SAAQ;YACV,CAAC;YAAC,MAAM,CAAC;gBACP,6BAA6B;YAC/B,CAAC;YAED,SAAQ;QACV,CAAC;QAED,mCAAmC;QACnC,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YAC7C,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,CAAA;YAC7E,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACpB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B;IAChD,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;IACzE,OAAO,iBAAiB,CAAC,iBAAiB,EAAE,UAAU,CAAC,CAAA;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B;IACjD,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;IACpE,OAAO,iBAAiB,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAA;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,CAAC,aAAa,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACtD,6BAA6B,EAAE;QAC/B,4BAA4B,EAAE;KAC/B,CAAC,CAAA;IAEF,gDAAgD;IAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAA;IAE/C,0BAA0B;IAC1B,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACjC,CAAC;IAED,iCAAiC;IACjC,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;QAClC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACjC,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;AACtC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import type { McpClientInfo, McpContext, McpServerConfig } from './types.js';
|
|
3
|
+
export interface SkillMcpManager {
|
|
4
|
+
getOrCreateClient(info: McpClientInfo, config: McpServerConfig): Promise<Client>;
|
|
5
|
+
disconnectSession(sessionID: string): Promise<void>;
|
|
6
|
+
disconnectAll(): Promise<void>;
|
|
7
|
+
listTools(info: McpClientInfo, context: McpContext): Promise<unknown[]>;
|
|
8
|
+
listResources(info: McpClientInfo, context: McpContext): Promise<unknown[]>;
|
|
9
|
+
listPrompts(info: McpClientInfo, context: McpContext): Promise<unknown[]>;
|
|
10
|
+
callTool(info: McpClientInfo, context: McpContext, name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
11
|
+
readResource(info: McpClientInfo, context: McpContext, uri: string): Promise<unknown>;
|
|
12
|
+
getPrompt(info: McpClientInfo, context: McpContext, name: string, args: Record<string, string>): Promise<unknown>;
|
|
13
|
+
getConnectedServers(): string[];
|
|
14
|
+
isConnected(info: McpClientInfo): boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create a SkillMcpManager instance
|
|
18
|
+
*
|
|
19
|
+
* Features:
|
|
20
|
+
* - Connection pooling keyed by session/skill/server
|
|
21
|
+
* - Lazy connection creation
|
|
22
|
+
* - Idle cleanup after 5 minutes
|
|
23
|
+
* - Session/process cleanup
|
|
24
|
+
*/
|
|
25
|
+
export declare function createSkillMcpManager(): SkillMcpManager;
|
|
26
|
+
//# sourceMappingURL=skill-mcp-manager.d.ts.map
|