skilldb 0.5.2 → 0.7.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/cli.js +33 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +32 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +32 -8
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +188 -13
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -94,7 +94,13 @@ var SkillDBClient = class {
|
|
|
94
94
|
}
|
|
95
95
|
return h;
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
/** Make an authenticated request to any endpoint (used by MCP tools for private skills). */
|
|
98
|
+
async rawRequest(endpoint, init) {
|
|
99
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
100
|
+
const headers = { ...this.headers(), ...init?.headers || {} };
|
|
101
|
+
return fetch(url, { ...init, headers });
|
|
102
|
+
}
|
|
103
|
+
async typedRequest(endpoint, params) {
|
|
98
104
|
const url = new URL(`${this.baseUrl}${endpoint}`);
|
|
99
105
|
if (params) {
|
|
100
106
|
for (const [k, v] of Object.entries(params)) {
|
|
@@ -103,15 +109,33 @@ var SkillDBClient = class {
|
|
|
103
109
|
}
|
|
104
110
|
const res = await fetch(url.toString(), { headers: this.headers() });
|
|
105
111
|
if (!res.ok) {
|
|
106
|
-
const body = await res.json().catch(() =>
|
|
107
|
-
const
|
|
112
|
+
const body = await res.json().catch(() => null);
|
|
113
|
+
const apiError = body && typeof body === "object" ? body.error : null;
|
|
114
|
+
const status = res.status;
|
|
115
|
+
const statusText = res.statusText || "Unknown";
|
|
116
|
+
let msg;
|
|
117
|
+
if (apiError) {
|
|
118
|
+
msg = apiError;
|
|
119
|
+
} else if (status === 500) {
|
|
120
|
+
msg = `SkillDB API internal error (HTTP 500). The service may be experiencing issues. Check status at https://skilldb.dev/api/v1/status`;
|
|
121
|
+
} else if (status === 503) {
|
|
122
|
+
msg = `SkillDB API unavailable (HTTP 503). The skills index may not be loaded. Check status at https://skilldb.dev/api/v1/status`;
|
|
123
|
+
} else if (status === 401) {
|
|
124
|
+
msg = `Invalid or expired API key (HTTP 401). Get a free key at https://skilldb.dev/api-access`;
|
|
125
|
+
} else if (status === 429) {
|
|
126
|
+
msg = `Rate limit exceeded (HTTP 429). Authenticate with an API key for higher limits: https://skilldb.dev/api-access`;
|
|
127
|
+
} else if (status === 404) {
|
|
128
|
+
msg = `Resource not found (HTTP 404). The requested skill or endpoint does not exist.`;
|
|
129
|
+
} else {
|
|
130
|
+
msg = `SkillDB API error: HTTP ${status} ${statusText}. Check https://skilldb.dev/api/v1/status for service health.`;
|
|
131
|
+
}
|
|
108
132
|
throw new Error(msg);
|
|
109
133
|
}
|
|
110
134
|
return res.json();
|
|
111
135
|
}
|
|
112
136
|
/** Search skills by keyword. */
|
|
113
137
|
async search(query, options) {
|
|
114
|
-
return this.
|
|
138
|
+
return this.typedRequest("/skills", {
|
|
115
139
|
search: query,
|
|
116
140
|
category: options?.category ?? "",
|
|
117
141
|
pack: options?.pack ?? "",
|
|
@@ -123,7 +147,7 @@ var SkillDBClient = class {
|
|
|
123
147
|
}
|
|
124
148
|
/** List skills with optional filters and sorting. */
|
|
125
149
|
async list(options) {
|
|
126
|
-
return this.
|
|
150
|
+
return this.typedRequest("/skills", {
|
|
127
151
|
category: options?.category ?? "",
|
|
128
152
|
pack: options?.pack ?? "",
|
|
129
153
|
search: options?.search ?? "",
|
|
@@ -136,21 +160,21 @@ var SkillDBClient = class {
|
|
|
136
160
|
/** Get a single skill by ID (e.g. "software-skills/code-review.md"). */
|
|
137
161
|
async get(id) {
|
|
138
162
|
const encoded = encodeURIComponent(id);
|
|
139
|
-
const res = await this.
|
|
163
|
+
const res = await this.typedRequest(`/skills/${encoded}`, {
|
|
140
164
|
include_content: "true"
|
|
141
165
|
});
|
|
142
166
|
return "skill" in res ? res.skill : res;
|
|
143
167
|
}
|
|
144
168
|
/** Batch retrieve multiple skills by IDs (max 50). */
|
|
145
169
|
async batch(ids) {
|
|
146
|
-
return this.
|
|
170
|
+
return this.typedRequest("/skills", {
|
|
147
171
|
ids: ids.slice(0, 50).join(","),
|
|
148
172
|
include_content: "true"
|
|
149
173
|
});
|
|
150
174
|
}
|
|
151
175
|
/** Get search autocomplete suggestions. */
|
|
152
176
|
async suggest(query) {
|
|
153
|
-
return this.
|
|
177
|
+
return this.typedRequest("/skills/suggest", { q: query });
|
|
154
178
|
}
|
|
155
179
|
/** Validate that the configured API key works. */
|
|
156
180
|
async validate() {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/client.ts","../src/cache.ts"],"sourcesContent":["export { SkillDBClient } from './client.js';\nexport type {\n Skill,\n PackInfo,\n CategoryInfo,\n SkillsResponse,\n SearchOptions,\n ClientConfig,\n Manifest,\n ApiKeyScope,\n ApiKeyInfo,\n} from './types.js';\nexport { resolveApiKey, resolveBaseUrl, saveApiKey } from './config.js';\nexport { initCache, cacheSkill, isCached, getCachedPath, listCached } from './cache.js';\n\nimport type { ClientConfig } from './types.js';\nimport { SkillDBClient } from './client.js';\n\n/** Create a SkillDB client. Auto-loads API key from env/config. */\nexport function createClient(config?: ClientConfig): SkillDBClient {\n return new SkillDBClient(config);\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\n\nconst RC_FILE = '.skilldbrc';\n\nexport const DEFAULT_BASE_URL = 'https://skilldb.dev/api/v1';\n\ninterface RcConfig {\n apiKey?: string;\n}\n\nfunction readJson(filePath: string): RcConfig | null {\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n return JSON.parse(raw);\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve API key from (in priority order):\n * 1. SKILLDB_API_KEY env var\n * 2. .skilldbrc in project root (cwd)\n * 3. ~/.skilldbrc in home dir\n */\nexport function resolveApiKey(): string | undefined {\n if (process.env.SKILLDB_API_KEY) {\n return process.env.SKILLDB_API_KEY;\n }\n\n const projectRc = path.join(process.cwd(), RC_FILE);\n const projectConfig = readJson(projectRc);\n if (projectConfig?.apiKey) return projectConfig.apiKey;\n\n const homeRc = path.join(os.homedir(), RC_FILE);\n const homeConfig = readJson(homeRc);\n if (homeConfig?.apiKey) return homeConfig.apiKey;\n\n return undefined;\n}\n\n/**\n * Resolve base URL from env or default.\n */\nexport function resolveBaseUrl(): string {\n return process.env.SKILLDB_API_URL || DEFAULT_BASE_URL;\n}\n\n/**\n * Save API key to ~/.skilldbrc (user-wide) or project .skilldbrc.\n */\nexport function saveApiKey(apiKey: string, global = true): string {\n const target = global\n ? path.join(os.homedir(), RC_FILE)\n : path.join(process.cwd(), RC_FILE);\n\n const existing = readJson(target) || {};\n fs.writeFileSync(target, JSON.stringify({ ...existing, apiKey }, null, 2) + '\\n', 'utf-8');\n return target;\n}\n","import type { ClientConfig, Skill, SkillsResponse, SearchOptions } from './types.js';\nimport { resolveApiKey, resolveBaseUrl } from './config.js';\n\nexport class SkillDBClient {\n private apiKey?: string;\n private baseUrl: string;\n\n constructor(config?: ClientConfig) {\n this.apiKey = config?.apiKey ?? resolveApiKey();\n this.baseUrl = config?.baseUrl ?? resolveBaseUrl();\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this.apiKey) {\n h['Authorization'] = `Bearer ${this.apiKey}`;\n }\n return h;\n }\n\n private async request<T>(endpoint: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${endpoint}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== '') url.searchParams.set(k, v);\n }\n }\n\n const res = await fetch(url.toString(), { headers: this.headers() });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n const msg = (body as Record<string, string>).error || `HTTP ${res.status}`;\n throw new Error(msg);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Search skills by keyword. */\n async search(query: string, options?: Omit<SearchOptions, 'search'>): Promise<SkillsResponse> {\n return this.request<SkillsResponse>('/skills', {\n search: query,\n category: options?.category ?? '',\n pack: options?.pack ?? '',\n sort: options?.sort ?? '',\n limit: String(options?.limit ?? 20),\n offset: String(options?.offset ?? 0),\n include_content: options?.includeContent ? 'true' : '',\n });\n }\n\n /** List skills with optional filters and sorting. */\n async list(options?: SearchOptions): Promise<SkillsResponse> {\n return this.request<SkillsResponse>('/skills', {\n category: options?.category ?? '',\n pack: options?.pack ?? '',\n search: options?.search ?? '',\n sort: options?.sort ?? '',\n limit: String(options?.limit ?? 50),\n offset: String(options?.offset ?? 0),\n include_content: options?.includeContent ? 'true' : '',\n });\n }\n\n /** Get a single skill by ID (e.g. \"software-skills/code-review.md\"). */\n async get(id: string): Promise<Skill> {\n const encoded = encodeURIComponent(id);\n const res = await this.request<Skill | { skill: Skill }>(`/skills/${encoded}`, {\n include_content: 'true',\n });\n // Handle both direct and wrapped responses\n return 'skill' in res ? res.skill : res;\n }\n\n /** Batch retrieve multiple skills by IDs (max 50). */\n async batch(ids: string[]): Promise<SkillsResponse> {\n return this.request<SkillsResponse>('/skills', {\n ids: ids.slice(0, 50).join(','),\n include_content: 'true',\n });\n }\n\n /** Get search autocomplete suggestions. */\n async suggest(query: string): Promise<{ suggestions: Array<{ title: string; pack: string; category: string; id: string }> }> {\n return this.request('/skills/suggest', { q: query });\n }\n\n /** Validate that the configured API key works. */\n async validate(): Promise<boolean> {\n try {\n const url = `${this.baseUrl}/keys/usage`;\n const res = await fetch(url, { headers: this.headers() });\n return res.ok;\n } catch {\n return false;\n }\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { Manifest, Skill } from './types.js';\n\nconst SKILLDB_DIR = '.skilldb';\nconst SKILLS_DIR = 'skills';\nconst MANIFEST_FILE = 'manifest.json';\n\nfunction ensureDir(dir: string): void {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n}\n\nfunction skilldbRoot(cwd?: string): string {\n return path.join(cwd ?? process.cwd(), SKILLDB_DIR);\n}\n\n/** Ensure .skilldb/ directory structure exists. */\nexport function initCache(cwd?: string): string {\n const root = skilldbRoot(cwd);\n ensureDir(path.join(root, SKILLS_DIR));\n\n const manifestPath = path.join(root, MANIFEST_FILE);\n if (!fs.existsSync(manifestPath)) {\n fs.writeFileSync(manifestPath, JSON.stringify({ installed: {} }, null, 2) + '\\n');\n }\n\n return root;\n}\n\n/** Read the local manifest. */\nexport function readManifest(cwd?: string): Manifest {\n const manifestPath = path.join(skilldbRoot(cwd), MANIFEST_FILE);\n try {\n return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));\n } catch {\n return { installed: {} };\n }\n}\n\n/** Write the local manifest. */\nexport function writeManifest(manifest: Manifest, cwd?: string): void {\n const root = skilldbRoot(cwd);\n ensureDir(root);\n fs.writeFileSync(\n path.join(root, MANIFEST_FILE),\n JSON.stringify(manifest, null, 2) + '\\n'\n );\n}\n\n/** Save a skill to the local cache. */\nexport function cacheSkill(skill: Skill, cwd?: string): string {\n const root = skilldbRoot(cwd);\n const skillDir = path.join(root, SKILLS_DIR, skill.pack);\n ensureDir(skillDir);\n\n const safeName = skill.name.replace(/[/\\\\:*?\"<>|]/g, '-');\n const filePath = path.join(skillDir, `${safeName}.md`);\n fs.writeFileSync(filePath, skill.content ?? `# ${skill.title}\\n\\n${skill.description}\\n`);\n\n // Update manifest\n const manifest = readManifest(cwd);\n manifest.installed[skill.id] = {\n addedAt: new Date().toISOString(),\n lines: skill.lines,\n };\n writeManifest(manifest, cwd);\n\n return filePath;\n}\n\n/** Check if a skill is already cached. */\nexport function isCached(skillId: string, cwd?: string): boolean {\n const manifest = readManifest(cwd);\n return skillId in manifest.installed;\n}\n\n/** Get the local path for a cached skill. */\nexport function getCachedPath(skillId: string, cwd?: string): string | null {\n if (!isCached(skillId, cwd)) return null;\n const [pack, file] = skillId.split('/');\n const name = file.replace('.md', '').replace(/[/\\\\:*?\"<>|]/g, '-');\n const filePath = path.join(skilldbRoot(cwd), SKILLS_DIR, pack, `${name}.md`);\n return fs.existsSync(filePath) ? filePath : null;\n}\n\n/** List all cached skills. */\nexport function listCached(cwd?: string): Manifest {\n return readManifest(cwd);\n}\n\n/** Ensure .skilldb/ and .skilldbrc are in .gitignore. */\nexport function updateGitignore(cwd?: string): void {\n const root = cwd ?? process.cwd();\n const gitignorePath = path.join(root, '.gitignore');\n\n const entries = ['.skilldb/', '.skilldbrc'];\n let content = '';\n\n if (fs.existsSync(gitignorePath)) {\n content = fs.readFileSync(gitignorePath, 'utf-8');\n }\n\n const toAdd = entries.filter(e => !content.includes(e));\n if (toAdd.length === 0) return;\n\n const suffix = (content && !content.endsWith('\\n') ? '\\n' : '') +\n '\\n# SkillDB\\n' + toAdd.join('\\n') + '\\n';\n\n fs.writeFileSync(gitignorePath, content + suffix);\n}\n"],"mappings":"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","names":["fs","path","os","import_node_fs","import_node_path","fs","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/client.ts","../src/cache.ts"],"sourcesContent":["export { SkillDBClient } from './client.js';\nexport type {\n Skill,\n PackInfo,\n CategoryInfo,\n SkillsResponse,\n SearchOptions,\n ClientConfig,\n Manifest,\n ApiKeyScope,\n ApiKeyInfo,\n} from './types.js';\nexport { resolveApiKey, resolveBaseUrl, saveApiKey } from './config.js';\nexport { initCache, cacheSkill, isCached, getCachedPath, listCached } from './cache.js';\n\nimport type { ClientConfig } from './types.js';\nimport { SkillDBClient } from './client.js';\n\n/** Create a SkillDB client. Auto-loads API key from env/config. */\nexport function createClient(config?: ClientConfig): SkillDBClient {\n return new SkillDBClient(config);\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\n\nconst RC_FILE = '.skilldbrc';\n\nexport const DEFAULT_BASE_URL = 'https://skilldb.dev/api/v1';\n\ninterface RcConfig {\n apiKey?: string;\n}\n\nfunction readJson(filePath: string): RcConfig | null {\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n return JSON.parse(raw);\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve API key from (in priority order):\n * 1. SKILLDB_API_KEY env var\n * 2. .skilldbrc in project root (cwd)\n * 3. ~/.skilldbrc in home dir\n */\nexport function resolveApiKey(): string | undefined {\n if (process.env.SKILLDB_API_KEY) {\n return process.env.SKILLDB_API_KEY;\n }\n\n const projectRc = path.join(process.cwd(), RC_FILE);\n const projectConfig = readJson(projectRc);\n if (projectConfig?.apiKey) return projectConfig.apiKey;\n\n const homeRc = path.join(os.homedir(), RC_FILE);\n const homeConfig = readJson(homeRc);\n if (homeConfig?.apiKey) return homeConfig.apiKey;\n\n return undefined;\n}\n\n/**\n * Resolve base URL from env or default.\n */\nexport function resolveBaseUrl(): string {\n return process.env.SKILLDB_API_URL || DEFAULT_BASE_URL;\n}\n\n/**\n * Save API key to ~/.skilldbrc (user-wide) or project .skilldbrc.\n */\nexport function saveApiKey(apiKey: string, global = true): string {\n const target = global\n ? path.join(os.homedir(), RC_FILE)\n : path.join(process.cwd(), RC_FILE);\n\n const existing = readJson(target) || {};\n fs.writeFileSync(target, JSON.stringify({ ...existing, apiKey }, null, 2) + '\\n', 'utf-8');\n return target;\n}\n","import type { ClientConfig, Skill, SkillsResponse, SearchOptions } from './types.js';\nimport { resolveApiKey, resolveBaseUrl } from './config.js';\n\nexport class SkillDBClient {\n private apiKey?: string;\n private baseUrl: string;\n\n constructor(config?: ClientConfig) {\n this.apiKey = config?.apiKey ?? resolveApiKey();\n this.baseUrl = config?.baseUrl ?? resolveBaseUrl();\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this.apiKey) {\n h['Authorization'] = `Bearer ${this.apiKey}`;\n }\n return h;\n }\n\n /** Make an authenticated request to any endpoint (used by MCP tools for private skills). */\n public async rawRequest(endpoint: string, init?: RequestInit): Promise<Response> {\n const url = `${this.baseUrl}${endpoint}`;\n const headers = { ...this.headers(), ...(init?.headers || {}) };\n return fetch(url, { ...init, headers });\n }\n\n\n private async typedRequest<T>(endpoint: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${endpoint}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== '') url.searchParams.set(k, v);\n }\n }\n\n const res = await fetch(url.toString(), { headers: this.headers() });\n\n if (!res.ok) {\n const body = await res.json().catch(() => null);\n const apiError = body && typeof body === 'object' ? (body as Record<string, string>).error : null;\n const status = res.status;\n const statusText = res.statusText || 'Unknown';\n\n // Build a descriptive error with actionable guidance\n let msg: string;\n if (apiError) {\n msg = apiError;\n } else if (status === 500) {\n msg = `SkillDB API internal error (HTTP 500). The service may be experiencing issues. Check status at https://skilldb.dev/api/v1/status`;\n } else if (status === 503) {\n msg = `SkillDB API unavailable (HTTP 503). The skills index may not be loaded. Check status at https://skilldb.dev/api/v1/status`;\n } else if (status === 401) {\n msg = `Invalid or expired API key (HTTP 401). Get a free key at https://skilldb.dev/api-access`;\n } else if (status === 429) {\n msg = `Rate limit exceeded (HTTP 429). Authenticate with an API key for higher limits: https://skilldb.dev/api-access`;\n } else if (status === 404) {\n msg = `Resource not found (HTTP 404). The requested skill or endpoint does not exist.`;\n } else {\n msg = `SkillDB API error: HTTP ${status} ${statusText}. Check https://skilldb.dev/api/v1/status for service health.`;\n }\n throw new Error(msg);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Search skills by keyword. */\n async search(query: string, options?: Omit<SearchOptions, 'search'>): Promise<SkillsResponse> {\n return this.typedRequest<SkillsResponse>('/skills', {\n search: query,\n category: options?.category ?? '',\n pack: options?.pack ?? '',\n sort: options?.sort ?? '',\n limit: String(options?.limit ?? 20),\n offset: String(options?.offset ?? 0),\n include_content: options?.includeContent ? 'true' : '',\n });\n }\n\n /** List skills with optional filters and sorting. */\n async list(options?: SearchOptions): Promise<SkillsResponse> {\n return this.typedRequest<SkillsResponse>('/skills', {\n category: options?.category ?? '',\n pack: options?.pack ?? '',\n search: options?.search ?? '',\n sort: options?.sort ?? '',\n limit: String(options?.limit ?? 50),\n offset: String(options?.offset ?? 0),\n include_content: options?.includeContent ? 'true' : '',\n });\n }\n\n /** Get a single skill by ID (e.g. \"software-skills/code-review.md\"). */\n async get(id: string): Promise<Skill> {\n const encoded = encodeURIComponent(id);\n const res = await this.typedRequest<Skill | { skill: Skill }>(`/skills/${encoded}`, {\n include_content: 'true',\n });\n // Handle both direct and wrapped responses\n return 'skill' in res ? res.skill : res;\n }\n\n /** Batch retrieve multiple skills by IDs (max 50). */\n async batch(ids: string[]): Promise<SkillsResponse> {\n return this.typedRequest<SkillsResponse>('/skills', {\n ids: ids.slice(0, 50).join(','),\n include_content: 'true',\n });\n }\n\n /** Get search autocomplete suggestions. */\n async suggest(query: string): Promise<{ suggestions: Array<{ title: string; pack: string; category: string; id: string }> }> {\n return this.typedRequest('/skills/suggest', { q: query });\n }\n\n /** Validate that the configured API key works. */\n async validate(): Promise<boolean> {\n try {\n const url = `${this.baseUrl}/keys/usage`;\n const res = await fetch(url, { headers: this.headers() });\n return res.ok;\n } catch {\n return false;\n }\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { Manifest, Skill } from './types.js';\n\nconst SKILLDB_DIR = '.skilldb';\nconst SKILLS_DIR = 'skills';\nconst MANIFEST_FILE = 'manifest.json';\n\nfunction ensureDir(dir: string): void {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n}\n\nfunction skilldbRoot(cwd?: string): string {\n return path.join(cwd ?? process.cwd(), SKILLDB_DIR);\n}\n\n/** Ensure .skilldb/ directory structure exists. */\nexport function initCache(cwd?: string): string {\n const root = skilldbRoot(cwd);\n ensureDir(path.join(root, SKILLS_DIR));\n\n const manifestPath = path.join(root, MANIFEST_FILE);\n if (!fs.existsSync(manifestPath)) {\n fs.writeFileSync(manifestPath, JSON.stringify({ installed: {} }, null, 2) + '\\n');\n }\n\n return root;\n}\n\n/** Read the local manifest. */\nexport function readManifest(cwd?: string): Manifest {\n const manifestPath = path.join(skilldbRoot(cwd), MANIFEST_FILE);\n try {\n return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));\n } catch {\n return { installed: {} };\n }\n}\n\n/** Write the local manifest. */\nexport function writeManifest(manifest: Manifest, cwd?: string): void {\n const root = skilldbRoot(cwd);\n ensureDir(root);\n fs.writeFileSync(\n path.join(root, MANIFEST_FILE),\n JSON.stringify(manifest, null, 2) + '\\n'\n );\n}\n\n/** Save a skill to the local cache. */\nexport function cacheSkill(skill: Skill, cwd?: string): string {\n const root = skilldbRoot(cwd);\n const skillDir = path.join(root, SKILLS_DIR, skill.pack);\n ensureDir(skillDir);\n\n const safeName = skill.name.replace(/[/\\\\:*?\"<>|]/g, '-');\n const filePath = path.join(skillDir, `${safeName}.md`);\n fs.writeFileSync(filePath, skill.content ?? `# ${skill.title}\\n\\n${skill.description}\\n`);\n\n // Update manifest\n const manifest = readManifest(cwd);\n manifest.installed[skill.id] = {\n addedAt: new Date().toISOString(),\n lines: skill.lines,\n };\n writeManifest(manifest, cwd);\n\n return filePath;\n}\n\n/** Check if a skill is already cached. */\nexport function isCached(skillId: string, cwd?: string): boolean {\n const manifest = readManifest(cwd);\n return skillId in manifest.installed;\n}\n\n/** Get the local path for a cached skill. */\nexport function getCachedPath(skillId: string, cwd?: string): string | null {\n if (!isCached(skillId, cwd)) return null;\n const [pack, file] = skillId.split('/');\n const name = file.replace('.md', '').replace(/[/\\\\:*?\"<>|]/g, '-');\n const filePath = path.join(skilldbRoot(cwd), SKILLS_DIR, pack, `${name}.md`);\n return fs.existsSync(filePath) ? filePath : null;\n}\n\n/** List all cached skills. */\nexport function listCached(cwd?: string): Manifest {\n return readManifest(cwd);\n}\n\n/** Ensure .skilldb/ and .skilldbrc are in .gitignore. */\nexport function updateGitignore(cwd?: string): void {\n const root = cwd ?? process.cwd();\n const gitignorePath = path.join(root, '.gitignore');\n\n const entries = ['.skilldb/', '.skilldbrc'];\n let content = '';\n\n if (fs.existsSync(gitignorePath)) {\n content = fs.readFileSync(gitignorePath, 'utf-8');\n }\n\n const toAdd = entries.filter(e => !content.includes(e));\n if (toAdd.length === 0) return;\n\n const suffix = (content && !content.endsWith('\\n') ? '\\n' : '') +\n '\\n# SkillDB\\n' + toAdd.join('\\n') + '\\n';\n\n fs.writeFileSync(gitignorePath, content + suffix);\n}\n"],"mappings":"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","names":["fs","path","os","import_node_fs","import_node_path","fs","path"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -78,7 +78,9 @@ declare class SkillDBClient {
|
|
|
78
78
|
private baseUrl;
|
|
79
79
|
constructor(config?: ClientConfig);
|
|
80
80
|
private headers;
|
|
81
|
-
private
|
|
81
|
+
/** Make an authenticated request to any endpoint (used by MCP tools for private skills). */
|
|
82
|
+
rawRequest(endpoint: string, init?: RequestInit): Promise<Response>;
|
|
83
|
+
private typedRequest;
|
|
82
84
|
/** Search skills by keyword. */
|
|
83
85
|
search(query: string, options?: Omit<SearchOptions, 'search'>): Promise<SkillsResponse>;
|
|
84
86
|
/** List skills with optional filters and sorting. */
|
package/dist/index.d.ts
CHANGED
|
@@ -78,7 +78,9 @@ declare class SkillDBClient {
|
|
|
78
78
|
private baseUrl;
|
|
79
79
|
constructor(config?: ClientConfig);
|
|
80
80
|
private headers;
|
|
81
|
-
private
|
|
81
|
+
/** Make an authenticated request to any endpoint (used by MCP tools for private skills). */
|
|
82
|
+
rawRequest(endpoint: string, init?: RequestInit): Promise<Response>;
|
|
83
|
+
private typedRequest;
|
|
82
84
|
/** Search skills by keyword. */
|
|
83
85
|
search(query: string, options?: Omit<SearchOptions, 'search'>): Promise<SkillsResponse>;
|
|
84
86
|
/** List skills with optional filters and sorting. */
|
package/dist/index.js
CHANGED
|
@@ -49,7 +49,13 @@ var SkillDBClient = class {
|
|
|
49
49
|
}
|
|
50
50
|
return h;
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
/** Make an authenticated request to any endpoint (used by MCP tools for private skills). */
|
|
53
|
+
async rawRequest(endpoint, init) {
|
|
54
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
55
|
+
const headers = { ...this.headers(), ...init?.headers || {} };
|
|
56
|
+
return fetch(url, { ...init, headers });
|
|
57
|
+
}
|
|
58
|
+
async typedRequest(endpoint, params) {
|
|
53
59
|
const url = new URL(`${this.baseUrl}${endpoint}`);
|
|
54
60
|
if (params) {
|
|
55
61
|
for (const [k, v] of Object.entries(params)) {
|
|
@@ -58,15 +64,33 @@ var SkillDBClient = class {
|
|
|
58
64
|
}
|
|
59
65
|
const res = await fetch(url.toString(), { headers: this.headers() });
|
|
60
66
|
if (!res.ok) {
|
|
61
|
-
const body = await res.json().catch(() =>
|
|
62
|
-
const
|
|
67
|
+
const body = await res.json().catch(() => null);
|
|
68
|
+
const apiError = body && typeof body === "object" ? body.error : null;
|
|
69
|
+
const status = res.status;
|
|
70
|
+
const statusText = res.statusText || "Unknown";
|
|
71
|
+
let msg;
|
|
72
|
+
if (apiError) {
|
|
73
|
+
msg = apiError;
|
|
74
|
+
} else if (status === 500) {
|
|
75
|
+
msg = `SkillDB API internal error (HTTP 500). The service may be experiencing issues. Check status at https://skilldb.dev/api/v1/status`;
|
|
76
|
+
} else if (status === 503) {
|
|
77
|
+
msg = `SkillDB API unavailable (HTTP 503). The skills index may not be loaded. Check status at https://skilldb.dev/api/v1/status`;
|
|
78
|
+
} else if (status === 401) {
|
|
79
|
+
msg = `Invalid or expired API key (HTTP 401). Get a free key at https://skilldb.dev/api-access`;
|
|
80
|
+
} else if (status === 429) {
|
|
81
|
+
msg = `Rate limit exceeded (HTTP 429). Authenticate with an API key for higher limits: https://skilldb.dev/api-access`;
|
|
82
|
+
} else if (status === 404) {
|
|
83
|
+
msg = `Resource not found (HTTP 404). The requested skill or endpoint does not exist.`;
|
|
84
|
+
} else {
|
|
85
|
+
msg = `SkillDB API error: HTTP ${status} ${statusText}. Check https://skilldb.dev/api/v1/status for service health.`;
|
|
86
|
+
}
|
|
63
87
|
throw new Error(msg);
|
|
64
88
|
}
|
|
65
89
|
return res.json();
|
|
66
90
|
}
|
|
67
91
|
/** Search skills by keyword. */
|
|
68
92
|
async search(query, options) {
|
|
69
|
-
return this.
|
|
93
|
+
return this.typedRequest("/skills", {
|
|
70
94
|
search: query,
|
|
71
95
|
category: options?.category ?? "",
|
|
72
96
|
pack: options?.pack ?? "",
|
|
@@ -78,7 +102,7 @@ var SkillDBClient = class {
|
|
|
78
102
|
}
|
|
79
103
|
/** List skills with optional filters and sorting. */
|
|
80
104
|
async list(options) {
|
|
81
|
-
return this.
|
|
105
|
+
return this.typedRequest("/skills", {
|
|
82
106
|
category: options?.category ?? "",
|
|
83
107
|
pack: options?.pack ?? "",
|
|
84
108
|
search: options?.search ?? "",
|
|
@@ -91,21 +115,21 @@ var SkillDBClient = class {
|
|
|
91
115
|
/** Get a single skill by ID (e.g. "software-skills/code-review.md"). */
|
|
92
116
|
async get(id) {
|
|
93
117
|
const encoded = encodeURIComponent(id);
|
|
94
|
-
const res = await this.
|
|
118
|
+
const res = await this.typedRequest(`/skills/${encoded}`, {
|
|
95
119
|
include_content: "true"
|
|
96
120
|
});
|
|
97
121
|
return "skill" in res ? res.skill : res;
|
|
98
122
|
}
|
|
99
123
|
/** Batch retrieve multiple skills by IDs (max 50). */
|
|
100
124
|
async batch(ids) {
|
|
101
|
-
return this.
|
|
125
|
+
return this.typedRequest("/skills", {
|
|
102
126
|
ids: ids.slice(0, 50).join(","),
|
|
103
127
|
include_content: "true"
|
|
104
128
|
});
|
|
105
129
|
}
|
|
106
130
|
/** Get search autocomplete suggestions. */
|
|
107
131
|
async suggest(query) {
|
|
108
|
-
return this.
|
|
132
|
+
return this.typedRequest("/skills/suggest", { q: query });
|
|
109
133
|
}
|
|
110
134
|
/** Validate that the configured API key works. */
|
|
111
135
|
async validate() {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/client.ts","../src/cache.ts","../src/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\n\nconst RC_FILE = '.skilldbrc';\n\nexport const DEFAULT_BASE_URL = 'https://skilldb.dev/api/v1';\n\ninterface RcConfig {\n apiKey?: string;\n}\n\nfunction readJson(filePath: string): RcConfig | null {\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n return JSON.parse(raw);\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve API key from (in priority order):\n * 1. SKILLDB_API_KEY env var\n * 2. .skilldbrc in project root (cwd)\n * 3. ~/.skilldbrc in home dir\n */\nexport function resolveApiKey(): string | undefined {\n if (process.env.SKILLDB_API_KEY) {\n return process.env.SKILLDB_API_KEY;\n }\n\n const projectRc = path.join(process.cwd(), RC_FILE);\n const projectConfig = readJson(projectRc);\n if (projectConfig?.apiKey) return projectConfig.apiKey;\n\n const homeRc = path.join(os.homedir(), RC_FILE);\n const homeConfig = readJson(homeRc);\n if (homeConfig?.apiKey) return homeConfig.apiKey;\n\n return undefined;\n}\n\n/**\n * Resolve base URL from env or default.\n */\nexport function resolveBaseUrl(): string {\n return process.env.SKILLDB_API_URL || DEFAULT_BASE_URL;\n}\n\n/**\n * Save API key to ~/.skilldbrc (user-wide) or project .skilldbrc.\n */\nexport function saveApiKey(apiKey: string, global = true): string {\n const target = global\n ? path.join(os.homedir(), RC_FILE)\n : path.join(process.cwd(), RC_FILE);\n\n const existing = readJson(target) || {};\n fs.writeFileSync(target, JSON.stringify({ ...existing, apiKey }, null, 2) + '\\n', 'utf-8');\n return target;\n}\n","import type { ClientConfig, Skill, SkillsResponse, SearchOptions } from './types.js';\nimport { resolveApiKey, resolveBaseUrl } from './config.js';\n\nexport class SkillDBClient {\n private apiKey?: string;\n private baseUrl: string;\n\n constructor(config?: ClientConfig) {\n this.apiKey = config?.apiKey ?? resolveApiKey();\n this.baseUrl = config?.baseUrl ?? resolveBaseUrl();\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this.apiKey) {\n h['Authorization'] = `Bearer ${this.apiKey}`;\n }\n return h;\n }\n\n private async request<T>(endpoint: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${endpoint}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== '') url.searchParams.set(k, v);\n }\n }\n\n const res = await fetch(url.toString(), { headers: this.headers() });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n const msg = (body as Record<string, string>).error || `HTTP ${res.status}`;\n throw new Error(msg);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Search skills by keyword. */\n async search(query: string, options?: Omit<SearchOptions, 'search'>): Promise<SkillsResponse> {\n return this.request<SkillsResponse>('/skills', {\n search: query,\n category: options?.category ?? '',\n pack: options?.pack ?? '',\n sort: options?.sort ?? '',\n limit: String(options?.limit ?? 20),\n offset: String(options?.offset ?? 0),\n include_content: options?.includeContent ? 'true' : '',\n });\n }\n\n /** List skills with optional filters and sorting. */\n async list(options?: SearchOptions): Promise<SkillsResponse> {\n return this.request<SkillsResponse>('/skills', {\n category: options?.category ?? '',\n pack: options?.pack ?? '',\n search: options?.search ?? '',\n sort: options?.sort ?? '',\n limit: String(options?.limit ?? 50),\n offset: String(options?.offset ?? 0),\n include_content: options?.includeContent ? 'true' : '',\n });\n }\n\n /** Get a single skill by ID (e.g. \"software-skills/code-review.md\"). */\n async get(id: string): Promise<Skill> {\n const encoded = encodeURIComponent(id);\n const res = await this.request<Skill | { skill: Skill }>(`/skills/${encoded}`, {\n include_content: 'true',\n });\n // Handle both direct and wrapped responses\n return 'skill' in res ? res.skill : res;\n }\n\n /** Batch retrieve multiple skills by IDs (max 50). */\n async batch(ids: string[]): Promise<SkillsResponse> {\n return this.request<SkillsResponse>('/skills', {\n ids: ids.slice(0, 50).join(','),\n include_content: 'true',\n });\n }\n\n /** Get search autocomplete suggestions. */\n async suggest(query: string): Promise<{ suggestions: Array<{ title: string; pack: string; category: string; id: string }> }> {\n return this.request('/skills/suggest', { q: query });\n }\n\n /** Validate that the configured API key works. */\n async validate(): Promise<boolean> {\n try {\n const url = `${this.baseUrl}/keys/usage`;\n const res = await fetch(url, { headers: this.headers() });\n return res.ok;\n } catch {\n return false;\n }\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { Manifest, Skill } from './types.js';\n\nconst SKILLDB_DIR = '.skilldb';\nconst SKILLS_DIR = 'skills';\nconst MANIFEST_FILE = 'manifest.json';\n\nfunction ensureDir(dir: string): void {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n}\n\nfunction skilldbRoot(cwd?: string): string {\n return path.join(cwd ?? process.cwd(), SKILLDB_DIR);\n}\n\n/** Ensure .skilldb/ directory structure exists. */\nexport function initCache(cwd?: string): string {\n const root = skilldbRoot(cwd);\n ensureDir(path.join(root, SKILLS_DIR));\n\n const manifestPath = path.join(root, MANIFEST_FILE);\n if (!fs.existsSync(manifestPath)) {\n fs.writeFileSync(manifestPath, JSON.stringify({ installed: {} }, null, 2) + '\\n');\n }\n\n return root;\n}\n\n/** Read the local manifest. */\nexport function readManifest(cwd?: string): Manifest {\n const manifestPath = path.join(skilldbRoot(cwd), MANIFEST_FILE);\n try {\n return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));\n } catch {\n return { installed: {} };\n }\n}\n\n/** Write the local manifest. */\nexport function writeManifest(manifest: Manifest, cwd?: string): void {\n const root = skilldbRoot(cwd);\n ensureDir(root);\n fs.writeFileSync(\n path.join(root, MANIFEST_FILE),\n JSON.stringify(manifest, null, 2) + '\\n'\n );\n}\n\n/** Save a skill to the local cache. */\nexport function cacheSkill(skill: Skill, cwd?: string): string {\n const root = skilldbRoot(cwd);\n const skillDir = path.join(root, SKILLS_DIR, skill.pack);\n ensureDir(skillDir);\n\n const safeName = skill.name.replace(/[/\\\\:*?\"<>|]/g, '-');\n const filePath = path.join(skillDir, `${safeName}.md`);\n fs.writeFileSync(filePath, skill.content ?? `# ${skill.title}\\n\\n${skill.description}\\n`);\n\n // Update manifest\n const manifest = readManifest(cwd);\n manifest.installed[skill.id] = {\n addedAt: new Date().toISOString(),\n lines: skill.lines,\n };\n writeManifest(manifest, cwd);\n\n return filePath;\n}\n\n/** Check if a skill is already cached. */\nexport function isCached(skillId: string, cwd?: string): boolean {\n const manifest = readManifest(cwd);\n return skillId in manifest.installed;\n}\n\n/** Get the local path for a cached skill. */\nexport function getCachedPath(skillId: string, cwd?: string): string | null {\n if (!isCached(skillId, cwd)) return null;\n const [pack, file] = skillId.split('/');\n const name = file.replace('.md', '').replace(/[/\\\\:*?\"<>|]/g, '-');\n const filePath = path.join(skilldbRoot(cwd), SKILLS_DIR, pack, `${name}.md`);\n return fs.existsSync(filePath) ? filePath : null;\n}\n\n/** List all cached skills. */\nexport function listCached(cwd?: string): Manifest {\n return readManifest(cwd);\n}\n\n/** Ensure .skilldb/ and .skilldbrc are in .gitignore. */\nexport function updateGitignore(cwd?: string): void {\n const root = cwd ?? process.cwd();\n const gitignorePath = path.join(root, '.gitignore');\n\n const entries = ['.skilldb/', '.skilldbrc'];\n let content = '';\n\n if (fs.existsSync(gitignorePath)) {\n content = fs.readFileSync(gitignorePath, 'utf-8');\n }\n\n const toAdd = entries.filter(e => !content.includes(e));\n if (toAdd.length === 0) return;\n\n const suffix = (content && !content.endsWith('\\n') ? '\\n' : '') +\n '\\n# SkillDB\\n' + toAdd.join('\\n') + '\\n';\n\n fs.writeFileSync(gitignorePath, content + suffix);\n}\n","export { SkillDBClient } from './client.js';\nexport type {\n Skill,\n PackInfo,\n CategoryInfo,\n SkillsResponse,\n SearchOptions,\n ClientConfig,\n Manifest,\n ApiKeyScope,\n ApiKeyInfo,\n} from './types.js';\nexport { resolveApiKey, resolveBaseUrl, saveApiKey } from './config.js';\nexport { initCache, cacheSkill, isCached, getCachedPath, listCached } from './cache.js';\n\nimport type { ClientConfig } from './types.js';\nimport { SkillDBClient } from './client.js';\n\n/** Create a SkillDB client. Auto-loads API key from env/config. */\nexport function createClient(config?: ClientConfig): SkillDBClient {\n return new SkillDBClient(config);\n}\n"],"mappings":"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","names":["fs","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/client.ts","../src/cache.ts","../src/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\n\nconst RC_FILE = '.skilldbrc';\n\nexport const DEFAULT_BASE_URL = 'https://skilldb.dev/api/v1';\n\ninterface RcConfig {\n apiKey?: string;\n}\n\nfunction readJson(filePath: string): RcConfig | null {\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n return JSON.parse(raw);\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve API key from (in priority order):\n * 1. SKILLDB_API_KEY env var\n * 2. .skilldbrc in project root (cwd)\n * 3. ~/.skilldbrc in home dir\n */\nexport function resolveApiKey(): string | undefined {\n if (process.env.SKILLDB_API_KEY) {\n return process.env.SKILLDB_API_KEY;\n }\n\n const projectRc = path.join(process.cwd(), RC_FILE);\n const projectConfig = readJson(projectRc);\n if (projectConfig?.apiKey) return projectConfig.apiKey;\n\n const homeRc = path.join(os.homedir(), RC_FILE);\n const homeConfig = readJson(homeRc);\n if (homeConfig?.apiKey) return homeConfig.apiKey;\n\n return undefined;\n}\n\n/**\n * Resolve base URL from env or default.\n */\nexport function resolveBaseUrl(): string {\n return process.env.SKILLDB_API_URL || DEFAULT_BASE_URL;\n}\n\n/**\n * Save API key to ~/.skilldbrc (user-wide) or project .skilldbrc.\n */\nexport function saveApiKey(apiKey: string, global = true): string {\n const target = global\n ? path.join(os.homedir(), RC_FILE)\n : path.join(process.cwd(), RC_FILE);\n\n const existing = readJson(target) || {};\n fs.writeFileSync(target, JSON.stringify({ ...existing, apiKey }, null, 2) + '\\n', 'utf-8');\n return target;\n}\n","import type { ClientConfig, Skill, SkillsResponse, SearchOptions } from './types.js';\nimport { resolveApiKey, resolveBaseUrl } from './config.js';\n\nexport class SkillDBClient {\n private apiKey?: string;\n private baseUrl: string;\n\n constructor(config?: ClientConfig) {\n this.apiKey = config?.apiKey ?? resolveApiKey();\n this.baseUrl = config?.baseUrl ?? resolveBaseUrl();\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this.apiKey) {\n h['Authorization'] = `Bearer ${this.apiKey}`;\n }\n return h;\n }\n\n /** Make an authenticated request to any endpoint (used by MCP tools for private skills). */\n public async rawRequest(endpoint: string, init?: RequestInit): Promise<Response> {\n const url = `${this.baseUrl}${endpoint}`;\n const headers = { ...this.headers(), ...(init?.headers || {}) };\n return fetch(url, { ...init, headers });\n }\n\n\n private async typedRequest<T>(endpoint: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${endpoint}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== '') url.searchParams.set(k, v);\n }\n }\n\n const res = await fetch(url.toString(), { headers: this.headers() });\n\n if (!res.ok) {\n const body = await res.json().catch(() => null);\n const apiError = body && typeof body === 'object' ? (body as Record<string, string>).error : null;\n const status = res.status;\n const statusText = res.statusText || 'Unknown';\n\n // Build a descriptive error with actionable guidance\n let msg: string;\n if (apiError) {\n msg = apiError;\n } else if (status === 500) {\n msg = `SkillDB API internal error (HTTP 500). The service may be experiencing issues. Check status at https://skilldb.dev/api/v1/status`;\n } else if (status === 503) {\n msg = `SkillDB API unavailable (HTTP 503). The skills index may not be loaded. Check status at https://skilldb.dev/api/v1/status`;\n } else if (status === 401) {\n msg = `Invalid or expired API key (HTTP 401). Get a free key at https://skilldb.dev/api-access`;\n } else if (status === 429) {\n msg = `Rate limit exceeded (HTTP 429). Authenticate with an API key for higher limits: https://skilldb.dev/api-access`;\n } else if (status === 404) {\n msg = `Resource not found (HTTP 404). The requested skill or endpoint does not exist.`;\n } else {\n msg = `SkillDB API error: HTTP ${status} ${statusText}. Check https://skilldb.dev/api/v1/status for service health.`;\n }\n throw new Error(msg);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Search skills by keyword. */\n async search(query: string, options?: Omit<SearchOptions, 'search'>): Promise<SkillsResponse> {\n return this.typedRequest<SkillsResponse>('/skills', {\n search: query,\n category: options?.category ?? '',\n pack: options?.pack ?? '',\n sort: options?.sort ?? '',\n limit: String(options?.limit ?? 20),\n offset: String(options?.offset ?? 0),\n include_content: options?.includeContent ? 'true' : '',\n });\n }\n\n /** List skills with optional filters and sorting. */\n async list(options?: SearchOptions): Promise<SkillsResponse> {\n return this.typedRequest<SkillsResponse>('/skills', {\n category: options?.category ?? '',\n pack: options?.pack ?? '',\n search: options?.search ?? '',\n sort: options?.sort ?? '',\n limit: String(options?.limit ?? 50),\n offset: String(options?.offset ?? 0),\n include_content: options?.includeContent ? 'true' : '',\n });\n }\n\n /** Get a single skill by ID (e.g. \"software-skills/code-review.md\"). */\n async get(id: string): Promise<Skill> {\n const encoded = encodeURIComponent(id);\n const res = await this.typedRequest<Skill | { skill: Skill }>(`/skills/${encoded}`, {\n include_content: 'true',\n });\n // Handle both direct and wrapped responses\n return 'skill' in res ? res.skill : res;\n }\n\n /** Batch retrieve multiple skills by IDs (max 50). */\n async batch(ids: string[]): Promise<SkillsResponse> {\n return this.typedRequest<SkillsResponse>('/skills', {\n ids: ids.slice(0, 50).join(','),\n include_content: 'true',\n });\n }\n\n /** Get search autocomplete suggestions. */\n async suggest(query: string): Promise<{ suggestions: Array<{ title: string; pack: string; category: string; id: string }> }> {\n return this.typedRequest('/skills/suggest', { q: query });\n }\n\n /** Validate that the configured API key works. */\n async validate(): Promise<boolean> {\n try {\n const url = `${this.baseUrl}/keys/usage`;\n const res = await fetch(url, { headers: this.headers() });\n return res.ok;\n } catch {\n return false;\n }\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { Manifest, Skill } from './types.js';\n\nconst SKILLDB_DIR = '.skilldb';\nconst SKILLS_DIR = 'skills';\nconst MANIFEST_FILE = 'manifest.json';\n\nfunction ensureDir(dir: string): void {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n}\n\nfunction skilldbRoot(cwd?: string): string {\n return path.join(cwd ?? process.cwd(), SKILLDB_DIR);\n}\n\n/** Ensure .skilldb/ directory structure exists. */\nexport function initCache(cwd?: string): string {\n const root = skilldbRoot(cwd);\n ensureDir(path.join(root, SKILLS_DIR));\n\n const manifestPath = path.join(root, MANIFEST_FILE);\n if (!fs.existsSync(manifestPath)) {\n fs.writeFileSync(manifestPath, JSON.stringify({ installed: {} }, null, 2) + '\\n');\n }\n\n return root;\n}\n\n/** Read the local manifest. */\nexport function readManifest(cwd?: string): Manifest {\n const manifestPath = path.join(skilldbRoot(cwd), MANIFEST_FILE);\n try {\n return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));\n } catch {\n return { installed: {} };\n }\n}\n\n/** Write the local manifest. */\nexport function writeManifest(manifest: Manifest, cwd?: string): void {\n const root = skilldbRoot(cwd);\n ensureDir(root);\n fs.writeFileSync(\n path.join(root, MANIFEST_FILE),\n JSON.stringify(manifest, null, 2) + '\\n'\n );\n}\n\n/** Save a skill to the local cache. */\nexport function cacheSkill(skill: Skill, cwd?: string): string {\n const root = skilldbRoot(cwd);\n const skillDir = path.join(root, SKILLS_DIR, skill.pack);\n ensureDir(skillDir);\n\n const safeName = skill.name.replace(/[/\\\\:*?\"<>|]/g, '-');\n const filePath = path.join(skillDir, `${safeName}.md`);\n fs.writeFileSync(filePath, skill.content ?? `# ${skill.title}\\n\\n${skill.description}\\n`);\n\n // Update manifest\n const manifest = readManifest(cwd);\n manifest.installed[skill.id] = {\n addedAt: new Date().toISOString(),\n lines: skill.lines,\n };\n writeManifest(manifest, cwd);\n\n return filePath;\n}\n\n/** Check if a skill is already cached. */\nexport function isCached(skillId: string, cwd?: string): boolean {\n const manifest = readManifest(cwd);\n return skillId in manifest.installed;\n}\n\n/** Get the local path for a cached skill. */\nexport function getCachedPath(skillId: string, cwd?: string): string | null {\n if (!isCached(skillId, cwd)) return null;\n const [pack, file] = skillId.split('/');\n const name = file.replace('.md', '').replace(/[/\\\\:*?\"<>|]/g, '-');\n const filePath = path.join(skilldbRoot(cwd), SKILLS_DIR, pack, `${name}.md`);\n return fs.existsSync(filePath) ? filePath : null;\n}\n\n/** List all cached skills. */\nexport function listCached(cwd?: string): Manifest {\n return readManifest(cwd);\n}\n\n/** Ensure .skilldb/ and .skilldbrc are in .gitignore. */\nexport function updateGitignore(cwd?: string): void {\n const root = cwd ?? process.cwd();\n const gitignorePath = path.join(root, '.gitignore');\n\n const entries = ['.skilldb/', '.skilldbrc'];\n let content = '';\n\n if (fs.existsSync(gitignorePath)) {\n content = fs.readFileSync(gitignorePath, 'utf-8');\n }\n\n const toAdd = entries.filter(e => !content.includes(e));\n if (toAdd.length === 0) return;\n\n const suffix = (content && !content.endsWith('\\n') ? '\\n' : '') +\n '\\n# SkillDB\\n' + toAdd.join('\\n') + '\\n';\n\n fs.writeFileSync(gitignorePath, content + suffix);\n}\n","export { SkillDBClient } from './client.js';\nexport type {\n Skill,\n PackInfo,\n CategoryInfo,\n SkillsResponse,\n SearchOptions,\n ClientConfig,\n Manifest,\n ApiKeyScope,\n ApiKeyInfo,\n} from './types.js';\nexport { resolveApiKey, resolveBaseUrl, saveApiKey } from './config.js';\nexport { initCache, cacheSkill, isCached, getCachedPath, listCached } from './cache.js';\n\nimport type { ClientConfig } from './types.js';\nimport { SkillDBClient } from './client.js';\n\n/** Create a SkillDB client. Auto-loads API key from env/config. */\nexport function createClient(config?: ClientConfig): SkillDBClient {\n return new SkillDBClient(config);\n}\n"],"mappings":"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","names":["fs","path"]}
|
package/dist/mcp.js
CHANGED
|
@@ -50,7 +50,13 @@ var SkillDBClient = class {
|
|
|
50
50
|
}
|
|
51
51
|
return h;
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
/** Make an authenticated request to any endpoint (used by MCP tools for private skills). */
|
|
54
|
+
async rawRequest(endpoint, init) {
|
|
55
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
56
|
+
const headers = { ...this.headers(), ...init?.headers || {} };
|
|
57
|
+
return fetch(url, { ...init, headers });
|
|
58
|
+
}
|
|
59
|
+
async typedRequest(endpoint, params) {
|
|
54
60
|
const url = new URL(`${this.baseUrl}${endpoint}`);
|
|
55
61
|
if (params) {
|
|
56
62
|
for (const [k, v] of Object.entries(params)) {
|
|
@@ -59,15 +65,33 @@ var SkillDBClient = class {
|
|
|
59
65
|
}
|
|
60
66
|
const res = await fetch(url.toString(), { headers: this.headers() });
|
|
61
67
|
if (!res.ok) {
|
|
62
|
-
const body = await res.json().catch(() =>
|
|
63
|
-
const
|
|
68
|
+
const body = await res.json().catch(() => null);
|
|
69
|
+
const apiError = body && typeof body === "object" ? body.error : null;
|
|
70
|
+
const status = res.status;
|
|
71
|
+
const statusText = res.statusText || "Unknown";
|
|
72
|
+
let msg;
|
|
73
|
+
if (apiError) {
|
|
74
|
+
msg = apiError;
|
|
75
|
+
} else if (status === 500) {
|
|
76
|
+
msg = `SkillDB API internal error (HTTP 500). The service may be experiencing issues. Check status at https://skilldb.dev/api/v1/status`;
|
|
77
|
+
} else if (status === 503) {
|
|
78
|
+
msg = `SkillDB API unavailable (HTTP 503). The skills index may not be loaded. Check status at https://skilldb.dev/api/v1/status`;
|
|
79
|
+
} else if (status === 401) {
|
|
80
|
+
msg = `Invalid or expired API key (HTTP 401). Get a free key at https://skilldb.dev/api-access`;
|
|
81
|
+
} else if (status === 429) {
|
|
82
|
+
msg = `Rate limit exceeded (HTTP 429). Authenticate with an API key for higher limits: https://skilldb.dev/api-access`;
|
|
83
|
+
} else if (status === 404) {
|
|
84
|
+
msg = `Resource not found (HTTP 404). The requested skill or endpoint does not exist.`;
|
|
85
|
+
} else {
|
|
86
|
+
msg = `SkillDB API error: HTTP ${status} ${statusText}. Check https://skilldb.dev/api/v1/status for service health.`;
|
|
87
|
+
}
|
|
64
88
|
throw new Error(msg);
|
|
65
89
|
}
|
|
66
90
|
return res.json();
|
|
67
91
|
}
|
|
68
92
|
/** Search skills by keyword. */
|
|
69
93
|
async search(query, options) {
|
|
70
|
-
return this.
|
|
94
|
+
return this.typedRequest("/skills", {
|
|
71
95
|
search: query,
|
|
72
96
|
category: options?.category ?? "",
|
|
73
97
|
pack: options?.pack ?? "",
|
|
@@ -79,7 +103,7 @@ var SkillDBClient = class {
|
|
|
79
103
|
}
|
|
80
104
|
/** List skills with optional filters and sorting. */
|
|
81
105
|
async list(options) {
|
|
82
|
-
return this.
|
|
106
|
+
return this.typedRequest("/skills", {
|
|
83
107
|
category: options?.category ?? "",
|
|
84
108
|
pack: options?.pack ?? "",
|
|
85
109
|
search: options?.search ?? "",
|
|
@@ -92,21 +116,21 @@ var SkillDBClient = class {
|
|
|
92
116
|
/** Get a single skill by ID (e.g. "software-skills/code-review.md"). */
|
|
93
117
|
async get(id) {
|
|
94
118
|
const encoded = encodeURIComponent(id);
|
|
95
|
-
const res = await this.
|
|
119
|
+
const res = await this.typedRequest(`/skills/${encoded}`, {
|
|
96
120
|
include_content: "true"
|
|
97
121
|
});
|
|
98
122
|
return "skill" in res ? res.skill : res;
|
|
99
123
|
}
|
|
100
124
|
/** Batch retrieve multiple skills by IDs (max 50). */
|
|
101
125
|
async batch(ids) {
|
|
102
|
-
return this.
|
|
126
|
+
return this.typedRequest("/skills", {
|
|
103
127
|
ids: ids.slice(0, 50).join(","),
|
|
104
128
|
include_content: "true"
|
|
105
129
|
});
|
|
106
130
|
}
|
|
107
131
|
/** Get search autocomplete suggestions. */
|
|
108
132
|
async suggest(query) {
|
|
109
|
-
return this.
|
|
133
|
+
return this.typedRequest("/skills/suggest", { q: query });
|
|
110
134
|
}
|
|
111
135
|
/** Validate that the configured API key works. */
|
|
112
136
|
async validate() {
|
|
@@ -129,13 +153,47 @@ var hasApiKey = !!apiKey;
|
|
|
129
153
|
var client = new SkillDBClient({ apiKey, baseUrl });
|
|
130
154
|
if (!hasApiKey) {
|
|
131
155
|
process.stderr.write(
|
|
132
|
-
"\n\u26A0\uFE0F SkillDB MCP: No API key configured.\n You can search and browse skills, but full content requires
|
|
156
|
+
"\n\u26A0\uFE0F SkillDB MCP: No API key configured.\n You can search and browse skills, but full content requires an API key.\n Get a free key at: https://skilldb.dev/api-access\n Then use: skilldb_set_key to configure it (no restart needed)\n Or restart with: claude mcp remove skilldb && claude mcp add skilldb -- skilldb-mcp --api-key YOUR_KEY\n\n"
|
|
133
157
|
);
|
|
134
158
|
}
|
|
135
159
|
var server = new McpServer({
|
|
136
160
|
name: "skilldb",
|
|
137
|
-
version: "0.
|
|
161
|
+
version: "0.7.0"
|
|
138
162
|
});
|
|
163
|
+
server.registerTool(
|
|
164
|
+
"skilldb_set_key",
|
|
165
|
+
{
|
|
166
|
+
title: "Set SkillDB API Key",
|
|
167
|
+
description: "Configure your SkillDB API key without restarting the session. Once set, all subsequent calls (search, get, list) will include full skill content. Get a free key at skilldb.dev/api-access.",
|
|
168
|
+
inputSchema: z.object({
|
|
169
|
+
key: z.string().describe("Your SkillDB API key (starts with sk_live_ or sk_test_)")
|
|
170
|
+
})
|
|
171
|
+
},
|
|
172
|
+
async ({ key }) => {
|
|
173
|
+
if (!key.startsWith("sk_")) {
|
|
174
|
+
return { content: [{ type: "text", text: "Invalid key format. SkillDB API keys start with `sk_live_` or `sk_test_`. Get one at https://skilldb.dev/api-access" }], isError: true };
|
|
175
|
+
}
|
|
176
|
+
apiKey = key;
|
|
177
|
+
hasApiKey = true;
|
|
178
|
+
client = new SkillDBClient({ apiKey: key, baseUrl });
|
|
179
|
+
try {
|
|
180
|
+
const valid = await client.validate();
|
|
181
|
+
if (valid) {
|
|
182
|
+
return { content: [{ type: "text", text: `\u2705 **API key configured successfully!**
|
|
183
|
+
|
|
184
|
+
All SkillDB tools now return full skill content (instructions, patterns, best practices, code examples).
|
|
185
|
+
|
|
186
|
+
Try: \`skilldb_search\` or \`skilldb_get\` to load skills with full content.` }] };
|
|
187
|
+
} else {
|
|
188
|
+
return { content: [{ type: "text", text: `\u26A0\uFE0F **Key set but validation failed.** The key may be invalid or expired. Calls will still be attempted.
|
|
189
|
+
|
|
190
|
+
Get a new key at https://skilldb.dev/api-access` }] };
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
return { content: [{ type: "text", text: `\u2705 **API key set.** Couldn't validate (API may be temporarily unavailable), but the key will be used for all subsequent calls.` }] };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
);
|
|
139
197
|
server.registerTool(
|
|
140
198
|
"skilldb_search",
|
|
141
199
|
{
|
|
@@ -159,7 +217,7 @@ server.registerTool(
|
|
|
159
217
|
const noKey = !hasApiKey;
|
|
160
218
|
const text = res.skills.length === 0 ? `No skills found for "${query}".` : (noKey ? `\u26A0\uFE0F **No API key configured** \u2014 showing metadata only. Full skill content (instructions, patterns, code examples) requires a free API key.
|
|
161
219
|
\u{1F449} Get yours in 30 seconds: https://skilldb.dev/api-access
|
|
162
|
-
\u{1F449} Then
|
|
220
|
+
\u{1F449} Then use \`skilldb_set_key\` with your key (no restart needed)
|
|
163
221
|
|
|
164
222
|
---
|
|
165
223
|
|
|
@@ -174,7 +232,7 @@ server.registerTool(
|
|
|
174
232
|
---
|
|
175
233
|
Found ${res.pagination.total} total results. Showing ${res.skills.length}.` + (noKey ? `
|
|
176
234
|
|
|
177
|
-
\u{1F511} **To unlock full content:** Get a free API key at https://skilldb.dev/api-access
|
|
235
|
+
\u{1F511} **To unlock full content:** Get a free API key at https://skilldb.dev/api-access then use \`skilldb_set_key\`` : "");
|
|
178
236
|
return { content: [{ type: "text", text }] };
|
|
179
237
|
} catch (e) {
|
|
180
238
|
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
@@ -210,7 +268,7 @@ ${skill.description}
|
|
|
210
268
|
|
|
211
269
|
To load the complete ${skill.lines}-line skill with instructions, patterns, and best practices:
|
|
212
270
|
1. Get a free API key at https://skilldb.dev/api-access
|
|
213
|
-
2.
|
|
271
|
+
2. Use \`skilldb_set_key\` with your key (no restart needed)
|
|
214
272
|
|
|
215
273
|
Free keys get 100 calls/month. Pro ($9/mo) gets unlimited access.`;
|
|
216
274
|
}
|
|
@@ -463,6 +521,123 @@ server.registerTool(
|
|
|
463
521
|
}
|
|
464
522
|
}
|
|
465
523
|
);
|
|
524
|
+
server.registerTool(
|
|
525
|
+
"skilldb_my_skills",
|
|
526
|
+
{
|
|
527
|
+
title: "List My Private Skills",
|
|
528
|
+
description: "List your private skills stored on SkillDB. Requires an API key with 'write' scope. Returns all skills you own plus skills shared with you.",
|
|
529
|
+
inputSchema: z.object({})
|
|
530
|
+
},
|
|
531
|
+
async () => {
|
|
532
|
+
if (!apiKey) {
|
|
533
|
+
return { content: [{ type: "text", text: "API key required for private skills. Set one with: skilldb-mcp --api-key sk_live_xxx" }], isError: true };
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const res = await client.rawRequest("/api/v1/my-skills", { method: "GET" });
|
|
537
|
+
const data = await res.json();
|
|
538
|
+
if (!res.ok) return { content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }], isError: true };
|
|
539
|
+
const own = data.skills || [];
|
|
540
|
+
const shared = data.shared || [];
|
|
541
|
+
if (own.length === 0 && shared.length === 0) {
|
|
542
|
+
return { content: [{ type: "text", text: "No private skills yet. Create one with `skilldb_create_skill`." }] };
|
|
543
|
+
}
|
|
544
|
+
let text = `## My Private Skills (${own.length})
|
|
545
|
+
|
|
546
|
+
`;
|
|
547
|
+
text += own.map((s, i) => `${i + 1}. **${s.title}** (${s.pack})
|
|
548
|
+
ID: \`${s.id}\` | ${s.lines} lines | ${s.visibility}`).join("\n\n");
|
|
549
|
+
if (shared.length > 0) {
|
|
550
|
+
text += `
|
|
551
|
+
|
|
552
|
+
## Shared With Me (${shared.length})
|
|
553
|
+
|
|
554
|
+
`;
|
|
555
|
+
text += shared.map((s, i) => `${i + 1}. **${s.title}** (${s.pack})
|
|
556
|
+
ID: \`${s.id}\` | ${s.lines} lines | shared by ${s.owner}`).join("\n\n");
|
|
557
|
+
}
|
|
558
|
+
return { content: [{ type: "text", text }] };
|
|
559
|
+
} catch (e) {
|
|
560
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
);
|
|
564
|
+
server.registerTool(
|
|
565
|
+
"skilldb_create_skill",
|
|
566
|
+
{
|
|
567
|
+
title: "Create Private Skill",
|
|
568
|
+
description: "Create a new private skill in your SkillDB account. The skill is only visible to you (and anyone you share it with). Requires an API key with 'write' scope.",
|
|
569
|
+
inputSchema: z.object({
|
|
570
|
+
name: z.string().describe("Skill filename (e.g. 'my-react-patterns')"),
|
|
571
|
+
title: z.string().describe("Human-readable title"),
|
|
572
|
+
content: z.string().describe("Full skill content in markdown"),
|
|
573
|
+
pack: z.string().optional().default("personal").describe("Pack/folder name (default: 'personal')"),
|
|
574
|
+
tags: z.array(z.string()).optional().describe("Tags for searchability"),
|
|
575
|
+
description: z.string().optional().describe("Short description (auto-generated from content if omitted)")
|
|
576
|
+
})
|
|
577
|
+
},
|
|
578
|
+
async ({ name, title, content, pack, tags, description }) => {
|
|
579
|
+
if (!apiKey) {
|
|
580
|
+
return { content: [{ type: "text", text: "API key required. Set one with: skilldb-mcp --api-key sk_live_xxx" }], isError: true };
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
const res = await client.rawRequest("/api/v1/my-skills", {
|
|
584
|
+
method: "POST",
|
|
585
|
+
headers: { "Content-Type": "application/json" },
|
|
586
|
+
body: JSON.stringify({ name, title, content, pack, tags, description })
|
|
587
|
+
});
|
|
588
|
+
const data = await res.json();
|
|
589
|
+
if (!res.ok) return { content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }], isError: true };
|
|
590
|
+
const s = data.skill;
|
|
591
|
+
return { content: [{ type: "text", text: `\u2705 Private skill created!
|
|
592
|
+
|
|
593
|
+
**${s.title}**
|
|
594
|
+
ID: \`${s.id}\`
|
|
595
|
+
Pack: ${s.pack}
|
|
596
|
+
Lines: ${s.lines}
|
|
597
|
+
Visibility: ${s.visibility}
|
|
598
|
+
|
|
599
|
+
You can now find this skill via \`skilldb_search\` or \`skilldb_my_skills\`.` }] };
|
|
600
|
+
} catch (e) {
|
|
601
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
server.registerTool(
|
|
606
|
+
"skilldb_update_skill",
|
|
607
|
+
{
|
|
608
|
+
title: "Update Private Skill",
|
|
609
|
+
description: "Update an existing private skill's content, title, tags, or visibility. Requires an API key with 'write' scope.",
|
|
610
|
+
inputSchema: z.object({
|
|
611
|
+
id: z.string().describe("Skill ID (e.g. 'personal/my-react-patterns.md')"),
|
|
612
|
+
title: z.string().optional().describe("New title"),
|
|
613
|
+
content: z.string().optional().describe("New content"),
|
|
614
|
+
tags: z.array(z.string()).optional().describe("New tags"),
|
|
615
|
+
visibility: z.enum(["private", "shared"]).optional().describe("Visibility setting")
|
|
616
|
+
})
|
|
617
|
+
},
|
|
618
|
+
async ({ id, title, content, tags, visibility }) => {
|
|
619
|
+
if (!apiKey) {
|
|
620
|
+
return { content: [{ type: "text", text: "API key required." }], isError: true };
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
const body = {};
|
|
624
|
+
if (title) body.title = title;
|
|
625
|
+
if (content) body.content = content;
|
|
626
|
+
if (tags) body.tags = tags;
|
|
627
|
+
if (visibility) body.visibility = visibility;
|
|
628
|
+
const res = await client.rawRequest(`/api/v1/my-skills/${encodeURIComponent(id)}`, {
|
|
629
|
+
method: "PUT",
|
|
630
|
+
headers: { "Content-Type": "application/json" },
|
|
631
|
+
body: JSON.stringify(body)
|
|
632
|
+
});
|
|
633
|
+
const data = await res.json();
|
|
634
|
+
if (!res.ok) return { content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }], isError: true };
|
|
635
|
+
return { content: [{ type: "text", text: `\u2705 Skill updated: **${data.skill.title}** (${data.skill.lines} lines)` }] };
|
|
636
|
+
} catch (e) {
|
|
637
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
);
|
|
466
641
|
var transport = new StdioServerTransport();
|
|
467
642
|
await server.connect(transport);
|
|
468
643
|
//# sourceMappingURL=mcp.js.map
|