openskillmd 0.2.0 → 0.3.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/README.md +4 -0
- package/dist/index.js +268 -113
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -66,6 +66,10 @@ The API base URL is resolved in this order:
|
|
|
66
66
|
OPENSKILL_API_URL=http://localhost:8080/api osm search testing
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
> Maintainers: see [docs/environments-and-releases.md](docs/environments-and-releases.md)
|
|
70
|
+
> for how environments (staging/prod) and release channels (`latest`/`beta`) work —
|
|
71
|
+
> **one CLI, never per-environment packages.**
|
|
72
|
+
|
|
69
73
|
## Coming soon
|
|
70
74
|
|
|
71
75
|
Authenticated publishing (`osm login` / `osm publish`) is in development and not
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,16 @@ import { Command } from "commander";
|
|
|
7
7
|
import { spawn } from "child_process";
|
|
8
8
|
import { createRequire } from "module";
|
|
9
9
|
import chalk3 from "chalk";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
|
|
12
|
+
// src/lib/config.ts
|
|
13
|
+
import { chmod, mkdir, readFile, writeFile } from "fs/promises";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
import { join } from "path";
|
|
16
|
+
import { z as z2 } from "zod";
|
|
17
|
+
|
|
18
|
+
// src/lib/env.ts
|
|
19
|
+
import { z } from "zod";
|
|
10
20
|
|
|
11
21
|
// src/lib/format.ts
|
|
12
22
|
import chalk2 from "chalk";
|
|
@@ -89,6 +99,155 @@ function jsonResult(obj) {
|
|
|
89
99
|
process.stdout.write(JSON.stringify(obj) + "\n");
|
|
90
100
|
}
|
|
91
101
|
|
|
102
|
+
// src/lib/env.ts
|
|
103
|
+
var envSchema = z.object({
|
|
104
|
+
OPENSKILL_API_URL: z.preprocess(
|
|
105
|
+
// An empty string behaved like "unset" before validation existed
|
|
106
|
+
// (`process.env.X || fallback`); keep that contract.
|
|
107
|
+
(value) => value === "" ? void 0 : value,
|
|
108
|
+
z.url(
|
|
109
|
+
"OPENSKILL_API_URL must be a full URL, e.g. https://staging.openskill.md/api"
|
|
110
|
+
).optional()
|
|
111
|
+
)
|
|
112
|
+
});
|
|
113
|
+
function parseEnv(raw) {
|
|
114
|
+
const result = envSchema.safeParse(raw);
|
|
115
|
+
if (!result.success) {
|
|
116
|
+
const issue = result.error.issues[0];
|
|
117
|
+
const where = issue?.path.join(".") ?? "environment";
|
|
118
|
+
return { ok: false, message: `${where}: ${issue?.message ?? "invalid"}` };
|
|
119
|
+
}
|
|
120
|
+
return { ok: true, env: result.data };
|
|
121
|
+
}
|
|
122
|
+
var cached;
|
|
123
|
+
function getEnv() {
|
|
124
|
+
if (cached) return cached;
|
|
125
|
+
const result = parseEnv(process.env);
|
|
126
|
+
if (!result.ok) {
|
|
127
|
+
console.error(
|
|
128
|
+
errorMessage(
|
|
129
|
+
result.message,
|
|
130
|
+
"Fix or unset the variable, then re-run the command."
|
|
131
|
+
)
|
|
132
|
+
);
|
|
133
|
+
process.exit(2);
|
|
134
|
+
}
|
|
135
|
+
cached = result.env;
|
|
136
|
+
return cached;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/lib/config.ts
|
|
140
|
+
var CONFIG_DIR = join(homedir(), ".openskill");
|
|
141
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
142
|
+
var configSchema = z2.object({
|
|
143
|
+
token: z2.string().min(1).optional(),
|
|
144
|
+
email: z2.email().optional(),
|
|
145
|
+
baseUrl: z2.url().optional()
|
|
146
|
+
});
|
|
147
|
+
async function loadConfig() {
|
|
148
|
+
let raw;
|
|
149
|
+
try {
|
|
150
|
+
raw = await readFile(CONFIG_FILE, "utf-8");
|
|
151
|
+
} catch {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
let parsed;
|
|
155
|
+
try {
|
|
156
|
+
parsed = JSON.parse(raw);
|
|
157
|
+
} catch {
|
|
158
|
+
warnInvalidConfig("not valid JSON");
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
const result = configSchema.safeParse(parsed);
|
|
162
|
+
if (!result.success) {
|
|
163
|
+
const issue = result.error.issues[0];
|
|
164
|
+
warnInvalidConfig(
|
|
165
|
+
`${issue?.path.join(".") ?? "config"}: ${issue?.message ?? "invalid"}`
|
|
166
|
+
);
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
return result.data;
|
|
170
|
+
}
|
|
171
|
+
function warnInvalidConfig(reason) {
|
|
172
|
+
process.stderr.write(
|
|
173
|
+
`Warning: ignoring invalid ${CONFIG_FILE} (${reason}) \u2014 re-run \`osm login\` or fix the file.
|
|
174
|
+
`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
var DEFAULT_API_BASE_URL = "https://staging.openskill.md/api";
|
|
178
|
+
function getApiBaseUrl(config) {
|
|
179
|
+
const raw = getEnv().OPENSKILL_API_URL || config.baseUrl || DEFAULT_API_BASE_URL;
|
|
180
|
+
return raw.replace(/\/+$/, "");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/lib/api.ts
|
|
184
|
+
var _apiBase;
|
|
185
|
+
async function getApiBase() {
|
|
186
|
+
if (_apiBase) return _apiBase;
|
|
187
|
+
const config = await loadConfig();
|
|
188
|
+
_apiBase = getApiBaseUrl(config);
|
|
189
|
+
return _apiBase;
|
|
190
|
+
}
|
|
191
|
+
async function resolveApiBase() {
|
|
192
|
+
return getApiBase();
|
|
193
|
+
}
|
|
194
|
+
async function resolveWebOrigin() {
|
|
195
|
+
return (await getApiBase()).replace(/\/api\/?$/, "");
|
|
196
|
+
}
|
|
197
|
+
var ApiError = class extends Error {
|
|
198
|
+
statusCode;
|
|
199
|
+
constructor(statusCode, message) {
|
|
200
|
+
super(message);
|
|
201
|
+
this.name = "ApiError";
|
|
202
|
+
this.statusCode = statusCode;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
async function fetchApi(path6) {
|
|
206
|
+
const base = await getApiBase();
|
|
207
|
+
const url = `${base}${path6}`;
|
|
208
|
+
const res = await fetch(url);
|
|
209
|
+
if (!res.ok) {
|
|
210
|
+
throw new ApiError(res.status, `API returned ${res.status} for ${path6}`);
|
|
211
|
+
}
|
|
212
|
+
return res.json();
|
|
213
|
+
}
|
|
214
|
+
async function searchApi(query) {
|
|
215
|
+
return fetchApi(`/search?q=${encodeURIComponent(query)}`);
|
|
216
|
+
}
|
|
217
|
+
async function getSkill(slug) {
|
|
218
|
+
return fetchApi(`/skills/${encodeURIComponent(slug)}`);
|
|
219
|
+
}
|
|
220
|
+
async function getBlueprint(slug) {
|
|
221
|
+
return fetchApi(`/blueprints/${encodeURIComponent(slug)}`);
|
|
222
|
+
}
|
|
223
|
+
async function getCollections(params) {
|
|
224
|
+
const qs = new URLSearchParams();
|
|
225
|
+
if (params?.search) qs.set("search", params.search);
|
|
226
|
+
qs.set("limit", String(params?.limit ?? 50));
|
|
227
|
+
const res = await fetchApi(
|
|
228
|
+
`/collections?${qs.toString()}`
|
|
229
|
+
);
|
|
230
|
+
return res.data;
|
|
231
|
+
}
|
|
232
|
+
async function getCollection(slug) {
|
|
233
|
+
return fetchApi(`/collections/${encodeURIComponent(slug)}`);
|
|
234
|
+
}
|
|
235
|
+
async function listMcpServers(params) {
|
|
236
|
+
const qs = new URLSearchParams();
|
|
237
|
+
if (params?.search) qs.set("search", params.search);
|
|
238
|
+
if (params?.category) qs.set("category", params.category);
|
|
239
|
+
qs.set("limit", String(params?.limit ?? 50));
|
|
240
|
+
qs.set("sort", "stars");
|
|
241
|
+
qs.set("order", "desc");
|
|
242
|
+
return fetchApi(`/mcp-servers?${qs.toString()}`);
|
|
243
|
+
}
|
|
244
|
+
async function getMcpCategories() {
|
|
245
|
+
return fetchApi("/mcp-servers/categories");
|
|
246
|
+
}
|
|
247
|
+
async function getMcpServer(slug) {
|
|
248
|
+
return fetchApi(`/mcp-servers/${encodeURIComponent(slug)}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
92
251
|
// src/commands/add.ts
|
|
93
252
|
var require2 = createRequire(import.meta.url);
|
|
94
253
|
function stripSkillsBanner(chunk) {
|
|
@@ -103,17 +262,64 @@ function validateSource(source) {
|
|
|
103
262
|
};
|
|
104
263
|
}
|
|
105
264
|
if (!/[/@:.]/.test(source)) {
|
|
265
|
+
return { ok: false, code: "needs-resolution", slug: source };
|
|
266
|
+
}
|
|
267
|
+
return { ok: true };
|
|
268
|
+
}
|
|
269
|
+
async function resolveSlug(slug) {
|
|
270
|
+
let skill;
|
|
271
|
+
try {
|
|
272
|
+
skill = await getSkill(slug);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
if (err instanceof ApiError && err.statusCode === 404) {
|
|
275
|
+
return {
|
|
276
|
+
ok: false,
|
|
277
|
+
message: `No skill named "${slug}" in the registry.`,
|
|
278
|
+
suggestion: "Run `osm search <query>` to find it, or pass owner/repo directly."
|
|
279
|
+
};
|
|
280
|
+
}
|
|
106
281
|
return {
|
|
107
282
|
ok: false,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
suggestion: "Use owner/repo@skill (e.g. anthropics/skills@frontend-design) or run `osm search <query>` to find it."
|
|
283
|
+
message: `Couldn't reach the registry to resolve "${slug}".`,
|
|
284
|
+
suggestion: "Check your connection (and OPENSKILL_API_URL, if set), or pass owner/repo directly."
|
|
111
285
|
};
|
|
112
286
|
}
|
|
113
|
-
|
|
287
|
+
const skillName = skill.name || null;
|
|
288
|
+
if (skill.githubOwner && skill.githubRepo) {
|
|
289
|
+
const source = `${skill.githubOwner}/${skill.githubRepo}`;
|
|
290
|
+
return {
|
|
291
|
+
ok: true,
|
|
292
|
+
source,
|
|
293
|
+
skillName,
|
|
294
|
+
note: skillName ? `resolved ${slug} \u2192 ${source} (skill: ${skillName})` : `resolved ${slug} \u2192 ${source}`
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (skill.sourceUrl && /^https?:\/\//.test(skill.sourceUrl)) {
|
|
298
|
+
return {
|
|
299
|
+
ok: true,
|
|
300
|
+
source: skill.sourceUrl,
|
|
301
|
+
skillName,
|
|
302
|
+
note: skillName ? `resolved ${slug} \u2192 ${skill.sourceUrl} (skill: ${skillName})` : `resolved ${slug} \u2192 ${skill.sourceUrl}`
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
ok: false,
|
|
307
|
+
message: `"${slug}" lives only in the OpenSkill registry \u2014 it has no GitHub source to install from yet.`,
|
|
308
|
+
suggestion: `Run \`osm info ${slug}\` to inspect it.`
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function toSkillSlug(name) {
|
|
312
|
+
return name.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
114
313
|
}
|
|
115
|
-
function buildSkillsArgs(source, opts = {}) {
|
|
314
|
+
function buildSkillsArgs(source, opts = {}, skillFilter = null) {
|
|
116
315
|
const args = ["add", source, "-y"];
|
|
316
|
+
if (skillFilter) {
|
|
317
|
+
args.push("--skill", skillFilter);
|
|
318
|
+
const kebab = toSkillSlug(skillFilter);
|
|
319
|
+
if (kebab && kebab !== skillFilter.toLowerCase()) {
|
|
320
|
+
args.push("--skill", kebab);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
117
323
|
for (const a of opts.agent ?? []) {
|
|
118
324
|
args.push("-a", a);
|
|
119
325
|
}
|
|
@@ -121,11 +327,12 @@ function buildSkillsArgs(source, opts = {}) {
|
|
|
121
327
|
if (opts.copy) args.push("--copy");
|
|
122
328
|
return args;
|
|
123
329
|
}
|
|
124
|
-
function buildJsonResult(source, opts, code, capturedStderr) {
|
|
330
|
+
function buildJsonResult(source, opts, code, capturedStderr, resolvedFrom = null) {
|
|
125
331
|
if (code === 0) {
|
|
126
332
|
return {
|
|
127
333
|
ok: true,
|
|
128
334
|
source,
|
|
335
|
+
resolvedFrom,
|
|
129
336
|
agent: opts.agent ?? null,
|
|
130
337
|
scope: opts.global ? "user" : "project"
|
|
131
338
|
};
|
|
@@ -133,6 +340,7 @@ function buildJsonResult(source, opts, code, capturedStderr) {
|
|
|
133
340
|
return {
|
|
134
341
|
ok: false,
|
|
135
342
|
source,
|
|
343
|
+
resolvedFrom,
|
|
136
344
|
error: {
|
|
137
345
|
code: `INSTALL_EXIT_${code}`,
|
|
138
346
|
message: capturedStderr.trim() || "install failed"
|
|
@@ -140,10 +348,40 @@ function buildJsonResult(source, opts, code, capturedStderr) {
|
|
|
140
348
|
};
|
|
141
349
|
}
|
|
142
350
|
async function addCommand(source, opts = {}) {
|
|
351
|
+
let installSource = source;
|
|
352
|
+
let resolvedFrom = null;
|
|
353
|
+
let skillFilter = null;
|
|
143
354
|
const validation = validateSource(source);
|
|
144
355
|
if (!validation.ok) {
|
|
145
|
-
|
|
146
|
-
|
|
356
|
+
if (validation.code !== "needs-resolution") {
|
|
357
|
+
console.error(errorMessage(validation.message, validation.suggestion));
|
|
358
|
+
process.exit(validation.code);
|
|
359
|
+
}
|
|
360
|
+
const spinner = opts.json ? null : ora({
|
|
361
|
+
text: `Resolving "${validation.slug}" in the registry...`,
|
|
362
|
+
color: "magenta"
|
|
363
|
+
}).start();
|
|
364
|
+
const resolved = await resolveSlug(validation.slug);
|
|
365
|
+
spinner?.stop();
|
|
366
|
+
if (!resolved.ok) {
|
|
367
|
+
if (opts.json) {
|
|
368
|
+
jsonResult({
|
|
369
|
+
ok: false,
|
|
370
|
+
source,
|
|
371
|
+
resolvedFrom: null,
|
|
372
|
+
error: { code: "SLUG_RESOLUTION_FAILED", message: resolved.message }
|
|
373
|
+
});
|
|
374
|
+
} else {
|
|
375
|
+
console.error(errorMessage(resolved.message, resolved.suggestion));
|
|
376
|
+
}
|
|
377
|
+
process.exit(2);
|
|
378
|
+
}
|
|
379
|
+
if (!opts.json) {
|
|
380
|
+
console.error(chalk3.gray(` \u2192 ${resolved.note}`));
|
|
381
|
+
}
|
|
382
|
+
installSource = resolved.source;
|
|
383
|
+
resolvedFrom = validation.slug;
|
|
384
|
+
skillFilter = resolved.skillName;
|
|
147
385
|
}
|
|
148
386
|
let binPath;
|
|
149
387
|
try {
|
|
@@ -157,13 +395,16 @@ async function addCommand(source, opts = {}) {
|
|
|
157
395
|
);
|
|
158
396
|
process.exit(3);
|
|
159
397
|
}
|
|
160
|
-
const args = buildSkillsArgs(
|
|
398
|
+
const args = buildSkillsArgs(installSource, opts, skillFilter);
|
|
161
399
|
if (!opts.json) {
|
|
162
400
|
console.error(chalk3.gray(" \u2192 fetching skill files\u2026"));
|
|
163
401
|
console.error("");
|
|
164
402
|
}
|
|
165
403
|
const child = spawn(process.execPath, [binPath, ...args], {
|
|
166
404
|
stdio: ["inherit", "pipe", "pipe"],
|
|
405
|
+
// env.ts carve-out: this is a *write* into the child installer's env
|
|
406
|
+
// (opting it out of telemetry), not a read of our own config — so it
|
|
407
|
+
// doesn't go through getEnv().
|
|
167
408
|
env: { ...process.env, DISABLE_TELEMETRY: "1" }
|
|
168
409
|
});
|
|
169
410
|
let capturedStderr = "";
|
|
@@ -189,7 +430,9 @@ async function addCommand(source, opts = {}) {
|
|
|
189
430
|
child.once("error", () => resolve(3));
|
|
190
431
|
});
|
|
191
432
|
if (opts.json) {
|
|
192
|
-
jsonResult(
|
|
433
|
+
jsonResult(
|
|
434
|
+
buildJsonResult(installSource, opts, code, capturedStderr, resolvedFrom)
|
|
435
|
+
);
|
|
193
436
|
}
|
|
194
437
|
process.exit(code);
|
|
195
438
|
}
|
|
@@ -197,97 +440,7 @@ async function addCommand(source, opts = {}) {
|
|
|
197
440
|
// src/commands/browse.ts
|
|
198
441
|
import chalk4 from "chalk";
|
|
199
442
|
import inquirer from "inquirer";
|
|
200
|
-
import
|
|
201
|
-
|
|
202
|
-
// src/lib/config.ts
|
|
203
|
-
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
204
|
-
import { join } from "path";
|
|
205
|
-
import { homedir } from "os";
|
|
206
|
-
var CONFIG_DIR = join(homedir(), ".openskill");
|
|
207
|
-
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
208
|
-
async function loadConfig() {
|
|
209
|
-
try {
|
|
210
|
-
const raw = await readFile(CONFIG_FILE, "utf-8");
|
|
211
|
-
return JSON.parse(raw);
|
|
212
|
-
} catch {
|
|
213
|
-
return {};
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
var DEFAULT_API_BASE_URL = "https://staging.openskill.md/api";
|
|
217
|
-
function getApiBaseUrl(config) {
|
|
218
|
-
const raw = process.env.OPENSKILL_API_URL || config.baseUrl || DEFAULT_API_BASE_URL;
|
|
219
|
-
return raw.replace(/\/+$/, "");
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// src/lib/api.ts
|
|
223
|
-
var _apiBase;
|
|
224
|
-
async function getApiBase() {
|
|
225
|
-
if (_apiBase) return _apiBase;
|
|
226
|
-
const config = await loadConfig();
|
|
227
|
-
_apiBase = getApiBaseUrl(config);
|
|
228
|
-
return _apiBase;
|
|
229
|
-
}
|
|
230
|
-
async function resolveApiBase() {
|
|
231
|
-
return getApiBase();
|
|
232
|
-
}
|
|
233
|
-
async function resolveWebOrigin() {
|
|
234
|
-
return (await getApiBase()).replace(/\/api\/?$/, "");
|
|
235
|
-
}
|
|
236
|
-
var ApiError = class extends Error {
|
|
237
|
-
statusCode;
|
|
238
|
-
constructor(statusCode, message) {
|
|
239
|
-
super(message);
|
|
240
|
-
this.name = "ApiError";
|
|
241
|
-
this.statusCode = statusCode;
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
async function fetchApi(path6) {
|
|
245
|
-
const base = await getApiBase();
|
|
246
|
-
const url = `${base}${path6}`;
|
|
247
|
-
const res = await fetch(url);
|
|
248
|
-
if (!res.ok) {
|
|
249
|
-
throw new ApiError(res.status, `API returned ${res.status} for ${path6}`);
|
|
250
|
-
}
|
|
251
|
-
return res.json();
|
|
252
|
-
}
|
|
253
|
-
async function searchApi(query) {
|
|
254
|
-
return fetchApi(`/search?q=${encodeURIComponent(query)}`);
|
|
255
|
-
}
|
|
256
|
-
async function getSkill(slug) {
|
|
257
|
-
return fetchApi(`/skills/${encodeURIComponent(slug)}`);
|
|
258
|
-
}
|
|
259
|
-
async function getBlueprint(slug) {
|
|
260
|
-
return fetchApi(`/blueprints/${encodeURIComponent(slug)}`);
|
|
261
|
-
}
|
|
262
|
-
async function getCollections(params) {
|
|
263
|
-
const qs = new URLSearchParams();
|
|
264
|
-
if (params?.search) qs.set("search", params.search);
|
|
265
|
-
qs.set("limit", String(params?.limit ?? 50));
|
|
266
|
-
const res = await fetchApi(
|
|
267
|
-
`/collections?${qs.toString()}`
|
|
268
|
-
);
|
|
269
|
-
return res.data;
|
|
270
|
-
}
|
|
271
|
-
async function getCollection(slug) {
|
|
272
|
-
return fetchApi(`/collections/${encodeURIComponent(slug)}`);
|
|
273
|
-
}
|
|
274
|
-
async function listMcpServers(params) {
|
|
275
|
-
const qs = new URLSearchParams();
|
|
276
|
-
if (params?.search) qs.set("search", params.search);
|
|
277
|
-
if (params?.category) qs.set("category", params.category);
|
|
278
|
-
qs.set("limit", String(params?.limit ?? 50));
|
|
279
|
-
qs.set("sort", "stars");
|
|
280
|
-
qs.set("order", "desc");
|
|
281
|
-
return fetchApi(`/mcp-servers?${qs.toString()}`);
|
|
282
|
-
}
|
|
283
|
-
async function getMcpCategories() {
|
|
284
|
-
return fetchApi("/mcp-servers/categories");
|
|
285
|
-
}
|
|
286
|
-
async function getMcpServer(slug) {
|
|
287
|
-
return fetchApi(`/mcp-servers/${encodeURIComponent(slug)}`);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// src/commands/browse.ts
|
|
443
|
+
import ora2 from "ora";
|
|
291
444
|
var EXIT_SENTINEL = "__exit__";
|
|
292
445
|
async function browseCommand() {
|
|
293
446
|
try {
|
|
@@ -316,7 +469,7 @@ async function browseCommand() {
|
|
|
316
469
|
}
|
|
317
470
|
}
|
|
318
471
|
async function browseCollections() {
|
|
319
|
-
const spinner =
|
|
472
|
+
const spinner = ora2({ text: "Loading collections...", color: "magenta" }).start();
|
|
320
473
|
const collections = await getCollections({ limit: 50 });
|
|
321
474
|
spinner.stop();
|
|
322
475
|
if (collections.length === 0) {
|
|
@@ -338,7 +491,7 @@ async function browseCollections() {
|
|
|
338
491
|
}
|
|
339
492
|
]);
|
|
340
493
|
if (slug === EXIT_SENTINEL) return;
|
|
341
|
-
const detailSpinner =
|
|
494
|
+
const detailSpinner = ora2({ text: "Loading collection...", color: "magenta" }).start();
|
|
342
495
|
const collection = await getCollection(slug);
|
|
343
496
|
detailSpinner.stop();
|
|
344
497
|
renderCollection(collection);
|
|
@@ -416,7 +569,7 @@ function renderCollection(collection) {
|
|
|
416
569
|
);
|
|
417
570
|
}
|
|
418
571
|
async function browseMcpServers() {
|
|
419
|
-
const spinner =
|
|
572
|
+
const spinner = ora2({ text: "Loading MCP categories...", color: "magenta" }).start();
|
|
420
573
|
const categories = await getMcpCategories();
|
|
421
574
|
spinner.stop();
|
|
422
575
|
if (categories.length === 0) {
|
|
@@ -438,7 +591,7 @@ async function browseMcpServers() {
|
|
|
438
591
|
}
|
|
439
592
|
]);
|
|
440
593
|
if (category === EXIT_SENTINEL) return;
|
|
441
|
-
const listSpinner =
|
|
594
|
+
const listSpinner = ora2({ text: "Loading MCP servers...", color: "magenta" }).start();
|
|
442
595
|
const { data: servers } = await listMcpServers({ category, limit: 50 });
|
|
443
596
|
listSpinner.stop();
|
|
444
597
|
process.stderr.write(sectionHeader(`MCP Servers \xB7 ${category}`) + "\n");
|
|
@@ -467,9 +620,9 @@ async function browseMcpServers() {
|
|
|
467
620
|
|
|
468
621
|
// src/commands/info.ts
|
|
469
622
|
import chalk5 from "chalk";
|
|
470
|
-
import
|
|
623
|
+
import ora3 from "ora";
|
|
471
624
|
async function infoCommand(slug) {
|
|
472
|
-
const spinner =
|
|
625
|
+
const spinner = ora3({ text: `Fetching info for "${slug}"...`, color: "magenta" }).start();
|
|
473
626
|
try {
|
|
474
627
|
const skill = await getSkill(slug);
|
|
475
628
|
spinner.stop();
|
|
@@ -633,7 +786,9 @@ import path from "path";
|
|
|
633
786
|
var SAFE_SLUG_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
634
787
|
function sanitizeSlug(slug) {
|
|
635
788
|
if (!SAFE_SLUG_PATTERN.test(slug)) {
|
|
636
|
-
throw new Error(
|
|
789
|
+
throw new Error(
|
|
790
|
+
`Invalid slug "${slug}". Slugs must contain only lowercase letters, numbers, hyphens, dots, and underscores.`
|
|
791
|
+
);
|
|
637
792
|
}
|
|
638
793
|
if (slug.includes("..") || slug.includes("/") || slug.includes("\\")) {
|
|
639
794
|
throw new Error(`Invalid slug "${slug}". Path separators are not allowed.`);
|
|
@@ -989,9 +1144,9 @@ async function removeCommand(slug) {
|
|
|
989
1144
|
import fs4 from "fs";
|
|
990
1145
|
import path5 from "path";
|
|
991
1146
|
import chalk10 from "chalk";
|
|
992
|
-
import
|
|
1147
|
+
import ora4 from "ora";
|
|
993
1148
|
async function routerInstallCommand() {
|
|
994
|
-
const spinner =
|
|
1149
|
+
const spinner = ora4({ text: "Downloading OpenSkill router...", color: "magenta" }).start();
|
|
995
1150
|
try {
|
|
996
1151
|
const origin = await resolveWebOrigin();
|
|
997
1152
|
const res = await fetch(`${origin}/skills/router/SKILL.md`);
|
|
@@ -1186,9 +1341,9 @@ async function scoreCommand(filepath) {
|
|
|
1186
1341
|
|
|
1187
1342
|
// src/commands/search.ts
|
|
1188
1343
|
import chalk12 from "chalk";
|
|
1189
|
-
import
|
|
1344
|
+
import ora5 from "ora";
|
|
1190
1345
|
async function searchCommand(query) {
|
|
1191
|
-
const spinner =
|
|
1346
|
+
const spinner = ora5({ text: `Searching for "${query}"...`, color: "magenta" }).start();
|
|
1192
1347
|
try {
|
|
1193
1348
|
const results = await searchApi(query);
|
|
1194
1349
|
spinner.stop();
|
|
@@ -1273,7 +1428,7 @@ async function searchCommand(query) {
|
|
|
1273
1428
|
}
|
|
1274
1429
|
|
|
1275
1430
|
// src/program.ts
|
|
1276
|
-
var VERSION = "0.
|
|
1431
|
+
var VERSION = "0.3.0";
|
|
1277
1432
|
var BrandedCommand = class _BrandedCommand extends Command {
|
|
1278
1433
|
createCommand(name) {
|
|
1279
1434
|
return new _BrandedCommand(name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openskillmd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The OpenSkill CLI (osm) — search, add, score, and explore AI agent skills from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"inquirer": "^12.6.0",
|
|
55
55
|
"open": "^10.2.0",
|
|
56
56
|
"ora": "^8.2.0",
|
|
57
|
-
"skills": "^1.4.6"
|
|
57
|
+
"skills": "^1.4.6",
|
|
58
|
+
"zod": "^4.4.3"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
61
|
"@biomejs/biome": "^2.0.0",
|