@operor/skills 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/dist/index.d.ts +203 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +348 -0
- package/dist/index.js.map +1 -0
- package/package.json +30 -0
- package/skill-catalog.json +465 -0
- package/src/MCPSkill.ts +154 -0
- package/src/PromptSkill.ts +38 -0
- package/src/SkillManager.ts +98 -0
- package/src/__tests__/MCPSkill.test.ts +308 -0
- package/src/__tests__/SkillManager.test.ts +172 -0
- package/src/__tests__/catalog.test.ts +383 -0
- package/src/__tests__/config.test.ts +181 -0
- package/src/catalog.ts +213 -0
- package/src/config.ts +95 -0
- package/src/index.ts +30 -0
- package/test/catalog.test.ts +221 -0
- package/test/config.test.ts +162 -0
- package/tsconfig.json +9 -0
- package/tsdown.config.ts +10 -0
- package/vitest.config.ts +3 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Skill, Tool } from "@operor/core";
|
|
2
|
+
|
|
3
|
+
//#region src/config.d.ts
|
|
4
|
+
interface MCPSkillConfig {
|
|
5
|
+
name: string;
|
|
6
|
+
transport: 'stdio' | 'http' | 'sse';
|
|
7
|
+
command?: string;
|
|
8
|
+
args?: string[];
|
|
9
|
+
env?: Record<string, string>;
|
|
10
|
+
url?: string;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
toolPrefix?: string;
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface PromptSkillConfig {
|
|
16
|
+
type: 'prompt';
|
|
17
|
+
name: string;
|
|
18
|
+
summary?: string;
|
|
19
|
+
version?: string;
|
|
20
|
+
tags?: string[];
|
|
21
|
+
author?: string;
|
|
22
|
+
content: string;
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
type AnySkillConfig = (MCPSkillConfig & {
|
|
26
|
+
type?: 'mcp';
|
|
27
|
+
}) | PromptSkillConfig;
|
|
28
|
+
declare function isPromptSkillConfig(config: AnySkillConfig): config is PromptSkillConfig;
|
|
29
|
+
declare function isMCPSkillConfig(config: AnySkillConfig): config is MCPSkillConfig & {
|
|
30
|
+
type?: 'mcp';
|
|
31
|
+
};
|
|
32
|
+
interface SkillsConfig {
|
|
33
|
+
skills: AnySkillConfig[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve `${ENV_VAR}` placeholders in a string value.
|
|
37
|
+
*/
|
|
38
|
+
declare function resolveEnvVars(value: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve env vars in all values of a record.
|
|
41
|
+
*/
|
|
42
|
+
declare function resolveEnvRecord(record: Record<string, string>): Record<string, string>;
|
|
43
|
+
/**
|
|
44
|
+
* Load skills config from `mcp.json` at the given root (defaults to cwd).
|
|
45
|
+
* Returns empty config if file doesn't exist.
|
|
46
|
+
*/
|
|
47
|
+
declare function loadSkillsConfig(root?: string): SkillsConfig;
|
|
48
|
+
/**
|
|
49
|
+
* Save skills config to `mcp.json` at the given root (defaults to cwd).
|
|
50
|
+
*/
|
|
51
|
+
declare function saveSkillsConfig(config: SkillsConfig, root?: string): void;
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/MCPSkill.d.ts
|
|
54
|
+
declare class MCPSkill implements Skill {
|
|
55
|
+
readonly name: string;
|
|
56
|
+
tools: Record<string, Tool>;
|
|
57
|
+
private client;
|
|
58
|
+
private ready;
|
|
59
|
+
private config;
|
|
60
|
+
constructor(config: MCPSkillConfig);
|
|
61
|
+
initialize(): Promise<void>;
|
|
62
|
+
isReady(): boolean;
|
|
63
|
+
close(): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Extract parameters from an AI SDK tool into JSON Schema format.
|
|
66
|
+
* The @ai-sdk/mcp client exposes the schema as `inputSchema` (a jsonSchema()
|
|
67
|
+
* wrapper with a `.jsonSchema` getter), while older/test code may use
|
|
68
|
+
* `parameters` directly.
|
|
69
|
+
*/
|
|
70
|
+
private extractParameters;
|
|
71
|
+
/**
|
|
72
|
+
* Create the appropriate transport based on config.
|
|
73
|
+
*/
|
|
74
|
+
private createTransport;
|
|
75
|
+
private createStdioTransport;
|
|
76
|
+
private createHttpTransport;
|
|
77
|
+
private createSseTransport;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/PromptSkill.d.ts
|
|
81
|
+
declare class PromptSkill implements Skill {
|
|
82
|
+
readonly name: string;
|
|
83
|
+
readonly tools: Record<string, Tool>;
|
|
84
|
+
private ready;
|
|
85
|
+
private _config;
|
|
86
|
+
constructor(config: PromptSkillConfig);
|
|
87
|
+
initialize(): Promise<void>;
|
|
88
|
+
isReady(): boolean;
|
|
89
|
+
close(): Promise<void>;
|
|
90
|
+
getContent(): string;
|
|
91
|
+
getConfig(): PromptSkillConfig;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/SkillManager.d.ts
|
|
95
|
+
declare class SkillManager {
|
|
96
|
+
private skills;
|
|
97
|
+
initialize(config: SkillsConfig): Promise<Skill[]>;
|
|
98
|
+
closeAll(): Promise<void>;
|
|
99
|
+
getSkills(): Skill[];
|
|
100
|
+
getPromptSkills(): PromptSkill[];
|
|
101
|
+
getMCPSkills(): MCPSkill[];
|
|
102
|
+
static validateConfig(config: MCPSkillConfig): string | null;
|
|
103
|
+
static validatePromptConfig(config: PromptSkillConfig): string | null;
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/catalog.d.ts
|
|
107
|
+
/** A single MCP skill entry in the catalog. */
|
|
108
|
+
interface MCPSkillCatalogEntry {
|
|
109
|
+
type?: 'mcp';
|
|
110
|
+
name: string;
|
|
111
|
+
displayName: string;
|
|
112
|
+
description: string;
|
|
113
|
+
category: SkillCategory;
|
|
114
|
+
tags: string[];
|
|
115
|
+
transport: 'stdio' | 'http' | 'sse';
|
|
116
|
+
package?: string;
|
|
117
|
+
command?: string;
|
|
118
|
+
args?: string[];
|
|
119
|
+
url?: string;
|
|
120
|
+
envVars: Record<string, EnvVarSpec>;
|
|
121
|
+
tools: SkillToolSummary[];
|
|
122
|
+
maturity: 'official' | 'community' | 'experimental';
|
|
123
|
+
vendor: string;
|
|
124
|
+
docsUrl?: string;
|
|
125
|
+
version?: string;
|
|
126
|
+
updatedAt?: string;
|
|
127
|
+
}
|
|
128
|
+
/** A prompt skill entry in the catalog — behavioral instructions, no tools. */
|
|
129
|
+
interface PromptSkillCatalogEntry {
|
|
130
|
+
type: 'prompt';
|
|
131
|
+
name: string;
|
|
132
|
+
displayName: string;
|
|
133
|
+
description: string;
|
|
134
|
+
category: SkillCategory;
|
|
135
|
+
tags: string[];
|
|
136
|
+
content: string;
|
|
137
|
+
maturity: 'official' | 'community' | 'experimental';
|
|
138
|
+
vendor: string;
|
|
139
|
+
version?: string;
|
|
140
|
+
updatedAt?: string;
|
|
141
|
+
}
|
|
142
|
+
/** A single skill entry in the catalog — the "app store" listing. */
|
|
143
|
+
type SkillCatalogEntry = MCPSkillCatalogEntry | PromptSkillCatalogEntry;
|
|
144
|
+
declare function isPromptCatalogEntry(entry: SkillCatalogEntry): entry is PromptSkillCatalogEntry;
|
|
145
|
+
type SkillCategory = 'commerce' | 'payments' | 'crm' | 'support' | 'marketing' | 'search' | 'analytics' | 'communication' | 'productivity' | 'developer' | 'other';
|
|
146
|
+
/** Describes an env var a skill needs. */
|
|
147
|
+
interface EnvVarSpec {
|
|
148
|
+
/** What this env var is for */
|
|
149
|
+
description: string;
|
|
150
|
+
/** Whether the skill cannot function without it */
|
|
151
|
+
required: boolean;
|
|
152
|
+
/** Hint for the setup wizard (e.g. "sk-..." or "https://...") */
|
|
153
|
+
placeholder?: string;
|
|
154
|
+
}
|
|
155
|
+
/** Summary of a tool exposed by a skill — for catalog display, not runtime. */
|
|
156
|
+
interface SkillToolSummary {
|
|
157
|
+
/** Tool name as exposed by the MCP server */
|
|
158
|
+
name: string;
|
|
159
|
+
/** What this tool does */
|
|
160
|
+
description: string;
|
|
161
|
+
}
|
|
162
|
+
/** The top-level catalog file structure. */
|
|
163
|
+
interface SkillCatalog {
|
|
164
|
+
/** Schema version for forward compatibility */
|
|
165
|
+
version: number;
|
|
166
|
+
/** When the catalog was last generated */
|
|
167
|
+
updatedAt: string;
|
|
168
|
+
/** The skill entries */
|
|
169
|
+
skills: SkillCatalogEntry[];
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Load the built-in skill catalog shipped with @operor/skills.
|
|
173
|
+
* Falls back to empty catalog if file is missing.
|
|
174
|
+
*/
|
|
175
|
+
declare function loadSkillCatalog(): SkillCatalog;
|
|
176
|
+
/**
|
|
177
|
+
* Load a custom/override catalog from a specific path.
|
|
178
|
+
*/
|
|
179
|
+
declare function loadSkillCatalogFrom(filePath: string): SkillCatalog;
|
|
180
|
+
/** Filter options for querying the catalog. */
|
|
181
|
+
interface CatalogFilter {
|
|
182
|
+
category?: SkillCategory;
|
|
183
|
+
maturity?: SkillCatalogEntry['maturity'];
|
|
184
|
+
search?: string;
|
|
185
|
+
tags?: string[];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Query the catalog with optional filters.
|
|
189
|
+
* Search matches against name, displayName, description, and tags.
|
|
190
|
+
*/
|
|
191
|
+
declare function querySkillCatalog(catalog: SkillCatalog, filter?: CatalogFilter): SkillCatalogEntry[];
|
|
192
|
+
/**
|
|
193
|
+
* Look up a single skill by name.
|
|
194
|
+
*/
|
|
195
|
+
declare function findSkillInCatalog(catalog: SkillCatalog, name: string): SkillCatalogEntry | undefined;
|
|
196
|
+
/**
|
|
197
|
+
* Convert a catalog entry into a config suitable for mcp.json.
|
|
198
|
+
* Handles both MCP and prompt skill entries.
|
|
199
|
+
*/
|
|
200
|
+
declare function catalogEntryToConfig(entry: SkillCatalogEntry): Record<string, any>;
|
|
201
|
+
//#endregion
|
|
202
|
+
export { type AnySkillConfig, type CatalogFilter, type EnvVarSpec, MCPSkill, type MCPSkillCatalogEntry, type MCPSkillConfig, PromptSkill, type PromptSkillCatalogEntry, type PromptSkillConfig, type SkillCatalog, type SkillCatalogEntry, type SkillCategory, SkillManager, type SkillToolSummary, type SkillsConfig, catalogEntryToConfig, findSkillInCatalog, isMCPSkillConfig, isPromptCatalogEntry, isPromptSkillConfig, loadSkillCatalog, loadSkillCatalogFrom, loadSkillsConfig, querySkillCatalog, resolveEnvRecord, resolveEnvVars, saveSkillsConfig };
|
|
203
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/config.ts","../src/MCPSkill.ts","../src/PromptSkill.ts","../src/SkillManager.ts","../src/catalog.ts"],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { createMCPClient } from "@ai-sdk/mcp";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
//#region src/config.ts
|
|
7
|
+
function isPromptSkillConfig(config) {
|
|
8
|
+
return config.type === "prompt";
|
|
9
|
+
}
|
|
10
|
+
function isMCPSkillConfig(config) {
|
|
11
|
+
return !isPromptSkillConfig(config);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve `${ENV_VAR}` placeholders in a string value.
|
|
15
|
+
*/
|
|
16
|
+
function resolveEnvVars(value) {
|
|
17
|
+
return value.replace(/\$\{([^}]+)\}/g, (_match, envVar) => {
|
|
18
|
+
return process.env[envVar] ?? "";
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve env vars in all values of a record.
|
|
23
|
+
*/
|
|
24
|
+
function resolveEnvRecord(record) {
|
|
25
|
+
const resolved = {};
|
|
26
|
+
for (const [key, value] of Object.entries(record)) resolved[key] = resolveEnvVars(value);
|
|
27
|
+
return resolved;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load skills config from `mcp.json` at the given root (defaults to cwd).
|
|
31
|
+
* Returns empty config if file doesn't exist.
|
|
32
|
+
*/
|
|
33
|
+
function loadSkillsConfig(root) {
|
|
34
|
+
const configPath = resolve(root ?? process.cwd(), "mcp.json");
|
|
35
|
+
if (!existsSync(configPath)) return { skills: [] };
|
|
36
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(raw);
|
|
40
|
+
} catch {
|
|
41
|
+
throw new Error(`Invalid JSON in ${configPath}`);
|
|
42
|
+
}
|
|
43
|
+
if (!parsed || typeof parsed !== "object") return { skills: [] };
|
|
44
|
+
return { skills: Array.isArray(parsed.skills) ? parsed.skills : [] };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Save skills config to `mcp.json` at the given root (defaults to cwd).
|
|
48
|
+
*/
|
|
49
|
+
function saveSkillsConfig(config, root) {
|
|
50
|
+
writeFileSync(resolve(root ?? process.cwd(), "mcp.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/MCPSkill.ts
|
|
55
|
+
var MCPSkill = class {
|
|
56
|
+
name;
|
|
57
|
+
tools = {};
|
|
58
|
+
client = null;
|
|
59
|
+
ready = false;
|
|
60
|
+
config;
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this.config = config;
|
|
63
|
+
this.name = config.name;
|
|
64
|
+
}
|
|
65
|
+
async initialize() {
|
|
66
|
+
if (this.ready) return;
|
|
67
|
+
this.client = await createMCPClient({ transport: await this.createTransport() });
|
|
68
|
+
const mcpTools = await this.client.tools();
|
|
69
|
+
const prefix = this.config.toolPrefix ?? this.name;
|
|
70
|
+
for (const [toolName, tool] of Object.entries(mcpTools)) {
|
|
71
|
+
const prefixedName = `${prefix}__${toolName}`;
|
|
72
|
+
const parameters = this.extractParameters(tool);
|
|
73
|
+
this.tools[prefixedName] = {
|
|
74
|
+
name: prefixedName,
|
|
75
|
+
description: tool.description || "",
|
|
76
|
+
parameters,
|
|
77
|
+
execute: async (params) => {
|
|
78
|
+
const result = await tool.execute(params, { toolCallId: `call_${Date.now()}` });
|
|
79
|
+
if (result && typeof result === "object" && result.isError) {
|
|
80
|
+
const msg = result.content?.[0]?.text || (result.content?.length ? JSON.stringify(result.content) : null) || "MCP tool returned an error (no details provided)";
|
|
81
|
+
throw new Error(msg);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
this.ready = true;
|
|
88
|
+
}
|
|
89
|
+
isReady() {
|
|
90
|
+
return this.ready;
|
|
91
|
+
}
|
|
92
|
+
async close() {
|
|
93
|
+
if (this.client) {
|
|
94
|
+
await this.client.close();
|
|
95
|
+
this.client = null;
|
|
96
|
+
}
|
|
97
|
+
this.ready = false;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Extract parameters from an AI SDK tool into JSON Schema format.
|
|
101
|
+
* The @ai-sdk/mcp client exposes the schema as `inputSchema` (a jsonSchema()
|
|
102
|
+
* wrapper with a `.jsonSchema` getter), while older/test code may use
|
|
103
|
+
* `parameters` directly.
|
|
104
|
+
*/
|
|
105
|
+
extractParameters(tool) {
|
|
106
|
+
const schema = tool.inputSchema ?? tool.parameters;
|
|
107
|
+
if (schema) {
|
|
108
|
+
if (schema.jsonSchema) return schema.jsonSchema;
|
|
109
|
+
if (schema.type === "object" && schema.properties) return schema;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create the appropriate transport based on config.
|
|
118
|
+
*/
|
|
119
|
+
async createTransport() {
|
|
120
|
+
switch (this.config.transport) {
|
|
121
|
+
case "stdio": return await this.createStdioTransport();
|
|
122
|
+
case "http": return this.createHttpTransport();
|
|
123
|
+
case "sse": return this.createSseTransport();
|
|
124
|
+
default: throw new Error(`Unknown transport type: ${this.config.transport}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async createStdioTransport() {
|
|
128
|
+
if (!this.config.command) throw new Error(`Skill "${this.name}": stdio transport requires a "command" field`);
|
|
129
|
+
const args = this.config.args?.map(resolveEnvVars) ?? [];
|
|
130
|
+
const env = this.config.env ? resolveEnvRecord(this.config.env) : void 0;
|
|
131
|
+
const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
|
|
132
|
+
return new StdioClientTransport({
|
|
133
|
+
command: this.config.command,
|
|
134
|
+
args,
|
|
135
|
+
env: env ? {
|
|
136
|
+
...process.env,
|
|
137
|
+
...env
|
|
138
|
+
} : void 0
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
createHttpTransport() {
|
|
142
|
+
if (!this.config.url) throw new Error(`Skill "${this.name}": http transport requires a "url" field`);
|
|
143
|
+
const headers = this.config.headers ? resolveEnvRecord(this.config.headers) : void 0;
|
|
144
|
+
return {
|
|
145
|
+
type: "http",
|
|
146
|
+
url: resolveEnvVars(this.config.url),
|
|
147
|
+
headers
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
createSseTransport() {
|
|
151
|
+
if (!this.config.url) throw new Error(`Skill "${this.name}": sse transport requires a "url" field`);
|
|
152
|
+
const headers = this.config.headers ? resolveEnvRecord(this.config.headers) : void 0;
|
|
153
|
+
return {
|
|
154
|
+
type: "sse",
|
|
155
|
+
url: resolveEnvVars(this.config.url),
|
|
156
|
+
headers
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/PromptSkill.ts
|
|
163
|
+
var PromptSkill = class {
|
|
164
|
+
name;
|
|
165
|
+
tools = {};
|
|
166
|
+
ready = false;
|
|
167
|
+
_config;
|
|
168
|
+
constructor(config) {
|
|
169
|
+
this._config = config;
|
|
170
|
+
this.name = config.name;
|
|
171
|
+
}
|
|
172
|
+
async initialize() {
|
|
173
|
+
if (this.ready) return;
|
|
174
|
+
if (!this._config.content?.trim()) throw new Error(`Prompt skill "${this.name}": content must be a non-empty string`);
|
|
175
|
+
this.ready = true;
|
|
176
|
+
}
|
|
177
|
+
isReady() {
|
|
178
|
+
return this.ready;
|
|
179
|
+
}
|
|
180
|
+
async close() {
|
|
181
|
+
this.ready = false;
|
|
182
|
+
}
|
|
183
|
+
getContent() {
|
|
184
|
+
return this._config.content;
|
|
185
|
+
}
|
|
186
|
+
getConfig() {
|
|
187
|
+
return { ...this._config };
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/SkillManager.ts
|
|
193
|
+
var SkillManager = class SkillManager {
|
|
194
|
+
skills = [];
|
|
195
|
+
async initialize(config) {
|
|
196
|
+
const enabledSkills = config.skills.filter((s) => s.enabled !== false);
|
|
197
|
+
for (const skillConfig of enabledSkills) if (isPromptSkillConfig(skillConfig)) {
|
|
198
|
+
const validationError = SkillManager.validatePromptConfig(skillConfig);
|
|
199
|
+
if (validationError) {
|
|
200
|
+
console.warn(`[SkillManager] Skipping prompt skill "${skillConfig.name}": ${validationError}`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const skill = new PromptSkill(skillConfig);
|
|
205
|
+
await skill.initialize();
|
|
206
|
+
this.skills.push(skill);
|
|
207
|
+
console.log(`[SkillManager] ✅ ${skill.name}: prompt skill loaded`);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.warn(`[SkillManager] ⚠️ Failed to load prompt skill "${skillConfig.name}": ${error.message}`);
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
const validationError = SkillManager.validateConfig(skillConfig);
|
|
213
|
+
if (validationError) {
|
|
214
|
+
console.warn(`[SkillManager] Skipping skill "${skillConfig.name}": ${validationError}`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
const skill = new MCPSkill(skillConfig);
|
|
219
|
+
await skill.initialize();
|
|
220
|
+
this.skills.push(skill);
|
|
221
|
+
const toolCount = Object.keys(skill.tools).length;
|
|
222
|
+
console.log(`[SkillManager] ✅ ${skill.name}: ${toolCount} tools loaded`);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.warn(`[SkillManager] ⚠️ Failed to start skill "${skillConfig.name}": ${error.message}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return this.skills;
|
|
228
|
+
}
|
|
229
|
+
async closeAll() {
|
|
230
|
+
const closePromises = this.skills.map(async (skill) => {
|
|
231
|
+
try {
|
|
232
|
+
if (skill.close) await skill.close();
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.warn(`[SkillManager] Error closing skill "${skill.name}": ${error.message}`);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
await Promise.all(closePromises);
|
|
238
|
+
this.skills = [];
|
|
239
|
+
}
|
|
240
|
+
getSkills() {
|
|
241
|
+
return this.skills;
|
|
242
|
+
}
|
|
243
|
+
getPromptSkills() {
|
|
244
|
+
return this.skills.filter((s) => s instanceof PromptSkill);
|
|
245
|
+
}
|
|
246
|
+
getMCPSkills() {
|
|
247
|
+
return this.skills.filter((s) => s instanceof MCPSkill);
|
|
248
|
+
}
|
|
249
|
+
static validateConfig(config) {
|
|
250
|
+
if (!config.name || typeof config.name !== "string") return "Missing or invalid \"name\" field";
|
|
251
|
+
if (![
|
|
252
|
+
"stdio",
|
|
253
|
+
"http",
|
|
254
|
+
"sse"
|
|
255
|
+
].includes(config.transport)) return `Invalid transport "${config.transport}" — must be "stdio", "http", or "sse"`;
|
|
256
|
+
if (config.transport === "stdio" && !config.command) return "stdio transport requires a \"command\" field";
|
|
257
|
+
if ((config.transport === "http" || config.transport === "sse") && !config.url) return `${config.transport} transport requires a "url" field`;
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
static validatePromptConfig(config) {
|
|
261
|
+
if (!config.name || typeof config.name !== "string") return "Missing or invalid \"name\" field";
|
|
262
|
+
if (!config.content || typeof config.content !== "string" || !config.content.trim()) return "Missing or empty \"content\" field";
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/catalog.ts
|
|
269
|
+
function isPromptCatalogEntry(entry) {
|
|
270
|
+
return entry.type === "prompt";
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Load the built-in skill catalog shipped with @operor/skills.
|
|
274
|
+
* Falls back to empty catalog if file is missing.
|
|
275
|
+
*/
|
|
276
|
+
function loadSkillCatalog() {
|
|
277
|
+
const catalogPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "skill-catalog.json");
|
|
278
|
+
if (!existsSync(catalogPath)) return {
|
|
279
|
+
version: 1,
|
|
280
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
281
|
+
skills: []
|
|
282
|
+
};
|
|
283
|
+
const raw = readFileSync(catalogPath, "utf-8");
|
|
284
|
+
return JSON.parse(raw);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Load a custom/override catalog from a specific path.
|
|
288
|
+
*/
|
|
289
|
+
function loadSkillCatalogFrom(filePath) {
|
|
290
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
291
|
+
return JSON.parse(raw);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Query the catalog with optional filters.
|
|
295
|
+
* Search matches against name, displayName, description, and tags.
|
|
296
|
+
*/
|
|
297
|
+
function querySkillCatalog(catalog, filter) {
|
|
298
|
+
let results = catalog.skills;
|
|
299
|
+
if (filter?.category) results = results.filter((s) => s.category === filter.category);
|
|
300
|
+
if (filter?.maturity) results = results.filter((s) => s.maturity === filter.maturity);
|
|
301
|
+
if (filter?.tags && filter.tags.length > 0) {
|
|
302
|
+
const filterTags = filter.tags.map((t) => t.toLowerCase());
|
|
303
|
+
results = results.filter((s) => filterTags.some((ft) => s.tags.some((st) => st.toLowerCase().includes(ft))));
|
|
304
|
+
}
|
|
305
|
+
if (filter?.search) {
|
|
306
|
+
const q = filter.search.toLowerCase();
|
|
307
|
+
results = results.filter((s) => s.name.toLowerCase().includes(q) || s.displayName.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.tags.some((t) => t.toLowerCase().includes(q)));
|
|
308
|
+
}
|
|
309
|
+
return results;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Look up a single skill by name.
|
|
313
|
+
*/
|
|
314
|
+
function findSkillInCatalog(catalog, name) {
|
|
315
|
+
return catalog.skills.find((s) => s.name === name);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Convert a catalog entry into a config suitable for mcp.json.
|
|
319
|
+
* Handles both MCP and prompt skill entries.
|
|
320
|
+
*/
|
|
321
|
+
function catalogEntryToConfig(entry) {
|
|
322
|
+
if (isPromptCatalogEntry(entry)) return {
|
|
323
|
+
type: "prompt",
|
|
324
|
+
name: entry.name,
|
|
325
|
+
content: entry.content,
|
|
326
|
+
enabled: true
|
|
327
|
+
};
|
|
328
|
+
const config = {
|
|
329
|
+
name: entry.name,
|
|
330
|
+
transport: entry.transport,
|
|
331
|
+
enabled: true
|
|
332
|
+
};
|
|
333
|
+
if (entry.transport === "stdio") {
|
|
334
|
+
config.command = entry.command ?? "npx";
|
|
335
|
+
config.args = entry.args ?? (entry.package ? ["-y", entry.package] : []);
|
|
336
|
+
}
|
|
337
|
+
if ((entry.transport === "http" || entry.transport === "sse") && entry.url) config.url = entry.url;
|
|
338
|
+
const envKeys = Object.keys(entry.envVars);
|
|
339
|
+
if (envKeys.length > 0) {
|
|
340
|
+
config.env = {};
|
|
341
|
+
for (const key of envKeys) config.env[key] = `\${${key}}`;
|
|
342
|
+
}
|
|
343
|
+
return config;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
//#endregion
|
|
347
|
+
export { MCPSkill, PromptSkill, SkillManager, catalogEntryToConfig, findSkillInCatalog, isMCPSkillConfig, isPromptCatalogEntry, isPromptSkillConfig, loadSkillCatalog, loadSkillCatalogFrom, loadSkillsConfig, querySkillCatalog, resolveEnvRecord, resolveEnvVars, saveSkillsConfig };
|
|
348
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/config.ts","../src/MCPSkill.ts","../src/PromptSkill.ts","../src/SkillManager.ts","../src/catalog.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport interface MCPSkillConfig {\n name: string;\n transport: 'stdio' | 'http' | 'sse';\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n url?: string;\n headers?: Record<string, string>;\n toolPrefix?: string;\n enabled?: boolean;\n}\n\nexport interface PromptSkillConfig {\n type: 'prompt';\n name: string;\n summary?: string;\n version?: string;\n tags?: string[];\n author?: string;\n content: string;\n enabled?: boolean;\n}\n\nexport type AnySkillConfig = (MCPSkillConfig & { type?: 'mcp' }) | PromptSkillConfig;\n\nexport function isPromptSkillConfig(config: AnySkillConfig): config is PromptSkillConfig {\n return (config as any).type === 'prompt';\n}\n\nexport function isMCPSkillConfig(config: AnySkillConfig): config is MCPSkillConfig & { type?: 'mcp' } {\n return !isPromptSkillConfig(config);\n}\n\nexport interface SkillsConfig {\n skills: AnySkillConfig[];\n}\n\n/**\n * Resolve `${ENV_VAR}` placeholders in a string value.\n */\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_match, envVar) => {\n return process.env[envVar] ?? '';\n });\n}\n\n/**\n * Resolve env vars in all values of a record.\n */\nexport function resolveEnvRecord(record: Record<string, string>): Record<string, string> {\n const resolved: Record<string, string> = {};\n for (const [key, value] of Object.entries(record)) {\n resolved[key] = resolveEnvVars(value);\n }\n return resolved;\n}\n\n/**\n * Load skills config from `mcp.json` at the given root (defaults to cwd).\n * Returns empty config if file doesn't exist.\n */\nexport function loadSkillsConfig(root?: string): SkillsConfig {\n const configPath = resolve(root ?? process.cwd(), 'mcp.json');\n\n if (!existsSync(configPath)) {\n return { skills: [] };\n }\n\n const raw = readFileSync(configPath, 'utf-8');\n\n let parsed: any;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Invalid JSON in ${configPath}`);\n }\n\n if (!parsed || typeof parsed !== 'object') {\n return { skills: [] };\n }\n\n const skills: AnySkillConfig[] = Array.isArray(parsed.skills) ? parsed.skills : [];\n return { skills };\n}\n\n/**\n * Save skills config to `mcp.json` at the given root (defaults to cwd).\n */\nexport function saveSkillsConfig(config: SkillsConfig, root?: string): void {\n const configPath = resolve(root ?? process.cwd(), 'mcp.json');\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n","import type { Skill, Tool } from '@operor/core';\nimport { createMCPClient } from '@ai-sdk/mcp';\nimport type { MCPSkillConfig } from './config.js';\nimport { resolveEnvVars, resolveEnvRecord } from './config.js';\n\nexport class MCPSkill implements Skill {\n public readonly name: string;\n public tools: Record<string, Tool> = {};\n private client: any = null;\n private ready = false;\n private config: MCPSkillConfig;\n\n constructor(config: MCPSkillConfig) {\n this.config = config;\n this.name = config.name;\n }\n\n async initialize(): Promise<void> {\n if (this.ready) return;\n const transport = await this.createTransport();\n\n this.client = await createMCPClient({ transport });\n\n // Discover tools via the AI SDK MCP client\n const mcpTools = await this.client.tools();\n const prefix = this.config.toolPrefix ?? this.name;\n\n for (const [toolName, tool] of Object.entries(mcpTools) as [string, any][]) {\n const prefixedName = `${prefix}__${toolName}`;\n\n // Extract the JSON Schema from the AI SDK tool's parameters\n const parameters = this.extractParameters(tool);\n\n this.tools[prefixedName] = {\n name: prefixedName,\n description: tool.description || '',\n parameters,\n execute: async (params: any) => {\n // Call the AI SDK tool's execute function directly\n const result = await tool.execute(params, { toolCallId: `call_${Date.now()}` });\n // MCP tools return { content: [...], isError: true } on failure\n // instead of throwing — surface these as real errors\n if (result && typeof result === 'object' && result.isError) {\n const msg = result.content?.[0]?.text\n || (result.content?.length ? JSON.stringify(result.content) : null)\n || 'MCP tool returned an error (no details provided)';\n throw new Error(msg);\n }\n return result;\n },\n };\n }\n\n this.ready = true;\n }\n\n isReady(): boolean {\n return this.ready;\n }\n\n async close(): Promise<void> {\n if (this.client) {\n await this.client.close();\n this.client = null;\n }\n this.ready = false;\n }\n\n /**\n * Extract parameters from an AI SDK tool into JSON Schema format.\n * The @ai-sdk/mcp client exposes the schema as `inputSchema` (a jsonSchema()\n * wrapper with a `.jsonSchema` getter), while older/test code may use\n * `parameters` directly.\n */\n private extractParameters(tool: any): Record<string, any> {\n // AI SDK MCP tools use `inputSchema` (a jsonSchema() wrapper)\n const schema = tool.inputSchema ?? tool.parameters;\n if (schema) {\n // jsonSchema() wrapper — has a `.jsonSchema` getter\n if (schema.jsonSchema) {\n return schema.jsonSchema;\n }\n // Plain JSON Schema object\n if (schema.type === 'object' && schema.properties) {\n return schema;\n }\n }\n return { type: 'object', properties: {} };\n }\n\n /**\n * Create the appropriate transport based on config.\n */\n private async createTransport(): Promise<any> {\n switch (this.config.transport) {\n case 'stdio':\n return await this.createStdioTransport();\n case 'http':\n return this.createHttpTransport();\n case 'sse':\n return this.createSseTransport();\n default:\n throw new Error(`Unknown transport type: ${this.config.transport}`);\n }\n }\n\n private async createStdioTransport(): Promise<any> {\n if (!this.config.command) {\n throw new Error(`Skill \"${this.name}\": stdio transport requires a \"command\" field`);\n }\n\n // Resolve env vars in args\n const args = this.config.args?.map(resolveEnvVars) ?? [];\n\n // Resolve env vars in env record\n const env = this.config.env ? resolveEnvRecord(this.config.env) : undefined;\n\n // ESM dynamic import for @modelcontextprotocol/sdk stdio transport\n const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js');\n return new StdioClientTransport({\n command: this.config.command,\n args,\n env: env ? { ...process.env, ...env } : undefined,\n });\n }\n\n private createHttpTransport(): any {\n if (!this.config.url) {\n throw new Error(`Skill \"${this.name}\": http transport requires a \"url\" field`);\n }\n\n const headers = this.config.headers ? resolveEnvRecord(this.config.headers) : undefined;\n\n return {\n type: 'http' as const,\n url: resolveEnvVars(this.config.url),\n headers,\n };\n }\n\n private createSseTransport(): any {\n if (!this.config.url) {\n throw new Error(`Skill \"${this.name}\": sse transport requires a \"url\" field`);\n }\n\n const headers = this.config.headers ? resolveEnvRecord(this.config.headers) : undefined;\n\n return {\n type: 'sse' as const,\n url: resolveEnvVars(this.config.url),\n headers,\n };\n }\n}\n","import type { Skill, Tool } from '@operor/core';\nimport type { PromptSkillConfig } from './config.js';\n\nexport class PromptSkill implements Skill {\n public readonly name: string;\n public readonly tools: Record<string, Tool> = {};\n private ready = false;\n private _config: PromptSkillConfig;\n\n constructor(config: PromptSkillConfig) {\n this._config = config;\n this.name = config.name;\n }\n\n async initialize(): Promise<void> {\n if (this.ready) return;\n if (!this._config.content?.trim()) {\n throw new Error(`Prompt skill \"${this.name}\": content must be a non-empty string`);\n }\n this.ready = true;\n }\n\n isReady(): boolean {\n return this.ready;\n }\n\n async close(): Promise<void> {\n this.ready = false;\n }\n\n getContent(): string {\n return this._config.content;\n }\n\n getConfig(): PromptSkillConfig {\n return { ...this._config };\n }\n}\n","import type { Skill } from '@operor/core';\nimport { MCPSkill } from './MCPSkill.js';\nimport { PromptSkill } from './PromptSkill.js';\nimport type { MCPSkillConfig, SkillsConfig, PromptSkillConfig } from './config.js';\nimport { isPromptSkillConfig } from './config.js';\n\nexport class SkillManager {\n private skills: Skill[] = [];\n\n async initialize(config: SkillsConfig): Promise<Skill[]> {\n const enabledSkills = config.skills.filter(s => s.enabled !== false);\n\n for (const skillConfig of enabledSkills) {\n if (isPromptSkillConfig(skillConfig)) {\n const validationError = SkillManager.validatePromptConfig(skillConfig);\n if (validationError) {\n console.warn(`[SkillManager] Skipping prompt skill \"${skillConfig.name}\": ${validationError}`);\n continue;\n }\n try {\n const skill = new PromptSkill(skillConfig);\n await skill.initialize();\n this.skills.push(skill);\n console.log(`[SkillManager] ✅ ${skill.name}: prompt skill loaded`);\n } catch (error: any) {\n console.warn(`[SkillManager] ⚠️ Failed to load prompt skill \"${skillConfig.name}\": ${error.message}`);\n }\n } else {\n const validationError = SkillManager.validateConfig(skillConfig);\n if (validationError) {\n console.warn(`[SkillManager] Skipping skill \"${skillConfig.name}\": ${validationError}`);\n continue;\n }\n try {\n const skill = new MCPSkill(skillConfig);\n await skill.initialize();\n this.skills.push(skill);\n const toolCount = Object.keys(skill.tools).length;\n console.log(`[SkillManager] ✅ ${skill.name}: ${toolCount} tools loaded`);\n } catch (error: any) {\n console.warn(`[SkillManager] ⚠️ Failed to start skill \"${skillConfig.name}\": ${error.message}`);\n }\n }\n }\n\n return this.skills;\n }\n\n async closeAll(): Promise<void> {\n const closePromises = this.skills.map(async (skill) => {\n try {\n if (skill.close) await skill.close();\n } catch (error: any) {\n console.warn(`[SkillManager] Error closing skill \"${skill.name}\": ${error.message}`);\n }\n });\n await Promise.all(closePromises);\n this.skills = [];\n }\n\n getSkills(): Skill[] {\n return this.skills;\n }\n\n getPromptSkills(): PromptSkill[] {\n return this.skills.filter((s): s is PromptSkill => s instanceof PromptSkill);\n }\n\n getMCPSkills(): MCPSkill[] {\n return this.skills.filter((s): s is MCPSkill => s instanceof MCPSkill);\n }\n\n static validateConfig(config: MCPSkillConfig): string | null {\n if (!config.name || typeof config.name !== 'string') {\n return 'Missing or invalid \"name\" field';\n }\n if (!['stdio', 'http', 'sse'].includes(config.transport)) {\n return `Invalid transport \"${config.transport}\" — must be \"stdio\", \"http\", or \"sse\"`;\n }\n if (config.transport === 'stdio' && !config.command) {\n return 'stdio transport requires a \"command\" field';\n }\n if ((config.transport === 'http' || config.transport === 'sse') && !config.url) {\n return `${config.transport} transport requires a \"url\" field`;\n }\n return null;\n }\n\n static validatePromptConfig(config: PromptSkillConfig): string | null {\n if (!config.name || typeof config.name !== 'string') {\n return 'Missing or invalid \"name\" field';\n }\n if (!config.content || typeof config.content !== 'string' || !config.content.trim()) {\n return 'Missing or empty \"content\" field';\n }\n return null;\n }\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// ─── Catalog Schema ─────────────────────────────────────────────────────────\n\n/** A single MCP skill entry in the catalog. */\nexport interface MCPSkillCatalogEntry {\n type?: 'mcp';\n name: string;\n displayName: string;\n description: string;\n category: SkillCategory;\n tags: string[];\n transport: 'stdio' | 'http' | 'sse';\n package?: string;\n command?: string;\n args?: string[];\n url?: string;\n envVars: Record<string, EnvVarSpec>;\n tools: SkillToolSummary[];\n maturity: 'official' | 'community' | 'experimental';\n vendor: string;\n docsUrl?: string;\n version?: string;\n updatedAt?: string;\n}\n\n/** A prompt skill entry in the catalog — behavioral instructions, no tools. */\nexport interface PromptSkillCatalogEntry {\n type: 'prompt';\n name: string;\n displayName: string;\n description: string;\n category: SkillCategory;\n tags: string[];\n content: string;\n maturity: 'official' | 'community' | 'experimental';\n vendor: string;\n version?: string;\n updatedAt?: string;\n}\n\n/** A single skill entry in the catalog — the \"app store\" listing. */\nexport type SkillCatalogEntry = MCPSkillCatalogEntry | PromptSkillCatalogEntry;\n\nexport function isPromptCatalogEntry(entry: SkillCatalogEntry): entry is PromptSkillCatalogEntry {\n return (entry as any).type === 'prompt';\n}\n\nexport type SkillCategory =\n | 'commerce'\n | 'payments'\n | 'crm'\n | 'support'\n | 'marketing'\n | 'search'\n | 'analytics'\n | 'communication'\n | 'productivity'\n | 'developer'\n | 'other';\n\n/** Describes an env var a skill needs. */\nexport interface EnvVarSpec {\n /** What this env var is for */\n description: string;\n /** Whether the skill cannot function without it */\n required: boolean;\n /** Hint for the setup wizard (e.g. \"sk-...\" or \"https://...\") */\n placeholder?: string;\n}\n\n/** Summary of a tool exposed by a skill — for catalog display, not runtime. */\nexport interface SkillToolSummary {\n /** Tool name as exposed by the MCP server */\n name: string;\n /** What this tool does */\n description: string;\n}\n\n/** The top-level catalog file structure. */\nexport interface SkillCatalog {\n /** Schema version for forward compatibility */\n version: number;\n /** When the catalog was last generated */\n updatedAt: string;\n /** The skill entries */\n skills: SkillCatalogEntry[];\n}\n\n// ─── Catalog Loader ─────────────────────────────────────────────────────────\n\n/**\n * Load the built-in skill catalog shipped with @operor/skills.\n * Falls back to empty catalog if file is missing.\n */\nexport function loadSkillCatalog(): SkillCatalog {\n const catalogPath = resolve(\n dirname(fileURLToPath(import.meta.url)),\n '..',\n 'skill-catalog.json',\n );\n\n if (!existsSync(catalogPath)) {\n return { version: 1, updatedAt: new Date().toISOString(), skills: [] };\n }\n\n const raw = readFileSync(catalogPath, 'utf-8');\n return JSON.parse(raw) as SkillCatalog;\n}\n\n/**\n * Load a custom/override catalog from a specific path.\n */\nexport function loadSkillCatalogFrom(filePath: string): SkillCatalog {\n const raw = readFileSync(filePath, 'utf-8');\n return JSON.parse(raw) as SkillCatalog;\n}\n\n// ─── Catalog Query Helpers ──────────────────────────────────────────────────\n\n/** Filter options for querying the catalog. */\nexport interface CatalogFilter {\n category?: SkillCategory;\n maturity?: SkillCatalogEntry['maturity'];\n search?: string;\n tags?: string[];\n}\n\n/**\n * Query the catalog with optional filters.\n * Search matches against name, displayName, description, and tags.\n */\nexport function querySkillCatalog(\n catalog: SkillCatalog,\n filter?: CatalogFilter,\n): SkillCatalogEntry[] {\n let results = catalog.skills;\n\n if (filter?.category) {\n results = results.filter((s) => s.category === filter.category);\n }\n\n if (filter?.maturity) {\n results = results.filter((s) => s.maturity === filter.maturity);\n }\n\n if (filter?.tags && filter.tags.length > 0) {\n const filterTags = filter.tags.map((t) => t.toLowerCase());\n results = results.filter((s) =>\n filterTags.some((ft) => s.tags.some((st) => st.toLowerCase().includes(ft))),\n );\n }\n\n if (filter?.search) {\n const q = filter.search.toLowerCase();\n results = results.filter(\n (s) =>\n s.name.toLowerCase().includes(q) ||\n s.displayName.toLowerCase().includes(q) ||\n s.description.toLowerCase().includes(q) ||\n s.tags.some((t) => t.toLowerCase().includes(q)),\n );\n }\n\n return results;\n}\n\n/**\n * Look up a single skill by name.\n */\nexport function findSkillInCatalog(\n catalog: SkillCatalog,\n name: string,\n): SkillCatalogEntry | undefined {\n return catalog.skills.find((s) => s.name === name);\n}\n\n/**\n * Convert a catalog entry into a config suitable for mcp.json.\n * Handles both MCP and prompt skill entries.\n */\nexport function catalogEntryToConfig(entry: SkillCatalogEntry): Record<string, any> {\n if (isPromptCatalogEntry(entry)) {\n return { type: 'prompt', name: entry.name, content: entry.content, enabled: true };\n }\n\n const config: Record<string, any> = {\n name: entry.name,\n transport: entry.transport,\n enabled: true,\n };\n\n if (entry.transport === 'stdio') {\n config.command = entry.command ?? 'npx';\n config.args = entry.args ?? (entry.package ? ['-y', entry.package] : []);\n }\n\n if ((entry.transport === 'http' || entry.transport === 'sse') && entry.url) {\n config.url = entry.url;\n }\n\n const envKeys = Object.keys(entry.envVars);\n if (envKeys.length > 0) {\n config.env = {};\n for (const key of envKeys) {\n config.env[key] = `\\${${key}}`;\n }\n }\n\n return config;\n}\n"],"mappings":";;;;;;AA4BA,SAAgB,oBAAoB,QAAqD;AACvF,QAAQ,OAAe,SAAS;;AAGlC,SAAgB,iBAAiB,QAAqE;AACpG,QAAO,CAAC,oBAAoB,OAAO;;;;;AAUrC,SAAgB,eAAe,OAAuB;AACpD,QAAO,MAAM,QAAQ,mBAAmB,QAAQ,WAAW;AACzD,SAAO,QAAQ,IAAI,WAAW;GAC9B;;;;;AAMJ,SAAgB,iBAAiB,QAAwD;CACvF,MAAM,WAAmC,EAAE;AAC3C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAC/C,UAAS,OAAO,eAAe,MAAM;AAEvC,QAAO;;;;;;AAOT,SAAgB,iBAAiB,MAA6B;CAC5D,MAAM,aAAa,QAAQ,QAAQ,QAAQ,KAAK,EAAE,WAAW;AAE7D,KAAI,CAAC,WAAW,WAAW,CACzB,QAAO,EAAE,QAAQ,EAAE,EAAE;CAGvB,MAAM,MAAM,aAAa,YAAY,QAAQ;CAE7C,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AACN,QAAM,IAAI,MAAM,mBAAmB,aAAa;;AAGlD,KAAI,CAAC,UAAU,OAAO,WAAW,SAC/B,QAAO,EAAE,QAAQ,EAAE,EAAE;AAIvB,QAAO,EAAE,QADwB,MAAM,QAAQ,OAAO,OAAO,GAAG,OAAO,SAAS,EAAE,EACjE;;;;;AAMnB,SAAgB,iBAAiB,QAAsB,MAAqB;AAE1E,eADmB,QAAQ,QAAQ,QAAQ,KAAK,EAAE,WAAW,EACnC,KAAK,UAAU,QAAQ,MAAM,EAAE,GAAG,MAAM,QAAQ;;;;;ACxF5E,IAAa,WAAb,MAAuC;CACrC,AAAgB;CAChB,AAAO,QAA8B,EAAE;CACvC,AAAQ,SAAc;CACtB,AAAQ,QAAQ;CAChB,AAAQ;CAER,YAAY,QAAwB;AAClC,OAAK,SAAS;AACd,OAAK,OAAO,OAAO;;CAGrB,MAAM,aAA4B;AAChC,MAAI,KAAK,MAAO;AAGhB,OAAK,SAAS,MAAM,gBAAgB,EAAE,WAFpB,MAAM,KAAK,iBAAiB,EAEG,CAAC;EAGlD,MAAM,WAAW,MAAM,KAAK,OAAO,OAAO;EAC1C,MAAM,SAAS,KAAK,OAAO,cAAc,KAAK;AAE9C,OAAK,MAAM,CAAC,UAAU,SAAS,OAAO,QAAQ,SAAS,EAAqB;GAC1E,MAAM,eAAe,GAAG,OAAO,IAAI;GAGnC,MAAM,aAAa,KAAK,kBAAkB,KAAK;AAE/C,QAAK,MAAM,gBAAgB;IACzB,MAAM;IACN,aAAa,KAAK,eAAe;IACjC;IACA,SAAS,OAAO,WAAgB;KAE9B,MAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,EAAE,YAAY,QAAQ,KAAK,KAAK,IAAI,CAAC;AAG/E,SAAI,UAAU,OAAO,WAAW,YAAY,OAAO,SAAS;MAC1D,MAAM,MAAM,OAAO,UAAU,IAAI,SAC3B,OAAO,SAAS,SAAS,KAAK,UAAU,OAAO,QAAQ,GAAG,SAC3D;AACL,YAAM,IAAI,MAAM,IAAI;;AAEtB,YAAO;;IAEV;;AAGH,OAAK,QAAQ;;CAGf,UAAmB;AACjB,SAAO,KAAK;;CAGd,MAAM,QAAuB;AAC3B,MAAI,KAAK,QAAQ;AACf,SAAM,KAAK,OAAO,OAAO;AACzB,QAAK,SAAS;;AAEhB,OAAK,QAAQ;;;;;;;;CASf,AAAQ,kBAAkB,MAAgC;EAExD,MAAM,SAAS,KAAK,eAAe,KAAK;AACxC,MAAI,QAAQ;AAEV,OAAI,OAAO,WACT,QAAO,OAAO;AAGhB,OAAI,OAAO,SAAS,YAAY,OAAO,WACrC,QAAO;;AAGX,SAAO;GAAE,MAAM;GAAU,YAAY,EAAE;GAAE;;;;;CAM3C,MAAc,kBAAgC;AAC5C,UAAQ,KAAK,OAAO,WAApB;GACE,KAAK,QACH,QAAO,MAAM,KAAK,sBAAsB;GAC1C,KAAK,OACH,QAAO,KAAK,qBAAqB;GACnC,KAAK,MACH,QAAO,KAAK,oBAAoB;GAClC,QACE,OAAM,IAAI,MAAM,2BAA2B,KAAK,OAAO,YAAY;;;CAIzE,MAAc,uBAAqC;AACjD,MAAI,CAAC,KAAK,OAAO,QACf,OAAM,IAAI,MAAM,UAAU,KAAK,KAAK,+CAA+C;EAIrF,MAAM,OAAO,KAAK,OAAO,MAAM,IAAI,eAAe,IAAI,EAAE;EAGxD,MAAM,MAAM,KAAK,OAAO,MAAM,iBAAiB,KAAK,OAAO,IAAI,GAAG;EAGlE,MAAM,EAAE,yBAAyB,MAAM,OAAO;AAC9C,SAAO,IAAI,qBAAqB;GAC9B,SAAS,KAAK,OAAO;GACrB;GACA,KAAK,MAAM;IAAE,GAAG,QAAQ;IAAK,GAAG;IAAK,GAAG;GACzC,CAAC;;CAGJ,AAAQ,sBAA2B;AACjC,MAAI,CAAC,KAAK,OAAO,IACf,OAAM,IAAI,MAAM,UAAU,KAAK,KAAK,0CAA0C;EAGhF,MAAM,UAAU,KAAK,OAAO,UAAU,iBAAiB,KAAK,OAAO,QAAQ,GAAG;AAE9E,SAAO;GACL,MAAM;GACN,KAAK,eAAe,KAAK,OAAO,IAAI;GACpC;GACD;;CAGH,AAAQ,qBAA0B;AAChC,MAAI,CAAC,KAAK,OAAO,IACf,OAAM,IAAI,MAAM,UAAU,KAAK,KAAK,yCAAyC;EAG/E,MAAM,UAAU,KAAK,OAAO,UAAU,iBAAiB,KAAK,OAAO,QAAQ,GAAG;AAE9E,SAAO;GACL,MAAM;GACN,KAAK,eAAe,KAAK,OAAO,IAAI;GACpC;GACD;;;;;;ACpJL,IAAa,cAAb,MAA0C;CACxC,AAAgB;CAChB,AAAgB,QAA8B,EAAE;CAChD,AAAQ,QAAQ;CAChB,AAAQ;CAER,YAAY,QAA2B;AACrC,OAAK,UAAU;AACf,OAAK,OAAO,OAAO;;CAGrB,MAAM,aAA4B;AAChC,MAAI,KAAK,MAAO;AAChB,MAAI,CAAC,KAAK,QAAQ,SAAS,MAAM,CAC/B,OAAM,IAAI,MAAM,iBAAiB,KAAK,KAAK,uCAAuC;AAEpF,OAAK,QAAQ;;CAGf,UAAmB;AACjB,SAAO,KAAK;;CAGd,MAAM,QAAuB;AAC3B,OAAK,QAAQ;;CAGf,aAAqB;AACnB,SAAO,KAAK,QAAQ;;CAGtB,YAA+B;AAC7B,SAAO,EAAE,GAAG,KAAK,SAAS;;;;;;AC7B9B,IAAa,eAAb,MAAa,aAAa;CACxB,AAAQ,SAAkB,EAAE;CAE5B,MAAM,WAAW,QAAwC;EACvD,MAAM,gBAAgB,OAAO,OAAO,QAAO,MAAK,EAAE,YAAY,MAAM;AAEpE,OAAK,MAAM,eAAe,cACxB,KAAI,oBAAoB,YAAY,EAAE;GACpC,MAAM,kBAAkB,aAAa,qBAAqB,YAAY;AACtE,OAAI,iBAAiB;AACnB,YAAQ,KAAK,yCAAyC,YAAY,KAAK,KAAK,kBAAkB;AAC9F;;AAEF,OAAI;IACF,MAAM,QAAQ,IAAI,YAAY,YAAY;AAC1C,UAAM,MAAM,YAAY;AACxB,SAAK,OAAO,KAAK,MAAM;AACvB,YAAQ,IAAI,oBAAoB,MAAM,KAAK,uBAAuB;YAC3D,OAAY;AACnB,YAAQ,KAAK,kDAAkD,YAAY,KAAK,KAAK,MAAM,UAAU;;SAElG;GACL,MAAM,kBAAkB,aAAa,eAAe,YAAY;AAChE,OAAI,iBAAiB;AACnB,YAAQ,KAAK,kCAAkC,YAAY,KAAK,KAAK,kBAAkB;AACvF;;AAEF,OAAI;IACF,MAAM,QAAQ,IAAI,SAAS,YAAY;AACvC,UAAM,MAAM,YAAY;AACxB,SAAK,OAAO,KAAK,MAAM;IACvB,MAAM,YAAY,OAAO,KAAK,MAAM,MAAM,CAAC;AAC3C,YAAQ,IAAI,oBAAoB,MAAM,KAAK,IAAI,UAAU,eAAe;YACjE,OAAY;AACnB,YAAQ,KAAK,4CAA4C,YAAY,KAAK,KAAK,MAAM,UAAU;;;AAKrG,SAAO,KAAK;;CAGd,MAAM,WAA0B;EAC9B,MAAM,gBAAgB,KAAK,OAAO,IAAI,OAAO,UAAU;AACrD,OAAI;AACF,QAAI,MAAM,MAAO,OAAM,MAAM,OAAO;YAC7B,OAAY;AACnB,YAAQ,KAAK,uCAAuC,MAAM,KAAK,KAAK,MAAM,UAAU;;IAEtF;AACF,QAAM,QAAQ,IAAI,cAAc;AAChC,OAAK,SAAS,EAAE;;CAGlB,YAAqB;AACnB,SAAO,KAAK;;CAGd,kBAAiC;AAC/B,SAAO,KAAK,OAAO,QAAQ,MAAwB,aAAa,YAAY;;CAG9E,eAA2B;AACzB,SAAO,KAAK,OAAO,QAAQ,MAAqB,aAAa,SAAS;;CAGxE,OAAO,eAAe,QAAuC;AAC3D,MAAI,CAAC,OAAO,QAAQ,OAAO,OAAO,SAAS,SACzC,QAAO;AAET,MAAI,CAAC;GAAC;GAAS;GAAQ;GAAM,CAAC,SAAS,OAAO,UAAU,CACtD,QAAO,sBAAsB,OAAO,UAAU;AAEhD,MAAI,OAAO,cAAc,WAAW,CAAC,OAAO,QAC1C,QAAO;AAET,OAAK,OAAO,cAAc,UAAU,OAAO,cAAc,UAAU,CAAC,OAAO,IACzE,QAAO,GAAG,OAAO,UAAU;AAE7B,SAAO;;CAGT,OAAO,qBAAqB,QAA0C;AACpE,MAAI,CAAC,OAAO,QAAQ,OAAO,OAAO,SAAS,SACzC,QAAO;AAET,MAAI,CAAC,OAAO,WAAW,OAAO,OAAO,YAAY,YAAY,CAAC,OAAO,QAAQ,MAAM,CACjF,QAAO;AAET,SAAO;;;;;;ACjDX,SAAgB,qBAAqB,OAA4D;AAC/F,QAAQ,MAAc,SAAS;;;;;;AAkDjC,SAAgB,mBAAiC;CAC/C,MAAM,cAAc,QAClB,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACvC,MACA,qBACD;AAED,KAAI,CAAC,WAAW,YAAY,CAC1B,QAAO;EAAE,SAAS;EAAG,4BAAW,IAAI,MAAM,EAAC,aAAa;EAAE,QAAQ,EAAE;EAAE;CAGxE,MAAM,MAAM,aAAa,aAAa,QAAQ;AAC9C,QAAO,KAAK,MAAM,IAAI;;;;;AAMxB,SAAgB,qBAAqB,UAAgC;CACnE,MAAM,MAAM,aAAa,UAAU,QAAQ;AAC3C,QAAO,KAAK,MAAM,IAAI;;;;;;AAiBxB,SAAgB,kBACd,SACA,QACqB;CACrB,IAAI,UAAU,QAAQ;AAEtB,KAAI,QAAQ,SACV,WAAU,QAAQ,QAAQ,MAAM,EAAE,aAAa,OAAO,SAAS;AAGjE,KAAI,QAAQ,SACV,WAAU,QAAQ,QAAQ,MAAM,EAAE,aAAa,OAAO,SAAS;AAGjE,KAAI,QAAQ,QAAQ,OAAO,KAAK,SAAS,GAAG;EAC1C,MAAM,aAAa,OAAO,KAAK,KAAK,MAAM,EAAE,aAAa,CAAC;AAC1D,YAAU,QAAQ,QAAQ,MACxB,WAAW,MAAM,OAAO,EAAE,KAAK,MAAM,OAAO,GAAG,aAAa,CAAC,SAAS,GAAG,CAAC,CAAC,CAC5E;;AAGH,KAAI,QAAQ,QAAQ;EAClB,MAAM,IAAI,OAAO,OAAO,aAAa;AACrC,YAAU,QAAQ,QACf,MACC,EAAE,KAAK,aAAa,CAAC,SAAS,EAAE,IAChC,EAAE,YAAY,aAAa,CAAC,SAAS,EAAE,IACvC,EAAE,YAAY,aAAa,CAAC,SAAS,EAAE,IACvC,EAAE,KAAK,MAAM,MAAM,EAAE,aAAa,CAAC,SAAS,EAAE,CAAC,CAClD;;AAGH,QAAO;;;;;AAMT,SAAgB,mBACd,SACA,MAC+B;AAC/B,QAAO,QAAQ,OAAO,MAAM,MAAM,EAAE,SAAS,KAAK;;;;;;AAOpD,SAAgB,qBAAqB,OAA+C;AAClF,KAAI,qBAAqB,MAAM,CAC7B,QAAO;EAAE,MAAM;EAAU,MAAM,MAAM;EAAM,SAAS,MAAM;EAAS,SAAS;EAAM;CAGpF,MAAM,SAA8B;EAClC,MAAM,MAAM;EACZ,WAAW,MAAM;EACjB,SAAS;EACV;AAED,KAAI,MAAM,cAAc,SAAS;AAC/B,SAAO,UAAU,MAAM,WAAW;AAClC,SAAO,OAAO,MAAM,SAAS,MAAM,UAAU,CAAC,MAAM,MAAM,QAAQ,GAAG,EAAE;;AAGzE,MAAK,MAAM,cAAc,UAAU,MAAM,cAAc,UAAU,MAAM,IACrE,QAAO,MAAM,MAAM;CAGrB,MAAM,UAAU,OAAO,KAAK,MAAM,QAAQ;AAC1C,KAAI,QAAQ,SAAS,GAAG;AACtB,SAAO,MAAM,EAAE;AACf,OAAK,MAAM,OAAO,QAChB,QAAO,IAAI,OAAO,MAAM,IAAI;;AAIhC,QAAO"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@operor/skills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP-based skills for Agent OS — universal tool integration via Model Context Protocol",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@ai-sdk/mcp": "^1.0.18",
|
|
16
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
17
|
+
"@operor/core": "0.1.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.0.0",
|
|
21
|
+
"tsdown": "^0.20.3",
|
|
22
|
+
"typescript": "^5.7.0",
|
|
23
|
+
"vitest": "^4.0.0"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsdown",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest"
|
|
29
|
+
}
|
|
30
|
+
}
|