skilldb 0.5.2 → 0.6.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.cjs CHANGED
@@ -94,7 +94,13 @@ var SkillDBClient = class {
94
94
  }
95
95
  return h;
96
96
  }
97
- async request(endpoint, params) {
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)) {
@@ -111,7 +117,7 @@ var SkillDBClient = class {
111
117
  }
112
118
  /** Search skills by keyword. */
113
119
  async search(query, options) {
114
- return this.request("/skills", {
120
+ return this.typedRequest("/skills", {
115
121
  search: query,
116
122
  category: options?.category ?? "",
117
123
  pack: options?.pack ?? "",
@@ -123,7 +129,7 @@ var SkillDBClient = class {
123
129
  }
124
130
  /** List skills with optional filters and sorting. */
125
131
  async list(options) {
126
- return this.request("/skills", {
132
+ return this.typedRequest("/skills", {
127
133
  category: options?.category ?? "",
128
134
  pack: options?.pack ?? "",
129
135
  search: options?.search ?? "",
@@ -136,21 +142,21 @@ var SkillDBClient = class {
136
142
  /** Get a single skill by ID (e.g. "software-skills/code-review.md"). */
137
143
  async get(id) {
138
144
  const encoded = encodeURIComponent(id);
139
- const res = await this.request(`/skills/${encoded}`, {
145
+ const res = await this.typedRequest(`/skills/${encoded}`, {
140
146
  include_content: "true"
141
147
  });
142
148
  return "skill" in res ? res.skill : res;
143
149
  }
144
150
  /** Batch retrieve multiple skills by IDs (max 50). */
145
151
  async batch(ids) {
146
- return this.request("/skills", {
152
+ return this.typedRequest("/skills", {
147
153
  ids: ids.slice(0, 50).join(","),
148
154
  include_content: "true"
149
155
  });
150
156
  }
151
157
  /** Get search autocomplete suggestions. */
152
158
  async suggest(query) {
153
- return this.request("/skills/suggest", { q: query });
159
+ return this.typedRequest("/skills/suggest", { q: query });
154
160
  }
155
161
  /** Validate that the configured API key works. */
156
162
  async validate() {
@@ -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(() => ({}));\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.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 request;
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 request;
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
- async request(endpoint, params) {
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)) {
@@ -66,7 +72,7 @@ var SkillDBClient = class {
66
72
  }
67
73
  /** Search skills by keyword. */
68
74
  async search(query, options) {
69
- return this.request("/skills", {
75
+ return this.typedRequest("/skills", {
70
76
  search: query,
71
77
  category: options?.category ?? "",
72
78
  pack: options?.pack ?? "",
@@ -78,7 +84,7 @@ var SkillDBClient = class {
78
84
  }
79
85
  /** List skills with optional filters and sorting. */
80
86
  async list(options) {
81
- return this.request("/skills", {
87
+ return this.typedRequest("/skills", {
82
88
  category: options?.category ?? "",
83
89
  pack: options?.pack ?? "",
84
90
  search: options?.search ?? "",
@@ -91,21 +97,21 @@ var SkillDBClient = class {
91
97
  /** Get a single skill by ID (e.g. "software-skills/code-review.md"). */
92
98
  async get(id) {
93
99
  const encoded = encodeURIComponent(id);
94
- const res = await this.request(`/skills/${encoded}`, {
100
+ const res = await this.typedRequest(`/skills/${encoded}`, {
95
101
  include_content: "true"
96
102
  });
97
103
  return "skill" in res ? res.skill : res;
98
104
  }
99
105
  /** Batch retrieve multiple skills by IDs (max 50). */
100
106
  async batch(ids) {
101
- return this.request("/skills", {
107
+ return this.typedRequest("/skills", {
102
108
  ids: ids.slice(0, 50).join(","),
103
109
  include_content: "true"
104
110
  });
105
111
  }
106
112
  /** Get search autocomplete suggestions. */
107
113
  async suggest(query) {
108
- return this.request("/skills/suggest", { q: query });
114
+ return this.typedRequest("/skills/suggest", { q: query });
109
115
  }
110
116
  /** Validate that the configured API key works. */
111
117
  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(() => ({}));\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.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
- async request(endpoint, params) {
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)) {
@@ -67,7 +73,7 @@ var SkillDBClient = class {
67
73
  }
68
74
  /** Search skills by keyword. */
69
75
  async search(query, options) {
70
- return this.request("/skills", {
76
+ return this.typedRequest("/skills", {
71
77
  search: query,
72
78
  category: options?.category ?? "",
73
79
  pack: options?.pack ?? "",
@@ -79,7 +85,7 @@ var SkillDBClient = class {
79
85
  }
80
86
  /** List skills with optional filters and sorting. */
81
87
  async list(options) {
82
- return this.request("/skills", {
88
+ return this.typedRequest("/skills", {
83
89
  category: options?.category ?? "",
84
90
  pack: options?.pack ?? "",
85
91
  search: options?.search ?? "",
@@ -92,21 +98,21 @@ var SkillDBClient = class {
92
98
  /** Get a single skill by ID (e.g. "software-skills/code-review.md"). */
93
99
  async get(id) {
94
100
  const encoded = encodeURIComponent(id);
95
- const res = await this.request(`/skills/${encoded}`, {
101
+ const res = await this.typedRequest(`/skills/${encoded}`, {
96
102
  include_content: "true"
97
103
  });
98
104
  return "skill" in res ? res.skill : res;
99
105
  }
100
106
  /** Batch retrieve multiple skills by IDs (max 50). */
101
107
  async batch(ids) {
102
- return this.request("/skills", {
108
+ return this.typedRequest("/skills", {
103
109
  ids: ids.slice(0, 50).join(","),
104
110
  include_content: "true"
105
111
  });
106
112
  }
107
113
  /** Get search autocomplete suggestions. */
108
114
  async suggest(query) {
109
- return this.request("/skills/suggest", { q: query });
115
+ return this.typedRequest("/skills/suggest", { q: query });
110
116
  }
111
117
  /** Validate that the configured API key works. */
112
118
  async validate() {
@@ -463,6 +469,123 @@ server.registerTool(
463
469
  }
464
470
  }
465
471
  );
472
+ server.registerTool(
473
+ "skilldb_my_skills",
474
+ {
475
+ title: "List My Private Skills",
476
+ 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.",
477
+ inputSchema: z.object({})
478
+ },
479
+ async () => {
480
+ if (!apiKey) {
481
+ return { content: [{ type: "text", text: "API key required for private skills. Set one with: skilldb-mcp --api-key sk_live_xxx" }], isError: true };
482
+ }
483
+ try {
484
+ const res = await client.rawRequest("/api/v1/my-skills", { method: "GET" });
485
+ const data = await res.json();
486
+ if (!res.ok) return { content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }], isError: true };
487
+ const own = data.skills || [];
488
+ const shared = data.shared || [];
489
+ if (own.length === 0 && shared.length === 0) {
490
+ return { content: [{ type: "text", text: "No private skills yet. Create one with `skilldb_create_skill`." }] };
491
+ }
492
+ let text = `## My Private Skills (${own.length})
493
+
494
+ `;
495
+ text += own.map((s, i) => `${i + 1}. **${s.title}** (${s.pack})
496
+ ID: \`${s.id}\` | ${s.lines} lines | ${s.visibility}`).join("\n\n");
497
+ if (shared.length > 0) {
498
+ text += `
499
+
500
+ ## Shared With Me (${shared.length})
501
+
502
+ `;
503
+ text += shared.map((s, i) => `${i + 1}. **${s.title}** (${s.pack})
504
+ ID: \`${s.id}\` | ${s.lines} lines | shared by ${s.owner}`).join("\n\n");
505
+ }
506
+ return { content: [{ type: "text", text }] };
507
+ } catch (e) {
508
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
509
+ }
510
+ }
511
+ );
512
+ server.registerTool(
513
+ "skilldb_create_skill",
514
+ {
515
+ title: "Create Private Skill",
516
+ 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.",
517
+ inputSchema: z.object({
518
+ name: z.string().describe("Skill filename (e.g. 'my-react-patterns')"),
519
+ title: z.string().describe("Human-readable title"),
520
+ content: z.string().describe("Full skill content in markdown"),
521
+ pack: z.string().optional().default("personal").describe("Pack/folder name (default: 'personal')"),
522
+ tags: z.array(z.string()).optional().describe("Tags for searchability"),
523
+ description: z.string().optional().describe("Short description (auto-generated from content if omitted)")
524
+ })
525
+ },
526
+ async ({ name, title, content, pack, tags, description }) => {
527
+ if (!apiKey) {
528
+ return { content: [{ type: "text", text: "API key required. Set one with: skilldb-mcp --api-key sk_live_xxx" }], isError: true };
529
+ }
530
+ try {
531
+ const res = await client.rawRequest("/api/v1/my-skills", {
532
+ method: "POST",
533
+ headers: { "Content-Type": "application/json" },
534
+ body: JSON.stringify({ name, title, content, pack, tags, description })
535
+ });
536
+ const data = await res.json();
537
+ if (!res.ok) return { content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }], isError: true };
538
+ const s = data.skill;
539
+ return { content: [{ type: "text", text: `\u2705 Private skill created!
540
+
541
+ **${s.title}**
542
+ ID: \`${s.id}\`
543
+ Pack: ${s.pack}
544
+ Lines: ${s.lines}
545
+ Visibility: ${s.visibility}
546
+
547
+ You can now find this skill via \`skilldb_search\` or \`skilldb_my_skills\`.` }] };
548
+ } catch (e) {
549
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
550
+ }
551
+ }
552
+ );
553
+ server.registerTool(
554
+ "skilldb_update_skill",
555
+ {
556
+ title: "Update Private Skill",
557
+ description: "Update an existing private skill's content, title, tags, or visibility. Requires an API key with 'write' scope.",
558
+ inputSchema: z.object({
559
+ id: z.string().describe("Skill ID (e.g. 'personal/my-react-patterns.md')"),
560
+ title: z.string().optional().describe("New title"),
561
+ content: z.string().optional().describe("New content"),
562
+ tags: z.array(z.string()).optional().describe("New tags"),
563
+ visibility: z.enum(["private", "shared"]).optional().describe("Visibility setting")
564
+ })
565
+ },
566
+ async ({ id, title, content, tags, visibility }) => {
567
+ if (!apiKey) {
568
+ return { content: [{ type: "text", text: "API key required." }], isError: true };
569
+ }
570
+ try {
571
+ const body = {};
572
+ if (title) body.title = title;
573
+ if (content) body.content = content;
574
+ if (tags) body.tags = tags;
575
+ if (visibility) body.visibility = visibility;
576
+ const res = await client.rawRequest(`/api/v1/my-skills/${encodeURIComponent(id)}`, {
577
+ method: "PUT",
578
+ headers: { "Content-Type": "application/json" },
579
+ body: JSON.stringify(body)
580
+ });
581
+ const data = await res.json();
582
+ if (!res.ok) return { content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }], isError: true };
583
+ return { content: [{ type: "text", text: `\u2705 Skill updated: **${data.skill.title}** (${data.skill.lines} lines)` }] };
584
+ } catch (e) {
585
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
586
+ }
587
+ }
588
+ );
466
589
  var transport = new StdioServerTransport();
467
590
  await server.connect(transport);
468
591
  //# sourceMappingURL=mcp.js.map