skilldb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +510 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +242 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +109 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +196 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/search.ts
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
|
|
9
|
+
// src/config.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import os from "os";
|
|
13
|
+
var RC_FILE = ".skilldbrc";
|
|
14
|
+
var DEFAULT_BASE_URL = "https://skilldb.dev/api/v1";
|
|
15
|
+
function readJson(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
18
|
+
return JSON.parse(raw);
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function resolveApiKey() {
|
|
24
|
+
if (process.env.SKILLDB_API_KEY) {
|
|
25
|
+
return process.env.SKILLDB_API_KEY;
|
|
26
|
+
}
|
|
27
|
+
const projectRc = path.join(process.cwd(), RC_FILE);
|
|
28
|
+
const projectConfig = readJson(projectRc);
|
|
29
|
+
if (projectConfig?.apiKey) return projectConfig.apiKey;
|
|
30
|
+
const homeRc = path.join(os.homedir(), RC_FILE);
|
|
31
|
+
const homeConfig = readJson(homeRc);
|
|
32
|
+
if (homeConfig?.apiKey) return homeConfig.apiKey;
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
function resolveBaseUrl() {
|
|
36
|
+
return process.env.SKILLDB_API_URL || DEFAULT_BASE_URL;
|
|
37
|
+
}
|
|
38
|
+
function saveApiKey(apiKey, global = true) {
|
|
39
|
+
const target = global ? path.join(os.homedir(), RC_FILE) : path.join(process.cwd(), RC_FILE);
|
|
40
|
+
const existing = readJson(target) || {};
|
|
41
|
+
fs.writeFileSync(target, JSON.stringify({ ...existing, apiKey }, null, 2) + "\n", "utf-8");
|
|
42
|
+
return target;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/client.ts
|
|
46
|
+
var SkillDBClient = class {
|
|
47
|
+
apiKey;
|
|
48
|
+
baseUrl;
|
|
49
|
+
constructor(config) {
|
|
50
|
+
this.apiKey = config?.apiKey ?? resolveApiKey();
|
|
51
|
+
this.baseUrl = config?.baseUrl ?? resolveBaseUrl();
|
|
52
|
+
}
|
|
53
|
+
headers() {
|
|
54
|
+
const h = { "Content-Type": "application/json" };
|
|
55
|
+
if (this.apiKey) {
|
|
56
|
+
h["Authorization"] = `Bearer ${this.apiKey}`;
|
|
57
|
+
}
|
|
58
|
+
return h;
|
|
59
|
+
}
|
|
60
|
+
async request(endpoint, params) {
|
|
61
|
+
const url = new URL(`${this.baseUrl}${endpoint}`);
|
|
62
|
+
if (params) {
|
|
63
|
+
for (const [k, v] of Object.entries(params)) {
|
|
64
|
+
if (v !== void 0 && v !== "") url.searchParams.set(k, v);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const res = await fetch(url.toString(), { headers: this.headers() });
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
const body = await res.json().catch(() => ({}));
|
|
70
|
+
const msg = body.error || `HTTP ${res.status}`;
|
|
71
|
+
throw new Error(msg);
|
|
72
|
+
}
|
|
73
|
+
return res.json();
|
|
74
|
+
}
|
|
75
|
+
/** Search skills by keyword. */
|
|
76
|
+
async search(query, options) {
|
|
77
|
+
return this.request("/skills", {
|
|
78
|
+
search: query,
|
|
79
|
+
category: options?.category ?? "",
|
|
80
|
+
pack: options?.pack ?? "",
|
|
81
|
+
limit: String(options?.limit ?? 20),
|
|
82
|
+
offset: String(options?.offset ?? 0),
|
|
83
|
+
include_content: options?.includeContent ? "true" : ""
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** List skills with optional filters. */
|
|
87
|
+
async list(options) {
|
|
88
|
+
return this.request("/skills", {
|
|
89
|
+
category: options?.category ?? "",
|
|
90
|
+
pack: options?.pack ?? "",
|
|
91
|
+
search: options?.search ?? "",
|
|
92
|
+
limit: String(options?.limit ?? 50),
|
|
93
|
+
offset: String(options?.offset ?? 0),
|
|
94
|
+
include_content: options?.includeContent ? "true" : ""
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** Get a single skill by ID (e.g. "software-skills/code-review.md"). */
|
|
98
|
+
async get(id) {
|
|
99
|
+
const encoded = encodeURIComponent(id);
|
|
100
|
+
const res = await this.request(`/skills/${encoded}`, {
|
|
101
|
+
include_content: "true"
|
|
102
|
+
});
|
|
103
|
+
return "skill" in res ? res.skill : res;
|
|
104
|
+
}
|
|
105
|
+
/** Validate that the configured API key works. */
|
|
106
|
+
async validate() {
|
|
107
|
+
try {
|
|
108
|
+
await this.list({ limit: 1 });
|
|
109
|
+
return true;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/commands/search.ts
|
|
117
|
+
async function searchCommand(query, options) {
|
|
118
|
+
const client = new SkillDBClient();
|
|
119
|
+
const limit = options.limit ? parseInt(options.limit) : 20;
|
|
120
|
+
try {
|
|
121
|
+
const res = await client.search(query, { category: options.category, limit });
|
|
122
|
+
if (res.skills.length === 0) {
|
|
123
|
+
console.log(pc.yellow(`No skills found for "${query}"`));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
console.log(pc.bold(`Found ${res.pagination.total} skill${res.pagination.total === 1 ? "" : "s"} for "${query}":
|
|
127
|
+
`));
|
|
128
|
+
const nameW = 30;
|
|
129
|
+
const packW = 24;
|
|
130
|
+
const descW = 44;
|
|
131
|
+
console.log(
|
|
132
|
+
pc.dim(
|
|
133
|
+
pad("SKILL", nameW) + pad("PACK", packW) + "DESCRIPTION"
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
console.log(pc.dim("\u2500".repeat(nameW + packW + descW)));
|
|
137
|
+
for (const s of res.skills) {
|
|
138
|
+
const name = truncate(s.title, nameW - 2);
|
|
139
|
+
const pack = truncate(s.packLabel, packW - 2);
|
|
140
|
+
const desc = truncate(s.description, descW);
|
|
141
|
+
console.log(
|
|
142
|
+
pc.cyan(pad(name, nameW)) + pc.white(pad(pack, packW)) + pc.dim(desc)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
if (res.pagination.hasMore) {
|
|
146
|
+
console.log(pc.dim(`
|
|
147
|
+
... and ${res.pagination.total - res.skills.length} more. Use --limit to see more.`));
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error(pc.red(`Error: ${err.message}`));
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function pad(str, width) {
|
|
155
|
+
return str.length >= width ? str.slice(0, width) : str + " ".repeat(width - str.length);
|
|
156
|
+
}
|
|
157
|
+
function truncate(str, max) {
|
|
158
|
+
return str.length <= max ? str : str.slice(0, max - 1) + "\u2026";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/commands/list.ts
|
|
162
|
+
import pc2 from "picocolors";
|
|
163
|
+
async function listCommand(options) {
|
|
164
|
+
const client = new SkillDBClient();
|
|
165
|
+
const limit = options.limit ? parseInt(options.limit) : 50;
|
|
166
|
+
try {
|
|
167
|
+
const res = await client.list({ category: options.category, pack: options.pack, limit });
|
|
168
|
+
if (res.skills.length === 0) {
|
|
169
|
+
console.log(pc2.yellow("No skills found."));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!options.category && !options.pack) {
|
|
173
|
+
console.log(pc2.bold("Categories:"));
|
|
174
|
+
for (const cat of res.meta.categories) {
|
|
175
|
+
console.log(` ${pc2.cyan(cat)}`);
|
|
176
|
+
}
|
|
177
|
+
console.log(`
|
|
178
|
+
${pc2.dim(`${res.meta.totalPacks} packs, ${res.pagination.total} skills total`)}`);
|
|
179
|
+
console.log(pc2.dim("Use --category or --pack to filter.\n"));
|
|
180
|
+
}
|
|
181
|
+
const nameW = 30;
|
|
182
|
+
const packW = 24;
|
|
183
|
+
const catW = 20;
|
|
184
|
+
console.log(pc2.dim(pad2("SKILL", nameW) + pad2("PACK", packW) + "CATEGORY"));
|
|
185
|
+
console.log(pc2.dim("\u2500".repeat(nameW + packW + catW)));
|
|
186
|
+
for (const s of res.skills) {
|
|
187
|
+
console.log(
|
|
188
|
+
pc2.cyan(pad2(truncate2(s.title, nameW - 2), nameW)) + pc2.white(pad2(truncate2(s.packLabel, packW - 2), packW)) + pc2.dim(s.category)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (res.pagination.hasMore) {
|
|
192
|
+
console.log(pc2.dim(`
|
|
193
|
+
Showing ${res.skills.length} of ${res.pagination.total}. Use --limit to see more.`));
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
console.error(pc2.red(`Error: ${err.message}`));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function pad2(str, width) {
|
|
201
|
+
return str.length >= width ? str.slice(0, width) : str + " ".repeat(width - str.length);
|
|
202
|
+
}
|
|
203
|
+
function truncate2(str, max) {
|
|
204
|
+
return str.length <= max ? str : str.slice(0, max - 1) + "\u2026";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/commands/add.ts
|
|
208
|
+
import pc3 from "picocolors";
|
|
209
|
+
|
|
210
|
+
// src/cache.ts
|
|
211
|
+
import fs2 from "fs";
|
|
212
|
+
import path2 from "path";
|
|
213
|
+
var SKILLDB_DIR = ".skilldb";
|
|
214
|
+
var SKILLS_DIR = "skills";
|
|
215
|
+
var MANIFEST_FILE = "manifest.json";
|
|
216
|
+
function ensureDir(dir) {
|
|
217
|
+
if (!fs2.existsSync(dir)) {
|
|
218
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function skilldbRoot(cwd) {
|
|
222
|
+
return path2.join(cwd ?? process.cwd(), SKILLDB_DIR);
|
|
223
|
+
}
|
|
224
|
+
function initCache(cwd) {
|
|
225
|
+
const root = skilldbRoot(cwd);
|
|
226
|
+
ensureDir(path2.join(root, SKILLS_DIR));
|
|
227
|
+
const manifestPath = path2.join(root, MANIFEST_FILE);
|
|
228
|
+
if (!fs2.existsSync(manifestPath)) {
|
|
229
|
+
fs2.writeFileSync(manifestPath, JSON.stringify({ installed: {} }, null, 2) + "\n");
|
|
230
|
+
}
|
|
231
|
+
return root;
|
|
232
|
+
}
|
|
233
|
+
function readManifest(cwd) {
|
|
234
|
+
const manifestPath = path2.join(skilldbRoot(cwd), MANIFEST_FILE);
|
|
235
|
+
try {
|
|
236
|
+
return JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
|
|
237
|
+
} catch {
|
|
238
|
+
return { installed: {} };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function writeManifest(manifest, cwd) {
|
|
242
|
+
const root = skilldbRoot(cwd);
|
|
243
|
+
ensureDir(root);
|
|
244
|
+
fs2.writeFileSync(
|
|
245
|
+
path2.join(root, MANIFEST_FILE),
|
|
246
|
+
JSON.stringify(manifest, null, 2) + "\n"
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
function cacheSkill(skill, cwd) {
|
|
250
|
+
const root = skilldbRoot(cwd);
|
|
251
|
+
const skillDir = path2.join(root, SKILLS_DIR, skill.pack);
|
|
252
|
+
ensureDir(skillDir);
|
|
253
|
+
const safeName = skill.name.replace(/[/\\:*?"<>|]/g, "-");
|
|
254
|
+
const filePath = path2.join(skillDir, `${safeName}.md`);
|
|
255
|
+
fs2.writeFileSync(filePath, skill.content ?? `# ${skill.title}
|
|
256
|
+
|
|
257
|
+
${skill.description}
|
|
258
|
+
`);
|
|
259
|
+
const manifest = readManifest(cwd);
|
|
260
|
+
manifest.installed[skill.id] = {
|
|
261
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
262
|
+
lines: skill.lines
|
|
263
|
+
};
|
|
264
|
+
writeManifest(manifest, cwd);
|
|
265
|
+
return filePath;
|
|
266
|
+
}
|
|
267
|
+
function isCached(skillId, cwd) {
|
|
268
|
+
const manifest = readManifest(cwd);
|
|
269
|
+
return skillId in manifest.installed;
|
|
270
|
+
}
|
|
271
|
+
function updateGitignore(cwd) {
|
|
272
|
+
const root = cwd ?? process.cwd();
|
|
273
|
+
const gitignorePath = path2.join(root, ".gitignore");
|
|
274
|
+
const entries = [".skilldb/", ".skilldbrc"];
|
|
275
|
+
let content = "";
|
|
276
|
+
if (fs2.existsSync(gitignorePath)) {
|
|
277
|
+
content = fs2.readFileSync(gitignorePath, "utf-8");
|
|
278
|
+
}
|
|
279
|
+
const toAdd = entries.filter((e) => !content.includes(e));
|
|
280
|
+
if (toAdd.length === 0) return;
|
|
281
|
+
const suffix = (content && !content.endsWith("\n") ? "\n" : "") + "\n# SkillDB\n" + toAdd.join("\n") + "\n";
|
|
282
|
+
fs2.writeFileSync(gitignorePath, content + suffix);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/commands/add.ts
|
|
286
|
+
async function addCommand(packName) {
|
|
287
|
+
const client = new SkillDBClient();
|
|
288
|
+
console.log(pc3.bold(`Adding pack: ${packName}`));
|
|
289
|
+
initCache();
|
|
290
|
+
try {
|
|
291
|
+
const res = await client.list({ pack: packName, limit: 500, includeContent: true });
|
|
292
|
+
if (res.skills.length === 0) {
|
|
293
|
+
console.error(pc3.red(`Pack "${packName}" not found or empty.`));
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
let added = 0;
|
|
297
|
+
let skipped = 0;
|
|
298
|
+
for (const skill of res.skills) {
|
|
299
|
+
if (isCached(skill.id)) {
|
|
300
|
+
skipped++;
|
|
301
|
+
console.log(pc3.dim(` skip ${skill.id} (already cached)`));
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const filePath = cacheSkill(skill);
|
|
305
|
+
added++;
|
|
306
|
+
console.log(pc3.green(` add ${skill.id}`) + pc3.dim(` \u2192 ${filePath}`));
|
|
307
|
+
}
|
|
308
|
+
console.log(
|
|
309
|
+
`
|
|
310
|
+
${pc3.green(`${added} skill${added === 1 ? "" : "s"} added`)}` + (skipped > 0 ? pc3.dim(`, ${skipped} skipped`) : "")
|
|
311
|
+
);
|
|
312
|
+
if (res.skills.some((s) => !s.content)) {
|
|
313
|
+
console.log(pc3.yellow("\nNote: Some skills were cached without content (metadata only)."));
|
|
314
|
+
console.log(pc3.yellow('Run "skilldb login" with a Pro key to download full content.'));
|
|
315
|
+
}
|
|
316
|
+
} catch (err) {
|
|
317
|
+
console.error(pc3.red(`Error: ${err.message}`));
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/commands/info.ts
|
|
323
|
+
import pc4 from "picocolors";
|
|
324
|
+
async function infoCommand(id) {
|
|
325
|
+
const client = new SkillDBClient();
|
|
326
|
+
if (!id.includes(".md")) {
|
|
327
|
+
id = id + ".md";
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
const skill = await client.get(id);
|
|
331
|
+
console.log(pc4.bold(skill.title));
|
|
332
|
+
console.log(pc4.dim("\u2500".repeat(50)));
|
|
333
|
+
console.log(`${pc4.cyan("ID:")} ${skill.id}`);
|
|
334
|
+
console.log(`${pc4.cyan("Pack:")} ${skill.packLabel} (${skill.pack})`);
|
|
335
|
+
console.log(`${pc4.cyan("Category:")} ${skill.category}`);
|
|
336
|
+
console.log(`${pc4.cyan("Lines:")} ${skill.lines}`);
|
|
337
|
+
console.log(`${pc4.cyan("Description:")} ${skill.description}`);
|
|
338
|
+
if (skill.content) {
|
|
339
|
+
console.log(pc4.dim("\n\u2500\u2500\u2500 Preview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
340
|
+
const preview = skill.content.split("\n").slice(0, 20).join("\n");
|
|
341
|
+
console.log(preview);
|
|
342
|
+
if (skill.content.split("\n").length > 20) {
|
|
343
|
+
console.log(pc4.dim(`
|
|
344
|
+
... ${skill.lines - 20} more lines`));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error(pc4.red(`Error: ${err.message}`));
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/commands/init.ts
|
|
354
|
+
import fs3 from "fs";
|
|
355
|
+
import path3 from "path";
|
|
356
|
+
import pc5 from "picocolors";
|
|
357
|
+
import readline from "readline";
|
|
358
|
+
function prompt(question) {
|
|
359
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
360
|
+
return new Promise((resolve) => {
|
|
361
|
+
rl.question(question, (answer) => {
|
|
362
|
+
rl.close();
|
|
363
|
+
resolve(answer.trim());
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function detectIDE(cwd) {
|
|
368
|
+
if (fs3.existsSync(path3.join(cwd, "CLAUDE.md")) || fs3.existsSync(path3.join(cwd, ".claude"))) {
|
|
369
|
+
return {
|
|
370
|
+
ide: "claude-code",
|
|
371
|
+
label: "Claude Code",
|
|
372
|
+
configFile: path3.join(cwd, "CLAUDE.md")
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (fs3.existsSync(path3.join(cwd, ".cursor")) || fs3.existsSync(path3.join(cwd, ".cursorrules"))) {
|
|
376
|
+
return {
|
|
377
|
+
ide: "cursor",
|
|
378
|
+
label: "Cursor",
|
|
379
|
+
configFile: fs3.existsSync(path3.join(cwd, ".cursorrules")) ? path3.join(cwd, ".cursorrules") : path3.join(cwd, ".cursor", "rules")
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (fs3.existsSync(path3.join(cwd, "codex.md")) || fs3.existsSync(path3.join(cwd, ".codex"))) {
|
|
383
|
+
return {
|
|
384
|
+
ide: "codex",
|
|
385
|
+
label: "Codex CLI",
|
|
386
|
+
configFile: path3.join(cwd, "codex.md")
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
function getIntegrationSnippet(ide) {
|
|
392
|
+
const marker = "<!-- skilldb:start -->";
|
|
393
|
+
const endMarker = "<!-- skilldb:end -->";
|
|
394
|
+
const content = `
|
|
395
|
+
## SkillDB Skills
|
|
396
|
+
|
|
397
|
+
Local skills are available in \`.skilldb/skills/\`. Use them as reference when working on tasks.
|
|
398
|
+
To add more skills: \`npx skilldb add <pack-name>\`
|
|
399
|
+
Browse available skills: \`npx skilldb search <query>\`
|
|
400
|
+
`.trim();
|
|
401
|
+
return `
|
|
402
|
+
|
|
403
|
+
${marker}
|
|
404
|
+
${content}
|
|
405
|
+
${endMarker}
|
|
406
|
+
`;
|
|
407
|
+
}
|
|
408
|
+
async function initCommand() {
|
|
409
|
+
const cwd = process.cwd();
|
|
410
|
+
console.log(pc5.bold("SkillDB Init\n"));
|
|
411
|
+
let detection = detectIDE(cwd);
|
|
412
|
+
if (detection) {
|
|
413
|
+
console.log(`Detected: ${pc5.cyan(detection.label)}`);
|
|
414
|
+
} else {
|
|
415
|
+
console.log("No IDE config detected. Which are you using?");
|
|
416
|
+
console.log(" 1) Claude Code");
|
|
417
|
+
console.log(" 2) Cursor");
|
|
418
|
+
console.log(" 3) Codex CLI");
|
|
419
|
+
const choice = await prompt("\nChoice (1-3): ");
|
|
420
|
+
const ideMap = { "1": "claude-code", "2": "cursor", "3": "codex" };
|
|
421
|
+
const ide = ideMap[choice];
|
|
422
|
+
if (!ide) {
|
|
423
|
+
console.log(pc5.red("Invalid choice."));
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
const configFiles = {
|
|
427
|
+
"claude-code": path3.join(cwd, "CLAUDE.md"),
|
|
428
|
+
"cursor": path3.join(cwd, ".cursorrules"),
|
|
429
|
+
"codex": path3.join(cwd, "codex.md")
|
|
430
|
+
};
|
|
431
|
+
detection = {
|
|
432
|
+
ide,
|
|
433
|
+
label: ide === "claude-code" ? "Claude Code" : ide === "cursor" ? "Cursor" : "Codex CLI",
|
|
434
|
+
configFile: configFiles[ide]
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const cacheDir = initCache(cwd);
|
|
438
|
+
console.log(`Created ${pc5.dim(cacheDir)}`);
|
|
439
|
+
updateGitignore(cwd);
|
|
440
|
+
console.log(`Updated ${pc5.dim(".gitignore")}`);
|
|
441
|
+
const snippet = getIntegrationSnippet(detection.ide);
|
|
442
|
+
const configFile = detection.configFile;
|
|
443
|
+
let existing = "";
|
|
444
|
+
if (fs3.existsSync(configFile)) {
|
|
445
|
+
existing = fs3.readFileSync(configFile, "utf-8");
|
|
446
|
+
}
|
|
447
|
+
if (existing.includes("<!-- skilldb:start -->")) {
|
|
448
|
+
console.log(pc5.dim(`${path3.basename(configFile)} already has SkillDB integration.`));
|
|
449
|
+
} else {
|
|
450
|
+
const dir = path3.dirname(configFile);
|
|
451
|
+
if (!fs3.existsSync(dir)) {
|
|
452
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
453
|
+
}
|
|
454
|
+
fs3.writeFileSync(configFile, existing + snippet);
|
|
455
|
+
console.log(`Added SkillDB snippet to ${pc5.cyan(path3.basename(configFile))}`);
|
|
456
|
+
}
|
|
457
|
+
console.log(pc5.green("\nDone! Next steps:"));
|
|
458
|
+
console.log(` ${pc5.dim("$")} skilldb login ${pc5.dim("# save your API key")}`);
|
|
459
|
+
console.log(` ${pc5.dim("$")} skilldb search testing ${pc5.dim("# find skills")}`);
|
|
460
|
+
console.log(` ${pc5.dim("$")} skilldb add <pack> ${pc5.dim("# install a skill pack")}`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/commands/login.ts
|
|
464
|
+
import pc6 from "picocolors";
|
|
465
|
+
import readline2 from "readline";
|
|
466
|
+
function prompt2(question) {
|
|
467
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
468
|
+
return new Promise((resolve) => {
|
|
469
|
+
rl.question(question, (answer) => {
|
|
470
|
+
rl.close();
|
|
471
|
+
resolve(answer.trim());
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
async function loginCommand() {
|
|
476
|
+
console.log(pc6.bold("SkillDB Login"));
|
|
477
|
+
console.log("Get your API key at https://skilldb.dev/api-access\n");
|
|
478
|
+
const apiKey = await prompt2("API key: ");
|
|
479
|
+
if (!apiKey) {
|
|
480
|
+
console.log(pc6.red("No API key provided."));
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
if (!apiKey.startsWith("sk_")) {
|
|
484
|
+
console.log(pc6.yellow('Warning: API key should start with "sk_". Saving anyway.'));
|
|
485
|
+
}
|
|
486
|
+
process.stdout.write("Validating... ");
|
|
487
|
+
const client = new SkillDBClient({ apiKey });
|
|
488
|
+
const valid = await client.validate();
|
|
489
|
+
if (valid) {
|
|
490
|
+
const savedTo = saveApiKey(apiKey);
|
|
491
|
+
console.log(pc6.green("valid!"));
|
|
492
|
+
console.log(`Saved to ${pc6.dim(savedTo)}`);
|
|
493
|
+
} else {
|
|
494
|
+
console.log(pc6.yellow("could not validate (saved anyway)"));
|
|
495
|
+
const savedTo = saveApiKey(apiKey);
|
|
496
|
+
console.log(`Saved to ${pc6.dim(savedTo)}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/cli.ts
|
|
501
|
+
var program = new Command();
|
|
502
|
+
program.name("skilldb").description("SkillDB CLI \u2014 discover, install, and manage AI agent skills").version("0.1.0");
|
|
503
|
+
program.command("init").description("Initialize SkillDB in this project (detect IDE, create .skilldb/)").action(initCommand);
|
|
504
|
+
program.command("login").description("Save your SkillDB API key").action(loginCommand);
|
|
505
|
+
program.command("search <query>").description("Search skills by keyword").option("-c, --category <name>", "Filter by category").option("-l, --limit <n>", "Max results", "20").action(searchCommand);
|
|
506
|
+
program.command("list").description("List skills, optionally filtered").option("-c, --category <name>", "Filter by category").option("-p, --pack <name>", "Filter by pack").option("-l, --limit <n>", "Max results", "50").action(listCommand);
|
|
507
|
+
program.command("add <pack>").description("Download a skill pack to local .skilldb/skills/ cache").action(addCommand);
|
|
508
|
+
program.command("info <id>").description('Show metadata and preview for a skill (e.g. "software-skills/code-review")').action(infoCommand);
|
|
509
|
+
program.parse();
|
|
510
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/search.ts","../src/config.ts","../src/client.ts","../src/commands/list.ts","../src/commands/add.ts","../src/cache.ts","../src/commands/info.ts","../src/commands/init.ts","../src/commands/login.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { searchCommand } from './commands/search.js';\nimport { listCommand } from './commands/list.js';\nimport { addCommand } from './commands/add.js';\nimport { infoCommand } from './commands/info.js';\nimport { initCommand } from './commands/init.js';\nimport { loginCommand } from './commands/login.js';\n\nconst program = new Command();\n\nprogram\n .name('skilldb')\n .description('SkillDB CLI — discover, install, and manage AI agent skills')\n .version('0.1.0');\n\nprogram\n .command('init')\n .description('Initialize SkillDB in this project (detect IDE, create .skilldb/)')\n .action(initCommand);\n\nprogram\n .command('login')\n .description('Save your SkillDB API key')\n .action(loginCommand);\n\nprogram\n .command('search <query>')\n .description('Search skills by keyword')\n .option('-c, --category <name>', 'Filter by category')\n .option('-l, --limit <n>', 'Max results', '20')\n .action(searchCommand);\n\nprogram\n .command('list')\n .description('List skills, optionally filtered')\n .option('-c, --category <name>', 'Filter by category')\n .option('-p, --pack <name>', 'Filter by pack')\n .option('-l, --limit <n>', 'Max results', '50')\n .action(listCommand);\n\nprogram\n .command('add <pack>')\n .description('Download a skill pack to local .skilldb/skills/ cache')\n .action(addCommand);\n\nprogram\n .command('info <id>')\n .description('Show metadata and preview for a skill (e.g. \"software-skills/code-review\")')\n .action(infoCommand);\n\nprogram.parse();\n","import pc from 'picocolors';\nimport { SkillDBClient } from '../client.js';\n\nexport async function searchCommand(query: string, options: { category?: string; limit?: string }): Promise<void> {\n const client = new SkillDBClient();\n const limit = options.limit ? parseInt(options.limit) : 20;\n\n try {\n const res = await client.search(query, { category: options.category, limit });\n\n if (res.skills.length === 0) {\n console.log(pc.yellow(`No skills found for \"${query}\"`));\n return;\n }\n\n console.log(pc.bold(`Found ${res.pagination.total} skill${res.pagination.total === 1 ? '' : 's'} for \"${query}\":\\n`));\n\n // Column widths\n const nameW = 30;\n const packW = 24;\n const descW = 44;\n\n console.log(\n pc.dim(\n pad('SKILL', nameW) + pad('PACK', packW) + 'DESCRIPTION'\n )\n );\n console.log(pc.dim('─'.repeat(nameW + packW + descW)));\n\n for (const s of res.skills) {\n const name = truncate(s.title, nameW - 2);\n const pack = truncate(s.packLabel, packW - 2);\n const desc = truncate(s.description, descW);\n console.log(\n pc.cyan(pad(name, nameW)) + pc.white(pad(pack, packW)) + pc.dim(desc)\n );\n }\n\n if (res.pagination.hasMore) {\n console.log(pc.dim(`\\n... and ${res.pagination.total - res.skills.length} more. Use --limit to see more.`));\n }\n } catch (err) {\n console.error(pc.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n\nfunction pad(str: string, width: number): string {\n return str.length >= width ? str.slice(0, width) : str + ' '.repeat(width - str.length);\n}\n\nfunction truncate(str: string, max: number): string {\n return str.length <= max ? str : str.slice(0, max - 1) + '…';\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 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. */\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 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 /** Validate that the configured API key works. */\n async validate(): Promise<boolean> {\n try {\n await this.list({ limit: 1 });\n return true;\n } catch {\n return false;\n }\n }\n}\n","import pc from 'picocolors';\nimport { SkillDBClient } from '../client.js';\n\nexport async function listCommand(options: { category?: string; pack?: string; limit?: string }): Promise<void> {\n const client = new SkillDBClient();\n const limit = options.limit ? parseInt(options.limit) : 50;\n\n try {\n const res = await client.list({ category: options.category, pack: options.pack, limit });\n\n if (res.skills.length === 0) {\n console.log(pc.yellow('No skills found.'));\n return;\n }\n\n // If no filter, show categories overview\n if (!options.category && !options.pack) {\n console.log(pc.bold('Categories:'));\n for (const cat of res.meta.categories) {\n console.log(` ${pc.cyan(cat)}`);\n }\n console.log(`\\n${pc.dim(`${res.meta.totalPacks} packs, ${res.pagination.total} skills total`)}`);\n console.log(pc.dim('Use --category or --pack to filter.\\n'));\n }\n\n // Print skills\n const nameW = 30;\n const packW = 24;\n const catW = 20;\n\n console.log(pc.dim(pad('SKILL', nameW) + pad('PACK', packW) + 'CATEGORY'));\n console.log(pc.dim('─'.repeat(nameW + packW + catW)));\n\n for (const s of res.skills) {\n console.log(\n pc.cyan(pad(truncate(s.title, nameW - 2), nameW)) +\n pc.white(pad(truncate(s.packLabel, packW - 2), packW)) +\n pc.dim(s.category)\n );\n }\n\n if (res.pagination.hasMore) {\n console.log(pc.dim(`\\nShowing ${res.skills.length} of ${res.pagination.total}. Use --limit to see more.`));\n }\n } catch (err) {\n console.error(pc.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n\nfunction pad(str: string, width: number): string {\n return str.length >= width ? str.slice(0, width) : str + ' '.repeat(width - str.length);\n}\n\nfunction truncate(str: string, max: number): string {\n return str.length <= max ? str : str.slice(0, max - 1) + '…';\n}\n","import pc from 'picocolors';\nimport { SkillDBClient } from '../client.js';\nimport { initCache, cacheSkill, isCached } from '../cache.js';\n\nexport async function addCommand(packName: string): Promise<void> {\n const client = new SkillDBClient();\n\n console.log(pc.bold(`Adding pack: ${packName}`));\n initCache();\n\n try {\n // Fetch all skills in the pack\n const res = await client.list({ pack: packName, limit: 500, includeContent: true });\n\n if (res.skills.length === 0) {\n console.error(pc.red(`Pack \"${packName}\" not found or empty.`));\n process.exit(1);\n }\n\n let added = 0;\n let skipped = 0;\n\n for (const skill of res.skills) {\n if (isCached(skill.id)) {\n skipped++;\n console.log(pc.dim(` skip ${skill.id} (already cached)`));\n continue;\n }\n\n const filePath = cacheSkill(skill);\n added++;\n console.log(pc.green(` add ${skill.id}`) + pc.dim(` → ${filePath}`));\n }\n\n console.log(\n `\\n${pc.green(`${added} skill${added === 1 ? '' : 's'} added`)}` +\n (skipped > 0 ? pc.dim(`, ${skipped} skipped`) : '')\n );\n\n if (res.skills.some(s => !s.content)) {\n console.log(pc.yellow('\\nNote: Some skills were cached without content (metadata only).'));\n console.log(pc.yellow('Run \"skilldb login\" with a Pro key to download full content.'));\n }\n } catch (err) {\n console.error(pc.red(`Error: ${(err as Error).message}`));\n process.exit(1);\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","import pc from 'picocolors';\nimport { SkillDBClient } from '../client.js';\n\nexport async function infoCommand(id: string): Promise<void> {\n const client = new SkillDBClient();\n\n // Normalize: accept \"pack/skill\" without .md\n if (!id.includes('.md')) {\n id = id + '.md';\n }\n\n try {\n const skill = await client.get(id);\n\n console.log(pc.bold(skill.title));\n console.log(pc.dim('─'.repeat(50)));\n console.log(`${pc.cyan('ID:')} ${skill.id}`);\n console.log(`${pc.cyan('Pack:')} ${skill.packLabel} (${skill.pack})`);\n console.log(`${pc.cyan('Category:')} ${skill.category}`);\n console.log(`${pc.cyan('Lines:')} ${skill.lines}`);\n console.log(`${pc.cyan('Description:')} ${skill.description}`);\n\n if (skill.content) {\n console.log(pc.dim('\\n─── Preview ───────────────────────────────────'));\n const preview = skill.content.split('\\n').slice(0, 20).join('\\n');\n console.log(preview);\n if (skill.content.split('\\n').length > 20) {\n console.log(pc.dim(`\\n... ${skill.lines - 20} more lines`));\n }\n }\n } catch (err) {\n console.error(pc.red(`Error: ${(err as Error).message}`));\n process.exit(1);\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport pc from 'picocolors';\nimport readline from 'node:readline';\nimport { initCache, updateGitignore } from '../cache.js';\n\ntype IDE = 'claude-code' | 'cursor' | 'codex';\n\ninterface Detection {\n ide: IDE;\n label: string;\n configFile: string;\n}\n\nfunction prompt(question: string): Promise<string> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n return new Promise(resolve => {\n rl.question(question, answer => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\nfunction detectIDE(cwd: string): Detection | null {\n // Claude Code\n if (fs.existsSync(path.join(cwd, 'CLAUDE.md')) || fs.existsSync(path.join(cwd, '.claude'))) {\n return {\n ide: 'claude-code',\n label: 'Claude Code',\n configFile: path.join(cwd, 'CLAUDE.md'),\n };\n }\n\n // Cursor\n if (fs.existsSync(path.join(cwd, '.cursor')) || fs.existsSync(path.join(cwd, '.cursorrules'))) {\n return {\n ide: 'cursor',\n label: 'Cursor',\n configFile: fs.existsSync(path.join(cwd, '.cursorrules'))\n ? path.join(cwd, '.cursorrules')\n : path.join(cwd, '.cursor', 'rules'),\n };\n }\n\n // Codex\n if (fs.existsSync(path.join(cwd, 'codex.md')) || fs.existsSync(path.join(cwd, '.codex'))) {\n return {\n ide: 'codex',\n label: 'Codex CLI',\n configFile: path.join(cwd, 'codex.md'),\n };\n }\n\n return null;\n}\n\nfunction getIntegrationSnippet(ide: IDE): string {\n const marker = '<!-- skilldb:start -->';\n const endMarker = '<!-- skilldb:end -->';\n\n const content = `\n## SkillDB Skills\n\nLocal skills are available in \\`.skilldb/skills/\\`. Use them as reference when working on tasks.\nTo add more skills: \\`npx skilldb add <pack-name>\\`\nBrowse available skills: \\`npx skilldb search <query>\\`\n`.trim();\n\n return `\\n\\n${marker}\\n${content}\\n${endMarker}\\n`;\n}\n\nexport async function initCommand(): Promise<void> {\n const cwd = process.cwd();\n console.log(pc.bold('SkillDB Init\\n'));\n\n // Detect IDE\n let detection = detectIDE(cwd);\n\n if (detection) {\n console.log(`Detected: ${pc.cyan(detection.label)}`);\n } else {\n console.log('No IDE config detected. Which are you using?');\n console.log(' 1) Claude Code');\n console.log(' 2) Cursor');\n console.log(' 3) Codex CLI');\n const choice = await prompt('\\nChoice (1-3): ');\n\n const ideMap: Record<string, IDE> = { '1': 'claude-code', '2': 'cursor', '3': 'codex' };\n const ide = ideMap[choice];\n if (!ide) {\n console.log(pc.red('Invalid choice.'));\n process.exit(1);\n }\n\n const configFiles: Record<IDE, string> = {\n 'claude-code': path.join(cwd, 'CLAUDE.md'),\n 'cursor': path.join(cwd, '.cursorrules'),\n 'codex': path.join(cwd, 'codex.md'),\n };\n\n detection = {\n ide,\n label: ide === 'claude-code' ? 'Claude Code' : ide === 'cursor' ? 'Cursor' : 'Codex CLI',\n configFile: configFiles[ide],\n };\n }\n\n // Create .skilldb/ directory\n const cacheDir = initCache(cwd);\n console.log(`Created ${pc.dim(cacheDir)}`);\n\n // Update .gitignore\n updateGitignore(cwd);\n console.log(`Updated ${pc.dim('.gitignore')}`);\n\n // Write integration snippet\n const snippet = getIntegrationSnippet(detection.ide);\n const configFile = detection.configFile;\n\n let existing = '';\n if (fs.existsSync(configFile)) {\n existing = fs.readFileSync(configFile, 'utf-8');\n }\n\n if (existing.includes('<!-- skilldb:start -->')) {\n console.log(pc.dim(`${path.basename(configFile)} already has SkillDB integration.`));\n } else {\n // Ensure parent dir exists\n const dir = path.dirname(configFile);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(configFile, existing + snippet);\n console.log(`Added SkillDB snippet to ${pc.cyan(path.basename(configFile))}`);\n }\n\n console.log(pc.green('\\nDone! Next steps:'));\n console.log(` ${pc.dim('$')} skilldb login ${pc.dim('# save your API key')}`);\n console.log(` ${pc.dim('$')} skilldb search testing ${pc.dim('# find skills')}`);\n console.log(` ${pc.dim('$')} skilldb add <pack> ${pc.dim('# install a skill pack')}`);\n}\n","import pc from 'picocolors';\nimport { saveApiKey } from '../config.js';\nimport { SkillDBClient } from '../client.js';\nimport readline from 'node:readline';\n\nfunction prompt(question: string): Promise<string> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n return new Promise(resolve => {\n rl.question(question, answer => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\nexport async function loginCommand(): Promise<void> {\n console.log(pc.bold('SkillDB Login'));\n console.log('Get your API key at https://skilldb.dev/api-access\\n');\n\n const apiKey = await prompt('API key: ');\n\n if (!apiKey) {\n console.log(pc.red('No API key provided.'));\n process.exit(1);\n }\n\n if (!apiKey.startsWith('sk_')) {\n console.log(pc.yellow('Warning: API key should start with \"sk_\". Saving anyway.'));\n }\n\n // Validate the key\n process.stdout.write('Validating... ');\n const client = new SkillDBClient({ apiKey });\n const valid = await client.validate();\n\n if (valid) {\n const savedTo = saveApiKey(apiKey);\n console.log(pc.green('valid!'));\n console.log(`Saved to ${pc.dim(savedTo)}`);\n } else {\n console.log(pc.yellow('could not validate (saved anyway)'));\n const savedTo = saveApiKey(apiKey);\n console.log(`Saved to ${pc.dim(savedTo)}`);\n }\n}\n"],"mappings":"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","names":["pc","pc","pad","truncate","pc","fs","path","path","fs","pc","pc","pc","fs","path","pc","fs","path","pc","pc","readline","prompt","pc"]}
|