@agent-nexus/csreg 0.1.5 → 0.1.7
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/chunk-VTCXMN4H.js +725 -0
- package/dist/index.js +128 -739
- package/dist/push-7P5F2O6O.js +6 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,206 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ApiClient,
|
|
4
|
+
CliError,
|
|
5
|
+
MAX_ARCHIVE_SIZE,
|
|
6
|
+
extract,
|
|
7
|
+
findClaudeSkillsDir,
|
|
8
|
+
formatTable,
|
|
9
|
+
getApiUrl,
|
|
10
|
+
getAuthToken,
|
|
11
|
+
handleError,
|
|
12
|
+
info,
|
|
13
|
+
pack,
|
|
14
|
+
parseManifest,
|
|
15
|
+
pushCommand,
|
|
16
|
+
runValidation,
|
|
17
|
+
setConfig,
|
|
18
|
+
spinner,
|
|
19
|
+
success,
|
|
20
|
+
validateCommand,
|
|
21
|
+
warn
|
|
22
|
+
} from "./chunk-VTCXMN4H.js";
|
|
2
23
|
|
|
3
24
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
25
|
+
import { Command as Command11 } from "commander";
|
|
5
26
|
|
|
6
27
|
// src/commands/login.ts
|
|
7
28
|
import { Command } from "commander";
|
|
8
29
|
import { input } from "@inquirer/prompts";
|
|
9
|
-
|
|
10
|
-
// src/config.ts
|
|
11
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
12
|
-
import { join } from "path";
|
|
13
|
-
import { homedir } from "os";
|
|
14
|
-
var CONFIG_DIR = join(homedir(), ".config", "csreg");
|
|
15
|
-
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
16
|
-
function getConfig() {
|
|
17
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
22
|
-
return JSON.parse(raw);
|
|
23
|
-
} catch {
|
|
24
|
-
return {};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function setConfig(updates) {
|
|
28
|
-
const current = getConfig();
|
|
29
|
-
const merged = { ...current, ...updates };
|
|
30
|
-
mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
31
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
32
|
-
}
|
|
33
|
-
function getApiUrl() {
|
|
34
|
-
return process.env.CSREG_API_URL ?? getConfig().apiUrl ?? "https://www.csreg.nexus";
|
|
35
|
-
}
|
|
36
|
-
function getAuthToken() {
|
|
37
|
-
return process.env.CSREG_TOKEN ?? getConfig().token;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// src/lib/errors.ts
|
|
41
|
-
import chalk from "chalk";
|
|
42
|
-
var CliError = class extends Error {
|
|
43
|
-
suggestions;
|
|
44
|
-
constructor(message, suggestions = []) {
|
|
45
|
-
super(message);
|
|
46
|
-
this.name = "CliError";
|
|
47
|
-
this.suggestions = suggestions;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
function handleError(err) {
|
|
51
|
-
if (err instanceof CliError) {
|
|
52
|
-
console.error(chalk.red("Error:") + " " + err.message);
|
|
53
|
-
if (err.suggestions.length > 0) {
|
|
54
|
-
console.error("");
|
|
55
|
-
console.error(chalk.yellow("Suggestions:"));
|
|
56
|
-
for (const suggestion of err.suggestions) {
|
|
57
|
-
console.error(" " + chalk.dim("-") + " " + suggestion);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
} else if (err instanceof Error) {
|
|
61
|
-
console.error(chalk.red("Error:") + " " + err.message);
|
|
62
|
-
} else {
|
|
63
|
-
console.error(chalk.red("Error:") + " " + String(err));
|
|
64
|
-
}
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// src/api-client.ts
|
|
69
|
-
var MAX_RETRIES = 3;
|
|
70
|
-
var BASE_DELAY_MS = 500;
|
|
71
|
-
var ApiClient = class {
|
|
72
|
-
baseUrl;
|
|
73
|
-
token;
|
|
74
|
-
constructor() {
|
|
75
|
-
this.baseUrl = getApiUrl();
|
|
76
|
-
this.token = getAuthToken();
|
|
77
|
-
}
|
|
78
|
-
buildUrl(path, query) {
|
|
79
|
-
const url = new URL(path, this.baseUrl);
|
|
80
|
-
if (query) {
|
|
81
|
-
for (const [key, value] of Object.entries(query)) {
|
|
82
|
-
url.searchParams.set(key, value);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return url.toString();
|
|
86
|
-
}
|
|
87
|
-
buildHeaders(extra) {
|
|
88
|
-
const headers = {
|
|
89
|
-
"Content-Type": "application/json",
|
|
90
|
-
"Accept": "application/json",
|
|
91
|
-
...extra
|
|
92
|
-
};
|
|
93
|
-
if (this.token) {
|
|
94
|
-
headers["Authorization"] = `Bearer ${this.token}`;
|
|
95
|
-
}
|
|
96
|
-
return headers;
|
|
97
|
-
}
|
|
98
|
-
async handleResponse(response) {
|
|
99
|
-
if (response.ok) {
|
|
100
|
-
if (response.status === 204) {
|
|
101
|
-
return void 0;
|
|
102
|
-
}
|
|
103
|
-
return response.json();
|
|
104
|
-
}
|
|
105
|
-
let errorBody;
|
|
106
|
-
try {
|
|
107
|
-
errorBody = await response.json();
|
|
108
|
-
} catch {
|
|
109
|
-
}
|
|
110
|
-
const detail = errorBody?.detail ?? response.statusText;
|
|
111
|
-
const title = errorBody?.title ?? `HTTP ${response.status}`;
|
|
112
|
-
const suggestions = [];
|
|
113
|
-
if (response.status === 401) {
|
|
114
|
-
suggestions.push("Run `csreg login` to authenticate.");
|
|
115
|
-
} else if (response.status === 403) {
|
|
116
|
-
suggestions.push("You may not have permission for this action.");
|
|
117
|
-
} else if (response.status === 404) {
|
|
118
|
-
suggestions.push("Check that the skill name and scope are correct.");
|
|
119
|
-
}
|
|
120
|
-
throw new CliError(`${title}: ${detail}`, suggestions);
|
|
121
|
-
}
|
|
122
|
-
async requestWithRetry(method, path, opts) {
|
|
123
|
-
const url = this.buildUrl(path, opts?.query);
|
|
124
|
-
const headers = this.buildHeaders(opts?.headers);
|
|
125
|
-
let lastError;
|
|
126
|
-
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
127
|
-
try {
|
|
128
|
-
const response = await fetch(url, {
|
|
129
|
-
method,
|
|
130
|
-
headers,
|
|
131
|
-
body: opts?.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
132
|
-
});
|
|
133
|
-
if (response.status >= 500 && attempt < MAX_RETRIES - 1) {
|
|
134
|
-
const delay = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
135
|
-
await new Promise((resolve7) => setTimeout(resolve7, delay));
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
return await this.handleResponse(response);
|
|
139
|
-
} catch (error2) {
|
|
140
|
-
lastError = error2;
|
|
141
|
-
if (error2 instanceof CliError) {
|
|
142
|
-
throw error2;
|
|
143
|
-
}
|
|
144
|
-
if (attempt < MAX_RETRIES - 1) {
|
|
145
|
-
const delay = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
146
|
-
await new Promise((resolve7) => setTimeout(resolve7, delay));
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
throw lastError instanceof CliError ? lastError : new CliError(`Request failed: ${String(lastError)}`, [
|
|
152
|
-
"Check your internet connection.",
|
|
153
|
-
"The API server may be unavailable."
|
|
154
|
-
]);
|
|
155
|
-
}
|
|
156
|
-
async get(path, opts) {
|
|
157
|
-
return this.requestWithRetry("GET", path, opts);
|
|
158
|
-
}
|
|
159
|
-
async post(path, opts) {
|
|
160
|
-
return this.requestWithRetry("POST", path, opts);
|
|
161
|
-
}
|
|
162
|
-
async put(path, opts) {
|
|
163
|
-
return this.requestWithRetry("PUT", path, opts);
|
|
164
|
-
}
|
|
165
|
-
async patch(path, opts) {
|
|
166
|
-
return this.requestWithRetry("PATCH", path, opts);
|
|
167
|
-
}
|
|
168
|
-
async delete(path, opts) {
|
|
169
|
-
return this.requestWithRetry("DELETE", path, opts);
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
// src/lib/output.ts
|
|
174
|
-
import chalk2 from "chalk";
|
|
175
|
-
import ora from "ora";
|
|
176
|
-
import Table from "cli-table3";
|
|
177
|
-
function formatTable(headers, rows) {
|
|
178
|
-
const table = new Table({
|
|
179
|
-
head: headers.map((h) => chalk2.bold.cyan(h)),
|
|
180
|
-
style: { head: [], border: [] }
|
|
181
|
-
});
|
|
182
|
-
for (const row of rows) {
|
|
183
|
-
table.push(row);
|
|
184
|
-
}
|
|
185
|
-
return table.toString();
|
|
186
|
-
}
|
|
187
|
-
function spinner(text) {
|
|
188
|
-
return ora({ text, color: "cyan" }).start();
|
|
189
|
-
}
|
|
190
|
-
function success(msg) {
|
|
191
|
-
console.log(chalk2.green("\u2713") + " " + msg);
|
|
192
|
-
}
|
|
193
|
-
function error(msg) {
|
|
194
|
-
console.error(chalk2.red("\u2717") + " " + msg);
|
|
195
|
-
}
|
|
196
|
-
function warn(msg) {
|
|
197
|
-
console.log(chalk2.yellow("!") + " " + msg);
|
|
198
|
-
}
|
|
199
|
-
function info(msg) {
|
|
200
|
-
console.log(chalk2.blue("i") + " " + msg);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// src/commands/login.ts
|
|
204
30
|
var loginCommand = new Command("login").description("Authenticate with the Skills Registry").option("--token <token>", "Provide auth token directly").action(async (opts) => {
|
|
205
31
|
try {
|
|
206
32
|
let token = opts.token;
|
|
@@ -260,8 +86,8 @@ var whoamiCommand = new Command3("whoami").description("Display the currently au
|
|
|
260
86
|
// src/commands/init.ts
|
|
261
87
|
import { Command as Command4 } from "commander";
|
|
262
88
|
import { input as input2, confirm } from "@inquirer/prompts";
|
|
263
|
-
import { mkdirSync
|
|
264
|
-
import { join
|
|
89
|
+
import { mkdirSync, writeFileSync, existsSync } from "fs";
|
|
90
|
+
import { join, resolve } from "path";
|
|
265
91
|
var initCommand = new Command4("init").description("Initialize a new skill project").argument("[dir]", "Directory to initialize the skill in").action(async (dir) => {
|
|
266
92
|
try {
|
|
267
93
|
const name = await input2({
|
|
@@ -298,18 +124,18 @@ var initCommand = new Command4("init").description("Initialize a new skill proje
|
|
|
298
124
|
message: "User-invocable (show in /slash command menu)?",
|
|
299
125
|
default: true
|
|
300
126
|
});
|
|
301
|
-
const defaultDir = dir ??
|
|
127
|
+
const defaultDir = dir ?? join(".claude", "skills", name.trim());
|
|
302
128
|
const chosenDir = await input2({
|
|
303
129
|
message: "Directory to create skill in:",
|
|
304
130
|
default: defaultDir
|
|
305
131
|
});
|
|
306
132
|
const targetDir = resolve(chosenDir.trim());
|
|
307
|
-
if (
|
|
133
|
+
if (existsSync(targetDir)) {
|
|
308
134
|
throw new CliError(`Directory already exists: ${targetDir}`, [
|
|
309
135
|
"Choose a different name or directory."
|
|
310
136
|
]);
|
|
311
137
|
}
|
|
312
|
-
|
|
138
|
+
mkdirSync(targetDir, { recursive: true });
|
|
313
139
|
const frontmatterLines = [
|
|
314
140
|
"---",
|
|
315
141
|
`name: ${name.trim()}`,
|
|
@@ -335,8 +161,8 @@ when this skill is invoked.
|
|
|
335
161
|
You can use \`$ARGUMENTS\` to reference arguments passed by the user.
|
|
336
162
|
For example: \`/my-skill some-argument\` makes \`$ARGUMENTS\` = "some-argument".
|
|
337
163
|
`;
|
|
338
|
-
|
|
339
|
-
|
|
164
|
+
writeFileSync(
|
|
165
|
+
join(targetDir, "SKILL.md"),
|
|
340
166
|
skillContent,
|
|
341
167
|
"utf-8"
|
|
342
168
|
);
|
|
@@ -357,315 +183,14 @@ For example: \`/my-skill some-argument\` makes \`$ARGUMENTS\` = "some-argument".
|
|
|
357
183
|
}
|
|
358
184
|
});
|
|
359
185
|
|
|
360
|
-
// src/commands/validate.ts
|
|
361
|
-
import { Command as Command5 } from "commander";
|
|
362
|
-
import { resolve as resolve3, join as join5, basename } from "path";
|
|
363
|
-
import { existsSync as existsSync5, statSync, readdirSync as readdirSync2, lstatSync } from "fs";
|
|
364
|
-
|
|
365
|
-
// src/lib/manifest.ts
|
|
366
|
-
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
367
|
-
import { join as join3 } from "path";
|
|
368
|
-
import { parse as parseYaml } from "yaml";
|
|
369
|
-
var SKILL_FILE = "SKILL.md";
|
|
370
|
-
function parseManifest(dir) {
|
|
371
|
-
const skillPath = join3(dir, SKILL_FILE);
|
|
372
|
-
if (!existsSync3(skillPath)) {
|
|
373
|
-
throw new CliError(`No ${SKILL_FILE} found in ${dir}`, [
|
|
374
|
-
"Run `csreg init` to create a new skill.",
|
|
375
|
-
"A valid skill requires a SKILL.md file with YAML frontmatter."
|
|
376
|
-
]);
|
|
377
|
-
}
|
|
378
|
-
const raw = readFileSync2(skillPath, "utf-8");
|
|
379
|
-
const frontmatterMatch = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
|
|
380
|
-
if (!frontmatterMatch) {
|
|
381
|
-
throw new CliError(`${SKILL_FILE} is missing YAML frontmatter.`, [
|
|
382
|
-
"SKILL.md must start with --- delimited YAML frontmatter.",
|
|
383
|
-
"Example:\n ---\n name: my-skill\n description: Does something\n ---\n \n Your instructions here."
|
|
384
|
-
]);
|
|
385
|
-
}
|
|
386
|
-
const [, frontmatterRaw, body] = frontmatterMatch;
|
|
387
|
-
let parsed;
|
|
388
|
-
try {
|
|
389
|
-
parsed = parseYaml(frontmatterRaw);
|
|
390
|
-
} catch (err) {
|
|
391
|
-
throw new CliError(`Failed to parse ${SKILL_FILE} frontmatter: ${String(err)}`, [
|
|
392
|
-
"Check that the YAML between --- delimiters is valid."
|
|
393
|
-
]);
|
|
394
|
-
}
|
|
395
|
-
if (!parsed || typeof parsed !== "object") {
|
|
396
|
-
throw new CliError(`${SKILL_FILE} frontmatter is empty or not an object.`, [
|
|
397
|
-
'Add at least a "name" and "description" field.'
|
|
398
|
-
]);
|
|
399
|
-
}
|
|
400
|
-
return { ...parsed, _body: body.trim() };
|
|
401
|
-
}
|
|
402
|
-
function validateManifest(manifest) {
|
|
403
|
-
const errors = [];
|
|
404
|
-
if (!manifest.name || typeof manifest.name !== "string") {
|
|
405
|
-
errors.push('Missing or invalid "name" field in frontmatter.');
|
|
406
|
-
} else {
|
|
407
|
-
if (manifest.name.length > 64) {
|
|
408
|
-
errors.push('"name" must be at most 64 characters.');
|
|
409
|
-
}
|
|
410
|
-
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(manifest.name)) {
|
|
411
|
-
errors.push('"name" must be lowercase alphanumeric with hyphens, not starting or ending with a hyphen.');
|
|
412
|
-
}
|
|
413
|
-
if (manifest.name.includes("--")) {
|
|
414
|
-
errors.push('"name" must not contain consecutive hyphens (--).');
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
if (!manifest.description || typeof manifest.description !== "string") {
|
|
418
|
-
errors.push('Missing "description" field. Claude uses this to decide when to invoke the skill.');
|
|
419
|
-
} else if (manifest.description.length > 1024) {
|
|
420
|
-
errors.push('"description" must be at most 1024 characters.');
|
|
421
|
-
}
|
|
422
|
-
if (manifest.version !== void 0) {
|
|
423
|
-
const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$/;
|
|
424
|
-
if (!semverRegex.test(manifest.version)) {
|
|
425
|
-
errors.push('"version" must be valid semver (e.g., 1.0.0).');
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (manifest.scope !== void 0 && typeof manifest.scope === "string") {
|
|
429
|
-
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(manifest.scope)) {
|
|
430
|
-
errors.push('"scope" must be lowercase alphanumeric with hyphens.');
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
if (manifest["allowed-tools"] !== void 0 && typeof manifest["allowed-tools"] !== "string") {
|
|
434
|
-
errors.push('"allowed-tools" must be a string (space-delimited list of tools).');
|
|
435
|
-
}
|
|
436
|
-
if (manifest["disable-model-invocation"] !== void 0 && typeof manifest["disable-model-invocation"] !== "boolean") {
|
|
437
|
-
errors.push('"disable-model-invocation" must be a boolean.');
|
|
438
|
-
}
|
|
439
|
-
if (manifest["user-invocable"] !== void 0 && typeof manifest["user-invocable"] !== "boolean") {
|
|
440
|
-
errors.push('"user-invocable" must be a boolean.');
|
|
441
|
-
}
|
|
442
|
-
return errors;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// src/lib/discovery.ts
|
|
446
|
-
import { resolve as resolve2, join as join4, dirname } from "path";
|
|
447
|
-
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
448
|
-
function findClaudeSkillsDir() {
|
|
449
|
-
let dir = resolve2(".");
|
|
450
|
-
const parts = dir.split("/");
|
|
451
|
-
const claudeIdx = parts.lastIndexOf(".claude");
|
|
452
|
-
if (claudeIdx >= 0) {
|
|
453
|
-
const claudeRoot = parts.slice(0, claudeIdx + 1).join("/");
|
|
454
|
-
return join4(claudeRoot, "skills");
|
|
455
|
-
}
|
|
456
|
-
while (dir !== dirname(dir)) {
|
|
457
|
-
const claudeDir = join4(dir, ".claude");
|
|
458
|
-
if (existsSync4(claudeDir)) {
|
|
459
|
-
return join4(claudeDir, "skills");
|
|
460
|
-
}
|
|
461
|
-
dir = dirname(dir);
|
|
462
|
-
}
|
|
463
|
-
return null;
|
|
464
|
-
}
|
|
465
|
-
function discoverSkillDirs(parentDir) {
|
|
466
|
-
if (!existsSync4(parentDir)) return [];
|
|
467
|
-
const dirs = [];
|
|
468
|
-
const entries = readdirSync(parentDir, { withFileTypes: true });
|
|
469
|
-
for (const entry of entries) {
|
|
470
|
-
if (!entry.isDirectory()) continue;
|
|
471
|
-
if (entry.name.startsWith(".")) continue;
|
|
472
|
-
const skillMd = join4(parentDir, entry.name, "SKILL.md");
|
|
473
|
-
if (existsSync4(skillMd)) {
|
|
474
|
-
dirs.push(join4(parentDir, entry.name));
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
return dirs.sort();
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// src/commands/validate.ts
|
|
481
|
-
var MAX_FILE_COUNT = 100;
|
|
482
|
-
var MAX_FILE_SIZE = 500 * 1024;
|
|
483
|
-
var MAX_TOTAL_SIZE = 2 * 1024 * 1024;
|
|
484
|
-
function collectFiles(dir, prefix = "") {
|
|
485
|
-
const files = [];
|
|
486
|
-
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
487
|
-
for (const entry of entries) {
|
|
488
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
489
|
-
const fullPath = join5(dir, entry.name);
|
|
490
|
-
const lstat = lstatSync(fullPath);
|
|
491
|
-
if (lstat.isSymbolicLink()) continue;
|
|
492
|
-
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
493
|
-
if (entry.isDirectory()) {
|
|
494
|
-
files.push(...collectFiles(fullPath, relativePath));
|
|
495
|
-
} else {
|
|
496
|
-
const stat = statSync(fullPath);
|
|
497
|
-
files.push({ path: relativePath, size: stat.size });
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
return files;
|
|
501
|
-
}
|
|
502
|
-
function runValidation(dir) {
|
|
503
|
-
const errors = [];
|
|
504
|
-
const warnings = [];
|
|
505
|
-
if (!existsSync5(join5(dir, "SKILL.md"))) {
|
|
506
|
-
errors.push("Missing SKILL.md \u2014 every skill must have a SKILL.md file with YAML frontmatter.");
|
|
507
|
-
return { valid: false, errors, warnings };
|
|
508
|
-
}
|
|
509
|
-
const manifest = parseManifest(dir);
|
|
510
|
-
const manifestErrors = validateManifest(manifest);
|
|
511
|
-
errors.push(...manifestErrors);
|
|
512
|
-
if (!manifest._body) {
|
|
513
|
-
warnings.push("SKILL.md has no instructions body after the frontmatter. Add skill instructions below the --- delimiter.");
|
|
514
|
-
}
|
|
515
|
-
if (!manifest.version) {
|
|
516
|
-
warnings.push('No "version" in frontmatter. Required for publishing to the registry (e.g., version: "1.0.0").');
|
|
517
|
-
}
|
|
518
|
-
if (!manifest.scope) {
|
|
519
|
-
warnings.push('No "scope" in frontmatter. Required for publishing to the registry (e.g., scope: my-team).');
|
|
520
|
-
}
|
|
521
|
-
const dirName = dir.split("/").pop();
|
|
522
|
-
if (manifest.name && dirName && dirName !== manifest.name) {
|
|
523
|
-
warnings.push(`Directory name "${dirName}" doesn't match skill name "${manifest.name}". They should match per the Agent Skills spec.`);
|
|
524
|
-
}
|
|
525
|
-
const files = collectFiles(dir);
|
|
526
|
-
if (files.length > MAX_FILE_COUNT) {
|
|
527
|
-
errors.push(`Too many files: ${files.length} (max ${MAX_FILE_COUNT}).`);
|
|
528
|
-
}
|
|
529
|
-
let totalSize = 0;
|
|
530
|
-
for (const file of files) {
|
|
531
|
-
totalSize += file.size;
|
|
532
|
-
if (file.size > MAX_FILE_SIZE) {
|
|
533
|
-
errors.push(`File too large: ${file.path} (${(file.size / 1024).toFixed(1)}KB, max 500KB).`);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
if (totalSize > MAX_TOTAL_SIZE) {
|
|
537
|
-
errors.push(`Total size too large: ${(totalSize / 1024 / 1024).toFixed(2)}MB (max 2MB).`);
|
|
538
|
-
}
|
|
539
|
-
return { valid: errors.length === 0, errors, warnings };
|
|
540
|
-
}
|
|
541
|
-
var validateCommand = new Command5("validate").description("Validate a skill package").argument("[dir]", "Skill directory", ".").option("--all", "Validate all skills in .claude/skills/").action(async (dir, opts) => {
|
|
542
|
-
try {
|
|
543
|
-
if (opts.all) {
|
|
544
|
-
const skillsDir = findClaudeSkillsDir();
|
|
545
|
-
if (!skillsDir) {
|
|
546
|
-
throw new CliError("Cannot find .claude/skills/ directory.", [
|
|
547
|
-
"Run this from a project with a .claude/ directory."
|
|
548
|
-
]);
|
|
549
|
-
}
|
|
550
|
-
const skillDirs = discoverSkillDirs(skillsDir);
|
|
551
|
-
if (skillDirs.length === 0) {
|
|
552
|
-
throw new CliError(`No skills found in ${skillsDir}/`, [
|
|
553
|
-
"Skills must be directories containing a SKILL.md file."
|
|
554
|
-
]);
|
|
555
|
-
}
|
|
556
|
-
console.log(`Validating ${skillDirs.length} skill(s) in ${skillsDir}/
|
|
557
|
-
`);
|
|
558
|
-
let passed = 0;
|
|
559
|
-
let failed = 0;
|
|
560
|
-
for (const skillDir of skillDirs) {
|
|
561
|
-
const name = basename(skillDir);
|
|
562
|
-
const result2 = runValidation(skillDir);
|
|
563
|
-
if (result2.valid) {
|
|
564
|
-
success(`${name}`);
|
|
565
|
-
passed++;
|
|
566
|
-
} else {
|
|
567
|
-
error(`${name}`);
|
|
568
|
-
for (const e of result2.errors) {
|
|
569
|
-
console.error(` ${e}`);
|
|
570
|
-
}
|
|
571
|
-
failed++;
|
|
572
|
-
}
|
|
573
|
-
for (const w of result2.warnings) {
|
|
574
|
-
warn(` ${name}: ${w}`);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
console.log("");
|
|
578
|
-
if (failed === 0) {
|
|
579
|
-
success(`All ${passed} skill(s) are valid.`);
|
|
580
|
-
} else {
|
|
581
|
-
throw new CliError(`${failed}/${passed + failed} skill(s) failed validation.`);
|
|
582
|
-
}
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
const resolved = resolve3(dir);
|
|
586
|
-
if (!existsSync5(resolved)) {
|
|
587
|
-
throw new CliError(`Directory not found: ${resolved}`);
|
|
588
|
-
}
|
|
589
|
-
const result = runValidation(resolved);
|
|
590
|
-
for (const w of result.warnings) {
|
|
591
|
-
warn(w);
|
|
592
|
-
}
|
|
593
|
-
if (result.valid) {
|
|
594
|
-
success("Skill is valid.");
|
|
595
|
-
} else {
|
|
596
|
-
for (const e of result.errors) {
|
|
597
|
-
error(e);
|
|
598
|
-
}
|
|
599
|
-
throw new CliError("Validation failed.", [
|
|
600
|
-
"Fix the errors above and try again."
|
|
601
|
-
]);
|
|
602
|
-
}
|
|
603
|
-
} catch (err) {
|
|
604
|
-
handleError(err);
|
|
605
|
-
}
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
// src/commands/pack.ts
|
|
609
|
-
import { Command as Command6 } from "commander";
|
|
610
|
-
import { resolve as resolve4 } from "path";
|
|
611
|
-
import { existsSync as existsSync6 } from "fs";
|
|
612
|
-
|
|
613
|
-
// src/lib/archive.ts
|
|
614
|
-
import { statSync as statSync2 } from "fs";
|
|
615
|
-
import { readFile } from "fs/promises";
|
|
616
|
-
import { createHash } from "crypto";
|
|
617
|
-
import { join as join6, basename as basename2 } from "path";
|
|
618
|
-
import { create as tarCreate, extract as tarExtract } from "tar";
|
|
619
|
-
var MAX_ARCHIVE_SIZE = 10 * 1024 * 1024;
|
|
620
|
-
async function computeSha256(filePath) {
|
|
621
|
-
const data = await readFile(filePath);
|
|
622
|
-
return createHash("sha256").update(data).digest("hex");
|
|
623
|
-
}
|
|
624
|
-
async function pack(dir, outputPath) {
|
|
625
|
-
const dirName = basename2(dir);
|
|
626
|
-
const archivePath = outputPath ?? join6(dir, "..", `${dirName}.tar.gz`);
|
|
627
|
-
await tarCreate(
|
|
628
|
-
{
|
|
629
|
-
gzip: true,
|
|
630
|
-
file: archivePath,
|
|
631
|
-
cwd: join6(dir, "..")
|
|
632
|
-
},
|
|
633
|
-
[dirName]
|
|
634
|
-
);
|
|
635
|
-
const sha256 = await computeSha256(archivePath);
|
|
636
|
-
const stat = statSync2(archivePath);
|
|
637
|
-
if (stat.size > MAX_ARCHIVE_SIZE) {
|
|
638
|
-
throw new CliError(
|
|
639
|
-
`Archive size ${(stat.size / 1024 / 1024).toFixed(1)}MB exceeds the 10MB limit.`,
|
|
640
|
-
["Remove unnecessary files or assets to reduce the package size."]
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
return {
|
|
644
|
-
path: archivePath,
|
|
645
|
-
sha256,
|
|
646
|
-
size: stat.size
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
async function extract(archivePath, outputDir, expectedSha256) {
|
|
650
|
-
const actualSha256 = await computeSha256(archivePath);
|
|
651
|
-
if (actualSha256 !== expectedSha256) {
|
|
652
|
-
throw new CliError(
|
|
653
|
-
`SHA-256 mismatch: expected ${expectedSha256}, got ${actualSha256}`,
|
|
654
|
-
["The archive may be corrupted or tampered with.", "Try downloading again."]
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
await tarExtract({
|
|
658
|
-
file: archivePath,
|
|
659
|
-
cwd: outputDir,
|
|
660
|
-
filter: (path) => !path.includes("..")
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
|
|
664
186
|
// src/commands/pack.ts
|
|
665
|
-
|
|
187
|
+
import { Command as Command5 } from "commander";
|
|
188
|
+
import { resolve as resolve2 } from "path";
|
|
189
|
+
import { existsSync as existsSync2 } from "fs";
|
|
190
|
+
var packCommand = new Command5("pack").description("Pack a skill into a tarball").argument("[dir]", "Skill directory", ".").action(async (dir) => {
|
|
666
191
|
try {
|
|
667
|
-
const resolved =
|
|
668
|
-
if (!
|
|
192
|
+
const resolved = resolve2(dir);
|
|
193
|
+
if (!existsSync2(resolved)) {
|
|
669
194
|
throw new CliError(`Directory not found: ${resolved}`);
|
|
670
195
|
}
|
|
671
196
|
const validation = runValidation(resolved);
|
|
@@ -688,208 +213,71 @@ var packCommand = new Command6("pack").description("Pack a skill into a tarball"
|
|
|
688
213
|
}
|
|
689
214
|
});
|
|
690
215
|
|
|
691
|
-
// src/commands/
|
|
692
|
-
import { Command as
|
|
693
|
-
import {
|
|
694
|
-
import {
|
|
695
|
-
import {
|
|
696
|
-
function
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const fullPath = join7(dir, entry.name);
|
|
702
|
-
const lstat = lstatSync2(fullPath);
|
|
703
|
-
if (lstat.isSymbolicLink()) continue;
|
|
704
|
-
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
705
|
-
if (entry.isDirectory()) {
|
|
706
|
-
files.push(...collectFileTree(fullPath, relativePath));
|
|
707
|
-
} else {
|
|
708
|
-
const stat = statSync3(fullPath);
|
|
709
|
-
const content = readFileSync3(fullPath);
|
|
710
|
-
const sha256 = createHash2("sha256").update(content).digest("hex");
|
|
711
|
-
files.push({ path: relativePath, size: stat.size, sha256 });
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
return files;
|
|
216
|
+
// src/commands/release.ts
|
|
217
|
+
import { Command as Command6 } from "commander";
|
|
218
|
+
import { select, input as input3 } from "@inquirer/prompts";
|
|
219
|
+
import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
220
|
+
import { join as join2, resolve as resolve3 } from "path";
|
|
221
|
+
function bumpPatch(version) {
|
|
222
|
+
const parts = version.replace(/^"(.*)"$/, "$1").split(".");
|
|
223
|
+
if (parts.length < 3) return version;
|
|
224
|
+
parts[2] = String(parseInt(parts[2], 10) + 1);
|
|
225
|
+
return parts.join(".");
|
|
715
226
|
}
|
|
716
|
-
|
|
717
|
-
const spin = spinner("Validating skill...");
|
|
718
|
-
const validation = runValidation(resolved);
|
|
719
|
-
if (!validation.valid) {
|
|
720
|
-
spin.fail("Validation failed.");
|
|
721
|
-
for (const e of validation.errors) {
|
|
722
|
-
console.error(" " + e);
|
|
723
|
-
}
|
|
724
|
-
throw new CliError("Fix validation errors before pushing.");
|
|
725
|
-
}
|
|
726
|
-
spin.succeed("Validation passed.");
|
|
727
|
-
const manifest = parseManifest(resolved);
|
|
728
|
-
if (!manifest.version) {
|
|
729
|
-
throw new CliError('Missing "version" in SKILL.md frontmatter.', [
|
|
730
|
-
'Add a version field: version: "1.0.0"'
|
|
731
|
-
]);
|
|
732
|
-
}
|
|
733
|
-
if (!manifest.scope) {
|
|
734
|
-
throw new CliError('Missing "scope" in SKILL.md frontmatter.', [
|
|
735
|
-
"Add a scope field: scope: my-team"
|
|
736
|
-
]);
|
|
737
|
-
}
|
|
738
|
-
spin.start("Packing skill...");
|
|
739
|
-
const archive = await pack(resolved);
|
|
740
|
-
spin.succeed(`Packed (${(archive.size / 1024).toFixed(1)}KB).`);
|
|
741
|
-
const client = new ApiClient();
|
|
742
|
-
spin.start("Checking registry...");
|
|
227
|
+
var releaseCommand = new Command6("release").description("Bump the version in SKILL.md and push to the registry").argument("[dir]", "Skill directory", ".").action(async (dir) => {
|
|
743
228
|
try {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
scope: manifest.scope,
|
|
751
|
-
slug: manifest.name,
|
|
752
|
-
displayName: manifest.name,
|
|
753
|
-
description: manifest.description || "",
|
|
754
|
-
tags: manifest.tags || []
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
spin.succeed(`Created skill ${manifest.scope}/${manifest.name}.`);
|
|
758
|
-
}
|
|
759
|
-
spin.start("Preparing version...");
|
|
760
|
-
const fileTree = collectFileTree(resolved);
|
|
761
|
-
const prepared = await client.post(
|
|
762
|
-
`/api/v1/skills/${manifest.scope}/${manifest.name}/versions`,
|
|
763
|
-
{
|
|
764
|
-
body: {
|
|
765
|
-
version: manifest.version,
|
|
766
|
-
fileTree,
|
|
767
|
-
archiveSha256: archive.sha256,
|
|
768
|
-
archiveSize: archive.size,
|
|
769
|
-
entryPoint: "SKILL.md",
|
|
770
|
-
manifestJson: {
|
|
771
|
-
name: manifest.name,
|
|
772
|
-
description: manifest.description,
|
|
773
|
-
version: manifest.version,
|
|
774
|
-
scope: manifest.scope,
|
|
775
|
-
"allowed-tools": manifest["allowed-tools"],
|
|
776
|
-
"argument-hint": manifest["argument-hint"],
|
|
777
|
-
"disable-model-invocation": manifest["disable-model-invocation"],
|
|
778
|
-
"user-invocable": manifest["user-invocable"],
|
|
779
|
-
tags: manifest.tags
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
);
|
|
784
|
-
spin.succeed("Version prepared.");
|
|
785
|
-
spin.start("Uploading archive...");
|
|
786
|
-
const archiveData = readFileSync3(archive.path);
|
|
787
|
-
const uploadHeaders = {
|
|
788
|
-
"Content-Type": "application/gzip",
|
|
789
|
-
"Content-Length": String(archive.size)
|
|
790
|
-
};
|
|
791
|
-
let uploadResponse = await fetch(prepared.uploadUrl, {
|
|
792
|
-
method: "PUT",
|
|
793
|
-
headers: uploadHeaders,
|
|
794
|
-
body: archiveData,
|
|
795
|
-
redirect: "manual"
|
|
796
|
-
});
|
|
797
|
-
if (uploadResponse.status === 301 || uploadResponse.status === 302 || uploadResponse.status === 307 || uploadResponse.status === 308) {
|
|
798
|
-
const redirectUrl = uploadResponse.headers.get("location");
|
|
799
|
-
if (!redirectUrl) {
|
|
800
|
-
spin.fail("Upload failed.");
|
|
801
|
-
throw new CliError("Upload redirect missing Location header.");
|
|
229
|
+
const resolved = resolve3(dir);
|
|
230
|
+
const manifest = parseManifest(resolved);
|
|
231
|
+
if (!manifest.version) {
|
|
232
|
+
throw new CliError('Missing "version" in SKILL.md frontmatter.', [
|
|
233
|
+
'Add a version field: version: "1.0.0"'
|
|
234
|
+
]);
|
|
802
235
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
236
|
+
const currentVersion = manifest.version;
|
|
237
|
+
const nextPatch = bumpPatch(currentVersion);
|
|
238
|
+
info(`Current version: ${currentVersion}`);
|
|
239
|
+
const chosen = await select({
|
|
240
|
+
message: "Version to release:",
|
|
241
|
+
choices: [
|
|
242
|
+
{ name: `${nextPatch} (patch bump)`, value: nextPatch },
|
|
243
|
+
{ name: `${currentVersion} (current)`, value: currentVersion },
|
|
244
|
+
{ name: "Enter manually", value: "__manual__" }
|
|
245
|
+
]
|
|
807
246
|
});
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
spin.start("Finalizing version...");
|
|
817
|
-
await client.post(
|
|
818
|
-
`/api/v1/skills/${manifest.scope}/${manifest.name}/versions/${manifest.version}/finalize`,
|
|
819
|
-
{
|
|
820
|
-
body: { status: "published" }
|
|
821
|
-
}
|
|
822
|
-
);
|
|
823
|
-
spin.succeed("Version finalized.");
|
|
824
|
-
return `${manifest.scope}/${manifest.name}@${manifest.version}`;
|
|
825
|
-
}
|
|
826
|
-
var pushCommand = new Command7("push").description("Publish a skill to the registry").argument("[dir]", "Skill directory", ".").option("--all", "Push all skills in .claude/skills/").action(async (dir, opts) => {
|
|
827
|
-
try {
|
|
828
|
-
if (!getAuthToken()) {
|
|
829
|
-
throw new CliError("Not logged in.", ["Run `csreg login` to authenticate."]);
|
|
830
|
-
}
|
|
831
|
-
if (opts.all) {
|
|
832
|
-
const skillsDir = findClaudeSkillsDir();
|
|
833
|
-
if (!skillsDir) {
|
|
834
|
-
throw new CliError("Cannot find .claude/skills/ directory.", [
|
|
835
|
-
"Run this from a project with a .claude/ directory."
|
|
836
|
-
]);
|
|
837
|
-
}
|
|
838
|
-
const skillDirs = discoverSkillDirs(skillsDir);
|
|
839
|
-
if (skillDirs.length === 0) {
|
|
840
|
-
throw new CliError(`No skills found in ${skillsDir}/`, [
|
|
841
|
-
"Skills must be directories containing a SKILL.md file."
|
|
842
|
-
]);
|
|
843
|
-
}
|
|
844
|
-
console.log(`Pushing ${skillDirs.length} skill(s) from ${skillsDir}/
|
|
845
|
-
`);
|
|
846
|
-
let published = 0;
|
|
847
|
-
let failed = 0;
|
|
848
|
-
for (const skillDir of skillDirs) {
|
|
849
|
-
const name = basename3(skillDir);
|
|
850
|
-
console.log(`
|
|
851
|
-
--- ${name} ---
|
|
852
|
-
`);
|
|
853
|
-
try {
|
|
854
|
-
const ref2 = await pushSingle(skillDir);
|
|
855
|
-
success(`Published ${ref2}`);
|
|
856
|
-
published++;
|
|
857
|
-
} catch (err) {
|
|
858
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
859
|
-
warn(`Failed to push ${name}: ${msg}`);
|
|
860
|
-
failed++;
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
console.log(`
|
|
864
|
-
${"\u2500".repeat(40)}
|
|
865
|
-
`);
|
|
866
|
-
if (failed === 0) {
|
|
867
|
-
success(`All ${published} skill(s) published.`);
|
|
868
|
-
} else {
|
|
869
|
-
success(`Published ${published}/${published + failed} skill(s).`);
|
|
870
|
-
if (failed > 0) {
|
|
871
|
-
warn(`${failed} skill(s) failed. Check the errors above.`);
|
|
247
|
+
let newVersion = chosen;
|
|
248
|
+
if (chosen === "__manual__") {
|
|
249
|
+
newVersion = await input3({
|
|
250
|
+
message: "Version:",
|
|
251
|
+
validate: (value) => {
|
|
252
|
+
const semver = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$/;
|
|
253
|
+
if (!semver.test(value.trim())) return "Must be valid semver (e.g., 1.2.3).";
|
|
254
|
+
return true;
|
|
872
255
|
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
256
|
+
});
|
|
257
|
+
newVersion = newVersion.trim();
|
|
258
|
+
}
|
|
259
|
+
if (newVersion !== currentVersion) {
|
|
260
|
+
const skillPath = join2(resolved, "SKILL.md");
|
|
261
|
+
const raw = readFileSync(skillPath, "utf-8");
|
|
262
|
+
const updated = raw.replace(
|
|
263
|
+
/^(version:\s*)"?[^"\n]+"?\s*$/m,
|
|
264
|
+
`$1"${newVersion}"`
|
|
265
|
+
);
|
|
266
|
+
writeFileSync2(skillPath, updated, "utf-8");
|
|
267
|
+
info(`Updated version to ${newVersion}`);
|
|
268
|
+
}
|
|
269
|
+
const { pushCommand: pushCommand2 } = await import("./push-7P5F2O6O.js");
|
|
270
|
+
await pushCommand2.parseAsync([dir], { from: "user" });
|
|
883
271
|
} catch (err) {
|
|
884
272
|
handleError(err);
|
|
885
273
|
}
|
|
886
274
|
});
|
|
887
275
|
|
|
888
276
|
// src/commands/pull.ts
|
|
889
|
-
import { Command as
|
|
890
|
-
import { resolve as
|
|
891
|
-
import { mkdirSync as
|
|
892
|
-
import { createHash
|
|
277
|
+
import { Command as Command7 } from "commander";
|
|
278
|
+
import { resolve as resolve4, join as join3, dirname } from "path";
|
|
279
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, existsSync as existsSync3, readFileSync as readFileSync2, renameSync } from "fs";
|
|
280
|
+
import { createHash } from "crypto";
|
|
893
281
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
894
282
|
function parseSkillRef(ref) {
|
|
895
283
|
const cleaned = ref.startsWith("@") ? ref.slice(1) : ref;
|
|
@@ -915,12 +303,12 @@ function parseSkillRef(ref) {
|
|
|
915
303
|
};
|
|
916
304
|
}
|
|
917
305
|
function readSkillsConfig() {
|
|
918
|
-
let dir =
|
|
919
|
-
while (dir !==
|
|
920
|
-
const configPath =
|
|
921
|
-
if (
|
|
306
|
+
let dir = resolve4(".");
|
|
307
|
+
while (dir !== dirname(dir)) {
|
|
308
|
+
const configPath = join3(dir, ".claude", "skills.json");
|
|
309
|
+
if (existsSync3(configPath)) {
|
|
922
310
|
try {
|
|
923
|
-
const raw =
|
|
311
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
924
312
|
return JSON.parse(raw);
|
|
925
313
|
} catch {
|
|
926
314
|
throw new CliError("Failed to parse .claude/skills.json", [
|
|
@@ -928,7 +316,7 @@ function readSkillsConfig() {
|
|
|
928
316
|
]);
|
|
929
317
|
}
|
|
930
318
|
}
|
|
931
|
-
dir =
|
|
319
|
+
dir = dirname(dir);
|
|
932
320
|
}
|
|
933
321
|
return null;
|
|
934
322
|
}
|
|
@@ -958,7 +346,7 @@ async function pullSkill(scope, name, version, targetDir) {
|
|
|
958
346
|
`Archive size ${(archiveData.length / 1024 / 1024).toFixed(1)}MB exceeds the 10MB limit.`
|
|
959
347
|
);
|
|
960
348
|
}
|
|
961
|
-
const actualSha =
|
|
349
|
+
const actualSha = createHash("sha256").update(archiveData).digest("hex");
|
|
962
350
|
if (actualSha !== downloadInfo.archiveSha256) {
|
|
963
351
|
spin.fail("Integrity check failed.");
|
|
964
352
|
throw new CliError(
|
|
@@ -967,17 +355,17 @@ async function pullSkill(scope, name, version, targetDir) {
|
|
|
967
355
|
);
|
|
968
356
|
}
|
|
969
357
|
spin.succeed("Downloaded and verified.");
|
|
970
|
-
|
|
358
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
971
359
|
spin.start("Installing...");
|
|
972
|
-
const tempDir =
|
|
973
|
-
|
|
974
|
-
const tempArchive =
|
|
360
|
+
const tempDir = join3(dirname(targetDir), `.csreg-tmp-${Date.now()}`);
|
|
361
|
+
mkdirSync2(tempDir, { recursive: true });
|
|
362
|
+
const tempArchive = join3(tempDir, `${name}.tar.gz`);
|
|
975
363
|
writeFileSync3(tempArchive, archiveData);
|
|
976
364
|
try {
|
|
977
365
|
await extract(tempArchive, tempDir, downloadInfo.archiveSha256);
|
|
978
|
-
const extractedDir =
|
|
979
|
-
const finalDir =
|
|
980
|
-
if (
|
|
366
|
+
const extractedDir = join3(tempDir, name);
|
|
367
|
+
const finalDir = join3(targetDir, name);
|
|
368
|
+
if (existsSync3(finalDir)) {
|
|
981
369
|
const { rmSync } = await import("fs");
|
|
982
370
|
rmSync(finalDir, { recursive: true });
|
|
983
371
|
}
|
|
@@ -992,7 +380,7 @@ async function pullSkill(scope, name, version, targetDir) {
|
|
|
992
380
|
}
|
|
993
381
|
}
|
|
994
382
|
}
|
|
995
|
-
var pullCommand = new
|
|
383
|
+
var pullCommand = new Command7("pull").description("Download and install a skill").argument("[ref]", "Skill reference (@scope/name[@version])").option("--all", "Pull all skills listed in .claude/skills.json").option("--path <dir>", "Custom install directory (overrides auto-detection)").action(async (ref, opts) => {
|
|
996
384
|
try {
|
|
997
385
|
if (opts.all) {
|
|
998
386
|
const config = readSkillsConfig();
|
|
@@ -1006,7 +394,7 @@ var pullCommand = new Command8("pull").description("Download and install a skill
|
|
|
1006
394
|
info("No skills listed in .claude/skills.json. Nothing to pull.");
|
|
1007
395
|
return;
|
|
1008
396
|
}
|
|
1009
|
-
const skillsDir = opts.path ?
|
|
397
|
+
const skillsDir = opts.path ? resolve4(opts.path) : findClaudeSkillsDir();
|
|
1010
398
|
if (!skillsDir) {
|
|
1011
399
|
throw new CliError("Cannot find .claude/ directory.", [
|
|
1012
400
|
"Run this from a project with a .claude/ directory, or use --path."
|
|
@@ -1040,54 +428,54 @@ var pullCommand = new Command8("pull").description("Download and install a skill
|
|
|
1040
428
|
const { scope, name, version } = parseSkillRef(ref);
|
|
1041
429
|
let targetDir;
|
|
1042
430
|
if (opts.path) {
|
|
1043
|
-
targetDir =
|
|
431
|
+
targetDir = resolve4(opts.path);
|
|
1044
432
|
} else {
|
|
1045
433
|
const detected = findClaudeSkillsDir();
|
|
1046
434
|
if (detected) {
|
|
1047
435
|
targetDir = detected;
|
|
1048
|
-
const finalPath =
|
|
436
|
+
const finalPath = join3(detected, name);
|
|
1049
437
|
const ok = await confirm2({
|
|
1050
438
|
message: `Install to ${finalPath}?`,
|
|
1051
439
|
default: true
|
|
1052
440
|
});
|
|
1053
441
|
if (!ok) {
|
|
1054
|
-
const { input:
|
|
1055
|
-
const custom = await
|
|
442
|
+
const { input: input4 } = await import("@inquirer/prompts");
|
|
443
|
+
const custom = await input4({
|
|
1056
444
|
message: "Custom install path:",
|
|
1057
|
-
default:
|
|
445
|
+
default: join3(".", ".claude", "skills")
|
|
1058
446
|
});
|
|
1059
|
-
targetDir =
|
|
447
|
+
targetDir = resolve4(custom);
|
|
1060
448
|
}
|
|
1061
449
|
} else {
|
|
1062
|
-
targetDir =
|
|
450
|
+
targetDir = join3(resolve4("."), ".claude", "skills");
|
|
1063
451
|
info(`No .claude/ directory found. Will create ${targetDir}/`);
|
|
1064
452
|
const ok = await confirm2({
|
|
1065
|
-
message: `Install to ${
|
|
453
|
+
message: `Install to ${join3(targetDir, name)}?`,
|
|
1066
454
|
default: true
|
|
1067
455
|
});
|
|
1068
456
|
if (!ok) {
|
|
1069
|
-
const { input:
|
|
1070
|
-
const custom = await
|
|
457
|
+
const { input: input4 } = await import("@inquirer/prompts");
|
|
458
|
+
const custom = await input4({
|
|
1071
459
|
message: "Custom install path:",
|
|
1072
460
|
default: targetDir
|
|
1073
461
|
});
|
|
1074
|
-
targetDir =
|
|
462
|
+
targetDir = resolve4(custom);
|
|
1075
463
|
}
|
|
1076
464
|
}
|
|
1077
465
|
}
|
|
1078
466
|
const result = await pullSkill(scope, name, version, targetDir);
|
|
1079
467
|
console.log("");
|
|
1080
468
|
success(`Pulled ${scope}/${name}@${result.version}`);
|
|
1081
|
-
info(`Skill is ready at ${
|
|
469
|
+
info(`Skill is ready at ${join3(targetDir, name)}/SKILL.md`);
|
|
1082
470
|
} catch (err) {
|
|
1083
471
|
handleError(err);
|
|
1084
472
|
}
|
|
1085
473
|
});
|
|
1086
474
|
|
|
1087
475
|
// src/commands/info.ts
|
|
1088
|
-
import { Command as
|
|
1089
|
-
import
|
|
1090
|
-
var infoCommand = new
|
|
476
|
+
import { Command as Command8 } from "commander";
|
|
477
|
+
import chalk from "chalk";
|
|
478
|
+
var infoCommand = new Command8("info").description("Display details about a skill").argument("<ref>", "Skill reference (scope/name)").action(async (ref) => {
|
|
1091
479
|
try {
|
|
1092
480
|
const cleaned = ref.startsWith("@") ? ref.slice(1) : ref;
|
|
1093
481
|
const slashIndex = cleaned.indexOf("/");
|
|
@@ -1101,7 +489,7 @@ var infoCommand = new Command9("info").description("Display details about a skil
|
|
|
1101
489
|
const client = new ApiClient();
|
|
1102
490
|
const skill = await client.get(`/api/v1/skills/${scope}/${name}`);
|
|
1103
491
|
console.log("");
|
|
1104
|
-
console.log(
|
|
492
|
+
console.log(chalk.bold(`${skill.scope}/${skill.name}`) + chalk.dim(` @ ${skill.latestVersion}`));
|
|
1105
493
|
console.log("");
|
|
1106
494
|
if (skill.description) {
|
|
1107
495
|
console.log(skill.description);
|
|
@@ -1122,7 +510,7 @@ var infoCommand = new Command9("info").description("Display details about a skil
|
|
|
1122
510
|
}
|
|
1123
511
|
const maxLabel = Math.max(...fields.map(([label]) => label.length));
|
|
1124
512
|
for (const [label, value] of fields) {
|
|
1125
|
-
console.log(` ${
|
|
513
|
+
console.log(` ${chalk.cyan(label.padEnd(maxLabel + 2))}${value}`);
|
|
1126
514
|
}
|
|
1127
515
|
console.log("");
|
|
1128
516
|
} catch (err) {
|
|
@@ -1131,8 +519,8 @@ var infoCommand = new Command9("info").description("Display details about a skil
|
|
|
1131
519
|
});
|
|
1132
520
|
|
|
1133
521
|
// src/commands/versions.ts
|
|
1134
|
-
import { Command as
|
|
1135
|
-
var versionsCommand = new
|
|
522
|
+
import { Command as Command9 } from "commander";
|
|
523
|
+
var versionsCommand = new Command9("versions").description("List all versions of a skill").argument("<ref>", "Skill reference (scope/name)").action(async (ref) => {
|
|
1136
524
|
try {
|
|
1137
525
|
const cleaned = ref.startsWith("@") ? ref.slice(1) : ref;
|
|
1138
526
|
const slashIndex = cleaned.indexOf("/");
|
|
@@ -1166,8 +554,8 @@ var versionsCommand = new Command10("versions").description("List all versions o
|
|
|
1166
554
|
});
|
|
1167
555
|
|
|
1168
556
|
// src/commands/search.ts
|
|
1169
|
-
import { Command as
|
|
1170
|
-
var searchCommand = new
|
|
557
|
+
import { Command as Command10 } from "commander";
|
|
558
|
+
var searchCommand = new Command10("search").description("Search for skills in the registry").argument("<query>", "Search query").option("-t, --type <type>", "Filter by skill type").option("-l, --limit <limit>", "Max results", "20").action(async (query, opts) => {
|
|
1171
559
|
try {
|
|
1172
560
|
const client = new ApiClient();
|
|
1173
561
|
const queryParams = {
|
|
@@ -1200,7 +588,7 @@ var searchCommand = new Command11("search").description("Search for skills in th
|
|
|
1200
588
|
});
|
|
1201
589
|
|
|
1202
590
|
// src/index.ts
|
|
1203
|
-
var program = new
|
|
591
|
+
var program = new Command11();
|
|
1204
592
|
program.name("csreg").description("Claude Skills Registry CLI").version("0.1.0");
|
|
1205
593
|
program.addCommand(loginCommand);
|
|
1206
594
|
program.addCommand(logoutCommand);
|
|
@@ -1209,6 +597,7 @@ program.addCommand(initCommand);
|
|
|
1209
597
|
program.addCommand(validateCommand);
|
|
1210
598
|
program.addCommand(packCommand);
|
|
1211
599
|
program.addCommand(pushCommand);
|
|
600
|
+
program.addCommand(releaseCommand);
|
|
1212
601
|
program.addCommand(pullCommand);
|
|
1213
602
|
program.addCommand(infoCommand);
|
|
1214
603
|
program.addCommand(versionsCommand);
|