@annals/agent-mesh 0.16.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +385 -109
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2562,7 +2562,7 @@ var ERROR_HINTS = {
|
|
|
2562
2562
|
agent_offline: "Agent must be online for first publish. Run `agent-mesh connect` first.",
|
|
2563
2563
|
email_required: "Email required. Visit https://agents.hot/settings to add one.",
|
|
2564
2564
|
github_required: "GitHub account required. Visit https://agents.hot/settings to link one.",
|
|
2565
|
-
validation_error: "Invalid input. Check your
|
|
2565
|
+
validation_error: "Invalid input. Check your SKILL.md frontmatter or command flags.",
|
|
2566
2566
|
permission_denied: "You don't have permission to modify this skill.",
|
|
2567
2567
|
file_too_large: "Package file exceeds the 50MB limit.",
|
|
2568
2568
|
subscription_required: "This is a private agent. Subscribe first: agent-mesh subscribe <author-login>"
|
|
@@ -2593,6 +2593,31 @@ var PlatformClient = class {
|
|
|
2593
2593
|
async del(path, body) {
|
|
2594
2594
|
return this.request("DELETE", path, body);
|
|
2595
2595
|
}
|
|
2596
|
+
async getRaw(path) {
|
|
2597
|
+
const url = `${this.baseUrl}${path}`;
|
|
2598
|
+
let res;
|
|
2599
|
+
try {
|
|
2600
|
+
res = await fetch(url, {
|
|
2601
|
+
method: "GET",
|
|
2602
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
2603
|
+
});
|
|
2604
|
+
} catch (err) {
|
|
2605
|
+
throw new PlatformApiError(0, "network_error", `Network error: ${err.message}`);
|
|
2606
|
+
}
|
|
2607
|
+
if (!res.ok) {
|
|
2608
|
+
let errorCode = "unknown";
|
|
2609
|
+
let message = `HTTP ${res.status}`;
|
|
2610
|
+
try {
|
|
2611
|
+
const data = await res.json();
|
|
2612
|
+
errorCode = data.error ?? errorCode;
|
|
2613
|
+
message = data.error_description ?? data.message ?? message;
|
|
2614
|
+
} catch {
|
|
2615
|
+
}
|
|
2616
|
+
const hint = ERROR_HINTS[errorCode];
|
|
2617
|
+
throw new PlatformApiError(res.status, errorCode, hint ?? message);
|
|
2618
|
+
}
|
|
2619
|
+
return res;
|
|
2620
|
+
}
|
|
2596
2621
|
async postFormData(path, formData) {
|
|
2597
2622
|
const url = `${this.baseUrl}${path}`;
|
|
2598
2623
|
let res;
|
|
@@ -3127,11 +3152,11 @@ function registerChatCommand(program2) {
|
|
|
3127
3152
|
}
|
|
3128
3153
|
|
|
3129
3154
|
// src/commands/skills.ts
|
|
3130
|
-
import { readFile as readFile4, writeFile as
|
|
3155
|
+
import { readFile as readFile4, writeFile as writeFile3, readdir as readdir2, mkdir as mkdir2, rm } from "fs/promises";
|
|
3131
3156
|
import { join as join8, resolve, relative as relative4 } from "path";
|
|
3132
3157
|
|
|
3133
3158
|
// src/utils/skill-parser.ts
|
|
3134
|
-
import { readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
3159
|
+
import { readFile as readFile3, writeFile as writeFile2, stat as stat2 } from "fs/promises";
|
|
3135
3160
|
import { join as join7 } from "path";
|
|
3136
3161
|
function parseSkillMd(raw) {
|
|
3137
3162
|
const trimmed = raw.trimStart();
|
|
@@ -3192,36 +3217,13 @@ function parseSkillMd(raw) {
|
|
|
3192
3217
|
return { frontmatter, content };
|
|
3193
3218
|
}
|
|
3194
3219
|
async function loadSkillManifest(dir) {
|
|
3195
|
-
const skillJsonPath = join7(dir, "skill.json");
|
|
3196
|
-
try {
|
|
3197
|
-
const raw = await readFile3(skillJsonPath, "utf-8");
|
|
3198
|
-
const data = JSON.parse(raw);
|
|
3199
|
-
if (!data.name) throw new Error("skill.json missing required field: name");
|
|
3200
|
-
if (!data.version) throw new Error("skill.json missing required field: version");
|
|
3201
|
-
return {
|
|
3202
|
-
name: data.name,
|
|
3203
|
-
version: data.version,
|
|
3204
|
-
description: data.description,
|
|
3205
|
-
main: data.main || "SKILL.md",
|
|
3206
|
-
category: data.category,
|
|
3207
|
-
tags: data.tags,
|
|
3208
|
-
author: data.author,
|
|
3209
|
-
source_url: data.source_url,
|
|
3210
|
-
private: data.private,
|
|
3211
|
-
files: data.files
|
|
3212
|
-
};
|
|
3213
|
-
} catch (err) {
|
|
3214
|
-
if (err.code !== "ENOENT") {
|
|
3215
|
-
throw err;
|
|
3216
|
-
}
|
|
3217
|
-
}
|
|
3218
3220
|
const skillMdPath = join7(dir, "SKILL.md");
|
|
3219
3221
|
try {
|
|
3220
3222
|
const raw = await readFile3(skillMdPath, "utf-8");
|
|
3221
3223
|
const { frontmatter } = parseSkillMd(raw);
|
|
3222
3224
|
const name = frontmatter.name;
|
|
3223
3225
|
if (!name) {
|
|
3224
|
-
throw new Error('
|
|
3226
|
+
throw new Error('SKILL.md has no "name" in frontmatter');
|
|
3225
3227
|
}
|
|
3226
3228
|
return {
|
|
3227
3229
|
name,
|
|
@@ -3236,7 +3238,7 @@ async function loadSkillManifest(dir) {
|
|
|
3236
3238
|
};
|
|
3237
3239
|
} catch (err) {
|
|
3238
3240
|
if (err.code === "ENOENT") {
|
|
3239
|
-
throw new Error(`No
|
|
3241
|
+
throw new Error(`No SKILL.md found in ${dir}`);
|
|
3240
3242
|
}
|
|
3241
3243
|
throw err;
|
|
3242
3244
|
}
|
|
@@ -3249,9 +3251,33 @@ async function pathExists(p) {
|
|
|
3249
3251
|
return false;
|
|
3250
3252
|
}
|
|
3251
3253
|
}
|
|
3254
|
+
async function updateFrontmatterField(filePath, field, value) {
|
|
3255
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
3256
|
+
const trimmed = raw.trimStart();
|
|
3257
|
+
if (!trimmed.startsWith("---")) {
|
|
3258
|
+
throw new Error("SKILL.md has no frontmatter block");
|
|
3259
|
+
}
|
|
3260
|
+
const endIdx = trimmed.indexOf("\n---", 3);
|
|
3261
|
+
if (endIdx === -1) {
|
|
3262
|
+
throw new Error("SKILL.md has no frontmatter block");
|
|
3263
|
+
}
|
|
3264
|
+
const yamlBlock = trimmed.slice(4, endIdx);
|
|
3265
|
+
const after = trimmed.slice(endIdx);
|
|
3266
|
+
const fieldRegex = new RegExp(`^(${field}\\s*:\\s*)(.*)$`, "m");
|
|
3267
|
+
if (fieldRegex.test(yamlBlock)) {
|
|
3268
|
+
const updated = yamlBlock.replace(fieldRegex, `$1${value}`);
|
|
3269
|
+
await writeFile2(filePath, `---
|
|
3270
|
+
${updated}${after}`);
|
|
3271
|
+
} else {
|
|
3272
|
+
const updated = `${yamlBlock}
|
|
3273
|
+
${field}: ${value}`;
|
|
3274
|
+
await writeFile2(filePath, `---
|
|
3275
|
+
${updated}${after}`);
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3252
3278
|
|
|
3253
3279
|
// src/utils/zip.ts
|
|
3254
|
-
import { deflateRawSync } from "zlib";
|
|
3280
|
+
import { deflateRawSync, inflateRawSync } from "zlib";
|
|
3255
3281
|
function dosTime(date) {
|
|
3256
3282
|
return {
|
|
3257
3283
|
time: date.getHours() << 11 | date.getMinutes() << 5 | date.getSeconds() >> 1,
|
|
@@ -3347,6 +3373,55 @@ function createZipBuffer(entries) {
|
|
|
3347
3373
|
chunks.push(eocd);
|
|
3348
3374
|
return Buffer.concat(chunks);
|
|
3349
3375
|
}
|
|
3376
|
+
function extractZipBuffer(buf) {
|
|
3377
|
+
const entries = [];
|
|
3378
|
+
let eocdOffset = -1;
|
|
3379
|
+
for (let i = buf.length - 22; i >= 0; i--) {
|
|
3380
|
+
if (buf.readUInt32LE(i) === 101010256) {
|
|
3381
|
+
eocdOffset = i;
|
|
3382
|
+
break;
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
if (eocdOffset === -1) {
|
|
3386
|
+
throw new Error("Invalid ZIP: EOCD not found");
|
|
3387
|
+
}
|
|
3388
|
+
const entryCount = buf.readUInt16LE(eocdOffset + 10);
|
|
3389
|
+
const centralDirOffset = buf.readUInt32LE(eocdOffset + 16);
|
|
3390
|
+
let offset = centralDirOffset;
|
|
3391
|
+
for (let i = 0; i < entryCount; i++) {
|
|
3392
|
+
if (buf.readUInt32LE(offset) !== 33639248) {
|
|
3393
|
+
throw new Error(`Invalid ZIP: bad central directory signature at ${offset}`);
|
|
3394
|
+
}
|
|
3395
|
+
const compressionMethod = buf.readUInt16LE(offset + 10);
|
|
3396
|
+
const compressedSize = buf.readUInt32LE(offset + 20);
|
|
3397
|
+
const uncompressedSize = buf.readUInt32LE(offset + 24);
|
|
3398
|
+
const nameLen = buf.readUInt16LE(offset + 28);
|
|
3399
|
+
const extraLen = buf.readUInt16LE(offset + 30);
|
|
3400
|
+
const commentLen = buf.readUInt16LE(offset + 32);
|
|
3401
|
+
const localHeaderOffset = buf.readUInt32LE(offset + 42);
|
|
3402
|
+
const name = buf.subarray(offset + 46, offset + 46 + nameLen).toString("utf-8");
|
|
3403
|
+
if (!name.endsWith("/")) {
|
|
3404
|
+
const localNameLen = buf.readUInt16LE(localHeaderOffset + 26);
|
|
3405
|
+
const localExtraLen = buf.readUInt16LE(localHeaderOffset + 28);
|
|
3406
|
+
const dataOffset = localHeaderOffset + 30 + localNameLen + localExtraLen;
|
|
3407
|
+
const compressedData = buf.subarray(dataOffset, dataOffset + compressedSize);
|
|
3408
|
+
let data;
|
|
3409
|
+
if (compressionMethod === 0) {
|
|
3410
|
+
data = Buffer.from(compressedData);
|
|
3411
|
+
} else if (compressionMethod === 8) {
|
|
3412
|
+
data = inflateRawSync(compressedData);
|
|
3413
|
+
} else {
|
|
3414
|
+
throw new Error(`Unsupported compression method: ${compressionMethod}`);
|
|
3415
|
+
}
|
|
3416
|
+
if (data.length !== uncompressedSize) {
|
|
3417
|
+
throw new Error(`Size mismatch for ${name}: expected ${uncompressedSize}, got ${data.length}`);
|
|
3418
|
+
}
|
|
3419
|
+
entries.push({ path: name, data });
|
|
3420
|
+
}
|
|
3421
|
+
offset += 46 + nameLen + extraLen + commentLen;
|
|
3422
|
+
}
|
|
3423
|
+
return entries;
|
|
3424
|
+
}
|
|
3350
3425
|
|
|
3351
3426
|
// src/commands/skills.ts
|
|
3352
3427
|
var slog = {
|
|
@@ -3379,31 +3454,34 @@ function outputError(error, message, hint) {
|
|
|
3379
3454
|
function resolveSkillDir(pathArg) {
|
|
3380
3455
|
return pathArg ? resolve(pathArg) : process.cwd();
|
|
3381
3456
|
}
|
|
3457
|
+
function parseSkillRef(ref) {
|
|
3458
|
+
if (!ref.includes("/")) {
|
|
3459
|
+
outputError("validation_error", `Invalid skill reference: "${ref}". Use author/slug format (e.g. kcsx/code-review)`);
|
|
3460
|
+
}
|
|
3461
|
+
const [authorLogin, slug] = ref.split("/", 2);
|
|
3462
|
+
if (!authorLogin || !slug) {
|
|
3463
|
+
outputError("validation_error", `Invalid skill reference: "${ref}". Use author/slug format (e.g. kcsx/code-review)`);
|
|
3464
|
+
}
|
|
3465
|
+
return { authorLogin, slug };
|
|
3466
|
+
}
|
|
3467
|
+
function skillApiPath(authorLogin, slug) {
|
|
3468
|
+
return `/api/skills/${encodeURIComponent(authorLogin)}/${encodeURIComponent(slug)}`;
|
|
3469
|
+
}
|
|
3470
|
+
async function resolveSkillsRootAsync(pathArg) {
|
|
3471
|
+
const projectRoot = pathArg ? resolve(pathArg) : process.cwd();
|
|
3472
|
+
const agentsDir = join8(projectRoot, ".agents", "skills");
|
|
3473
|
+
if (await pathExists(agentsDir)) {
|
|
3474
|
+
return { projectRoot, skillsDir: agentsDir, convention: "agents" };
|
|
3475
|
+
}
|
|
3476
|
+
const claudeDir = join8(projectRoot, ".claude", "skills");
|
|
3477
|
+
return { projectRoot, skillsDir: claudeDir, convention: "claude" };
|
|
3478
|
+
}
|
|
3382
3479
|
async function collectPackFiles(dir, manifest) {
|
|
3383
3480
|
const results = [];
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
const s = await stat3(fullPath);
|
|
3389
|
-
if (s.isDirectory()) {
|
|
3390
|
-
const sub = await walkDir(fullPath);
|
|
3391
|
-
for (const f of sub) {
|
|
3392
|
-
results.push(relative4(dir, f));
|
|
3393
|
-
}
|
|
3394
|
-
} else {
|
|
3395
|
-
results.push(pattern);
|
|
3396
|
-
}
|
|
3397
|
-
} catch {
|
|
3398
|
-
}
|
|
3399
|
-
}
|
|
3400
|
-
} else {
|
|
3401
|
-
const all = await walkDir(dir);
|
|
3402
|
-
for (const f of all) {
|
|
3403
|
-
const rel = relative4(dir, f);
|
|
3404
|
-
if (rel === "skill.json") continue;
|
|
3405
|
-
results.push(rel);
|
|
3406
|
-
}
|
|
3481
|
+
const all = await walkDir(dir);
|
|
3482
|
+
for (const f of all) {
|
|
3483
|
+
const rel = relative4(dir, f);
|
|
3484
|
+
results.push(rel);
|
|
3407
3485
|
}
|
|
3408
3486
|
const mainFile = manifest.main || "SKILL.md";
|
|
3409
3487
|
if (!results.includes(mainFile)) {
|
|
@@ -3474,8 +3552,42 @@ function bumpVersion(current, bump) {
|
|
|
3474
3552
|
throw new Error(`Invalid bump type: ${bump}. Use major, minor, patch, or a version string.`);
|
|
3475
3553
|
}
|
|
3476
3554
|
}
|
|
3555
|
+
async function downloadAndInstallSkill(client, authorLogin, slug, skillsDir) {
|
|
3556
|
+
const meta = await client.get(skillApiPath(authorLogin, slug));
|
|
3557
|
+
const targetDir = join8(skillsDir, slug);
|
|
3558
|
+
await mkdir2(targetDir, { recursive: true });
|
|
3559
|
+
if (meta.has_files) {
|
|
3560
|
+
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/download`);
|
|
3561
|
+
const arrayBuf = await res.arrayBuffer();
|
|
3562
|
+
const buf = Buffer.from(arrayBuf);
|
|
3563
|
+
const entries = extractZipBuffer(buf);
|
|
3564
|
+
for (const entry of entries) {
|
|
3565
|
+
const filePath = join8(targetDir, entry.path);
|
|
3566
|
+
const dir = join8(filePath, "..");
|
|
3567
|
+
await mkdir2(dir, { recursive: true });
|
|
3568
|
+
await writeFile3(filePath, entry.data);
|
|
3569
|
+
}
|
|
3570
|
+
return {
|
|
3571
|
+
slug,
|
|
3572
|
+
name: meta.name,
|
|
3573
|
+
version: meta.version || "1.0.0",
|
|
3574
|
+
files_count: entries.length
|
|
3575
|
+
};
|
|
3576
|
+
} else {
|
|
3577
|
+
const res = await client.getRaw(`${skillApiPath(authorLogin, slug)}/raw`);
|
|
3578
|
+
const content = await res.text();
|
|
3579
|
+
await writeFile3(join8(targetDir, "SKILL.md"), content);
|
|
3580
|
+
return {
|
|
3581
|
+
slug,
|
|
3582
|
+
name: meta.name,
|
|
3583
|
+
version: meta.version || "1.0.0",
|
|
3584
|
+
files_count: 1
|
|
3585
|
+
};
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3477
3588
|
var SKILL_MD_TEMPLATE = `---
|
|
3478
3589
|
name: {{name}}
|
|
3590
|
+
description: "{{description}}"
|
|
3479
3591
|
version: 1.0.0
|
|
3480
3592
|
---
|
|
3481
3593
|
|
|
@@ -3487,62 +3599,31 @@ version: 1.0.0
|
|
|
3487
3599
|
|
|
3488
3600
|
Describe how to use this skill.
|
|
3489
3601
|
`;
|
|
3490
|
-
var SKILL_JSON_TEMPLATE = (name, description) => ({
|
|
3491
|
-
name,
|
|
3492
|
-
version: "1.0.0",
|
|
3493
|
-
description,
|
|
3494
|
-
main: "SKILL.md",
|
|
3495
|
-
category: "general",
|
|
3496
|
-
tags: [],
|
|
3497
|
-
files: ["SKILL.md"]
|
|
3498
|
-
});
|
|
3499
3602
|
function registerSkillsCommand(program2) {
|
|
3500
|
-
const skills = program2.command("skills").description("Manage skill packages (publish, pack, version)");
|
|
3603
|
+
const skills = program2.command("skills").description("Manage skill packages (publish, install, pack, version)");
|
|
3501
3604
|
skills.command("init [path]").description("Initialize a new skill project").option("--name <name>", "Skill name").option("--description <desc>", "Skill description").action(async (pathArg, opts) => {
|
|
3502
3605
|
try {
|
|
3503
3606
|
const dir = resolveSkillDir(pathArg);
|
|
3504
3607
|
await mkdir2(dir, { recursive: true });
|
|
3505
|
-
let name = opts.name;
|
|
3506
|
-
let description = opts.description || "";
|
|
3507
3608
|
const skillMdPath = join8(dir, "SKILL.md");
|
|
3508
|
-
const skillJsonPath = join8(dir, "skill.json");
|
|
3509
|
-
if (await pathExists(skillJsonPath)) {
|
|
3510
|
-
outputError("already_exists", "skill.json already exists in this directory");
|
|
3511
|
-
}
|
|
3512
3609
|
if (await pathExists(skillMdPath)) {
|
|
3513
3610
|
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3514
3611
|
const { frontmatter } = parseSkillMd(raw);
|
|
3515
3612
|
if (frontmatter.name) {
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
const manifest2 = {
|
|
3519
|
-
name,
|
|
3520
|
-
version: frontmatter.version || "1.0.0",
|
|
3521
|
-
description,
|
|
3522
|
-
main: "SKILL.md",
|
|
3523
|
-
category: frontmatter.category || "general",
|
|
3524
|
-
tags: frontmatter.tags || [],
|
|
3525
|
-
author: frontmatter.author,
|
|
3526
|
-
source_url: frontmatter.source_url,
|
|
3527
|
-
files: ["SKILL.md"]
|
|
3528
|
-
};
|
|
3529
|
-
await writeFile2(skillJsonPath, JSON.stringify(manifest2, null, 2) + "\n");
|
|
3530
|
-
slog.info(`Migrated frontmatter from SKILL.md to skill.json`);
|
|
3531
|
-
outputJson({ success: true, path: skillJsonPath, migrated: true });
|
|
3613
|
+
slog.info(`SKILL.md already exists with name: ${frontmatter.name}`);
|
|
3614
|
+
outputJson({ success: true, exists: true, path: skillMdPath });
|
|
3532
3615
|
return;
|
|
3533
3616
|
}
|
|
3534
3617
|
}
|
|
3618
|
+
let name = opts.name;
|
|
3619
|
+
const description = opts.description || "";
|
|
3535
3620
|
if (!name) {
|
|
3536
3621
|
name = dir.split("/").pop()?.replace(/[^a-z0-9-]/gi, "-").toLowerCase() || "my-skill";
|
|
3537
3622
|
}
|
|
3538
|
-
const
|
|
3539
|
-
await
|
|
3540
|
-
if (!await pathExists(skillMdPath)) {
|
|
3541
|
-
const content = SKILL_MD_TEMPLATE.replace(/\{\{name\}\}/g, name).replace(/\{\{description\}\}/g, description || "A new skill.");
|
|
3542
|
-
await writeFile2(skillMdPath, content);
|
|
3543
|
-
}
|
|
3623
|
+
const content = SKILL_MD_TEMPLATE.replace(/\{\{name\}\}/g, name).replace(/\{\{description\}\}/g, description || "A new skill.");
|
|
3624
|
+
await writeFile3(skillMdPath, content);
|
|
3544
3625
|
slog.info(`Initialized skill: ${name}`);
|
|
3545
|
-
outputJson({ success: true, path:
|
|
3626
|
+
outputJson({ success: true, path: skillMdPath });
|
|
3546
3627
|
} catch (err) {
|
|
3547
3628
|
if (err instanceof Error && err.message.includes("already_exists")) throw err;
|
|
3548
3629
|
outputError("init_failed", err.message);
|
|
@@ -3554,7 +3635,7 @@ function registerSkillsCommand(program2) {
|
|
|
3554
3635
|
const manifest = await loadSkillManifest(dir);
|
|
3555
3636
|
const result = await packSkill(dir, manifest);
|
|
3556
3637
|
const outPath = join8(dir, result.filename);
|
|
3557
|
-
await
|
|
3638
|
+
await writeFile3(outPath, result.buffer);
|
|
3558
3639
|
slog.info(`Packed ${result.files.length} files \u2192 ${result.filename} (${result.size} bytes)`);
|
|
3559
3640
|
outputJson({
|
|
3560
3641
|
success: true,
|
|
@@ -3624,11 +3705,13 @@ function registerSkillsCommand(program2) {
|
|
|
3624
3705
|
const client = createClient();
|
|
3625
3706
|
const result = await client.postFormData("/api/skills/publish", formData);
|
|
3626
3707
|
slog.success(`Skill ${result.action}: ${manifest.name}`);
|
|
3708
|
+
const authorLogin = result.skill.author_login;
|
|
3709
|
+
const skillUrl = authorLogin ? `https://agents.hot/skills/${authorLogin}/${result.skill.slug}` : `https://agents.hot/skills/${result.skill.slug}`;
|
|
3627
3710
|
outputJson({
|
|
3628
3711
|
success: true,
|
|
3629
3712
|
action: result.action,
|
|
3630
3713
|
skill: result.skill,
|
|
3631
|
-
url:
|
|
3714
|
+
url: skillUrl
|
|
3632
3715
|
});
|
|
3633
3716
|
} catch (err) {
|
|
3634
3717
|
if (err instanceof PlatformApiError) {
|
|
@@ -3637,16 +3720,17 @@ function registerSkillsCommand(program2) {
|
|
|
3637
3720
|
outputError("publish_failed", err.message);
|
|
3638
3721
|
}
|
|
3639
3722
|
});
|
|
3640
|
-
skills.command("info <
|
|
3723
|
+
skills.command("info <ref>").description("View skill details (use author/slug format)").option("--human", "Human-readable output").action(async (ref, opts) => {
|
|
3641
3724
|
try {
|
|
3725
|
+
const { authorLogin, slug } = parseSkillRef(ref);
|
|
3642
3726
|
const client = createClient();
|
|
3643
|
-
const data = await client.get(
|
|
3727
|
+
const data = await client.get(skillApiPath(authorLogin, slug));
|
|
3644
3728
|
if (opts.human) {
|
|
3645
3729
|
console.log("");
|
|
3646
3730
|
console.log(` ${BOLD}${data.name}${RESET} v${data.version || "?"}`);
|
|
3647
3731
|
if (data.description) console.log(` ${data.description}`);
|
|
3648
|
-
console.log(` ${GRAY}
|
|
3649
|
-
console.log(` ${GRAY}author${RESET} ${data.author || "\u2014"}`);
|
|
3732
|
+
console.log(` ${GRAY}ref${RESET} ${authorLogin}/${data.slug}`);
|
|
3733
|
+
console.log(` ${GRAY}author${RESET} ${data.author_login || data.author || "\u2014"}`);
|
|
3650
3734
|
console.log(` ${GRAY}category${RESET} ${data.category || "\u2014"}`);
|
|
3651
3735
|
console.log(` ${GRAY}installs${RESET} ${data.installs ?? 0}`);
|
|
3652
3736
|
console.log(` ${GRAY}private${RESET} ${data.is_private ? "yes" : "no"}`);
|
|
@@ -3675,12 +3759,14 @@ function registerSkillsCommand(program2) {
|
|
|
3675
3759
|
const table = renderTable(
|
|
3676
3760
|
[
|
|
3677
3761
|
{ key: "name", label: "NAME", width: 24 },
|
|
3762
|
+
{ key: "author", label: "AUTHOR", width: 16 },
|
|
3678
3763
|
{ key: "version", label: "VERSION", width: 12 },
|
|
3679
3764
|
{ key: "installs", label: "INSTALLS", width: 12, align: "right" },
|
|
3680
3765
|
{ key: "private", label: "PRIVATE", width: 10 }
|
|
3681
3766
|
],
|
|
3682
3767
|
data.owned.map((s) => ({
|
|
3683
3768
|
name: s.name,
|
|
3769
|
+
author: s.author_login || s.author || "\u2014",
|
|
3684
3770
|
version: s.version || "\u2014",
|
|
3685
3771
|
installs: String(s.installs ?? 0),
|
|
3686
3772
|
private: s.is_private ? "yes" : `${GREEN}no${RESET}`
|
|
@@ -3698,7 +3784,7 @@ function registerSkillsCommand(program2) {
|
|
|
3698
3784
|
],
|
|
3699
3785
|
data.authorized.map((s) => ({
|
|
3700
3786
|
name: s.name,
|
|
3701
|
-
author: s.author || "\u2014",
|
|
3787
|
+
author: s.author_login || s.author || "\u2014",
|
|
3702
3788
|
version: s.version || "\u2014"
|
|
3703
3789
|
}))
|
|
3704
3790
|
);
|
|
@@ -3714,11 +3800,12 @@ function registerSkillsCommand(program2) {
|
|
|
3714
3800
|
outputError("list_failed", err.message);
|
|
3715
3801
|
}
|
|
3716
3802
|
});
|
|
3717
|
-
skills.command("unpublish <
|
|
3803
|
+
skills.command("unpublish <ref>").description("Unpublish a skill (use author/slug format)").action(async (ref) => {
|
|
3718
3804
|
try {
|
|
3805
|
+
const { authorLogin, slug } = parseSkillRef(ref);
|
|
3719
3806
|
const client = createClient();
|
|
3720
|
-
const result = await client.del(
|
|
3721
|
-
slog.success(`Skill unpublished: ${slug}`);
|
|
3807
|
+
const result = await client.del(skillApiPath(authorLogin, slug));
|
|
3808
|
+
slog.success(`Skill unpublished: ${authorLogin}/${slug}`);
|
|
3722
3809
|
outputJson(result);
|
|
3723
3810
|
} catch (err) {
|
|
3724
3811
|
if (err instanceof PlatformApiError) {
|
|
@@ -3730,16 +3817,15 @@ function registerSkillsCommand(program2) {
|
|
|
3730
3817
|
skills.command("version <bump> [path]").description("Bump skill version (patch | minor | major | x.y.z)").action(async (bump, pathArg) => {
|
|
3731
3818
|
try {
|
|
3732
3819
|
const dir = resolveSkillDir(pathArg);
|
|
3733
|
-
const
|
|
3734
|
-
if (!await pathExists(
|
|
3735
|
-
outputError("not_found", "No
|
|
3820
|
+
const skillMdPath = join8(dir, "SKILL.md");
|
|
3821
|
+
if (!await pathExists(skillMdPath)) {
|
|
3822
|
+
outputError("not_found", "No SKILL.md found. Run `agent-mesh skills init` first.");
|
|
3736
3823
|
}
|
|
3737
|
-
const raw = await readFile4(
|
|
3738
|
-
const
|
|
3739
|
-
const oldVersion =
|
|
3824
|
+
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3825
|
+
const { frontmatter } = parseSkillMd(raw);
|
|
3826
|
+
const oldVersion = frontmatter.version || "0.0.0";
|
|
3740
3827
|
const newVersion = bumpVersion(oldVersion, bump);
|
|
3741
|
-
|
|
3742
|
-
await writeFile2(skillJsonPath, JSON.stringify(data, null, 2) + "\n");
|
|
3828
|
+
await updateFrontmatterField(skillMdPath, "version", newVersion);
|
|
3743
3829
|
slog.success(`${oldVersion} \u2192 ${newVersion}`);
|
|
3744
3830
|
outputJson({ success: true, old: oldVersion, new: newVersion });
|
|
3745
3831
|
} catch (err) {
|
|
@@ -3747,6 +3833,196 @@ function registerSkillsCommand(program2) {
|
|
|
3747
3833
|
outputError("version_failed", err.message);
|
|
3748
3834
|
}
|
|
3749
3835
|
});
|
|
3836
|
+
skills.command("install <ref> [path]").description("Install a skill from agents.hot (use author/slug format)").option("--force", "Overwrite if already installed").action(async (ref, pathArg, opts) => {
|
|
3837
|
+
try {
|
|
3838
|
+
const { authorLogin, slug } = parseSkillRef(ref);
|
|
3839
|
+
const { skillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
3840
|
+
const targetDir = join8(skillsDir, slug);
|
|
3841
|
+
if (await pathExists(targetDir)) {
|
|
3842
|
+
if (!opts.force) {
|
|
3843
|
+
outputError("already_installed", `Skill "${slug}" is already installed at ${targetDir}. Use --force to overwrite.`);
|
|
3844
|
+
}
|
|
3845
|
+
await rm(targetDir, { recursive: true, force: true });
|
|
3846
|
+
}
|
|
3847
|
+
slog.info(`Installing ${authorLogin}/${slug}...`);
|
|
3848
|
+
const client = createClient();
|
|
3849
|
+
const result = await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
|
|
3850
|
+
slog.success(`Installed ${result.name} (${result.files_count} files)`);
|
|
3851
|
+
outputJson({
|
|
3852
|
+
success: true,
|
|
3853
|
+
skill: {
|
|
3854
|
+
author: authorLogin,
|
|
3855
|
+
slug: result.slug,
|
|
3856
|
+
name: result.name,
|
|
3857
|
+
version: result.version
|
|
3858
|
+
},
|
|
3859
|
+
installed_to: targetDir,
|
|
3860
|
+
files_count: result.files_count
|
|
3861
|
+
});
|
|
3862
|
+
} catch (err) {
|
|
3863
|
+
if (err instanceof PlatformApiError) {
|
|
3864
|
+
outputError(err.errorCode, err.message);
|
|
3865
|
+
}
|
|
3866
|
+
outputError("install_failed", err.message);
|
|
3867
|
+
}
|
|
3868
|
+
});
|
|
3869
|
+
skills.command("update [ref] [path]").description("Update installed skill(s) from agents.hot").action(async (ref, pathArg) => {
|
|
3870
|
+
try {
|
|
3871
|
+
const { skillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
3872
|
+
const client = createClient();
|
|
3873
|
+
const updated = [];
|
|
3874
|
+
const skipped = [];
|
|
3875
|
+
const failed = [];
|
|
3876
|
+
if (ref) {
|
|
3877
|
+
const { authorLogin, slug } = parseSkillRef(ref);
|
|
3878
|
+
const targetDir = join8(skillsDir, slug);
|
|
3879
|
+
if (!await pathExists(targetDir)) {
|
|
3880
|
+
outputError("not_installed", `Skill "${slug}" is not installed. Use "skills install ${ref}" first.`);
|
|
3881
|
+
}
|
|
3882
|
+
const skillMdPath = join8(targetDir, "SKILL.md");
|
|
3883
|
+
let localVersion = "0.0.0";
|
|
3884
|
+
if (await pathExists(skillMdPath)) {
|
|
3885
|
+
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3886
|
+
const { frontmatter } = parseSkillMd(raw);
|
|
3887
|
+
localVersion = frontmatter.version || "0.0.0";
|
|
3888
|
+
}
|
|
3889
|
+
const remote = await client.get(skillApiPath(authorLogin, slug));
|
|
3890
|
+
const remoteVersion = remote.version || "0.0.0";
|
|
3891
|
+
if (remoteVersion === localVersion) {
|
|
3892
|
+
slog.info(`${slug} is already up to date (v${localVersion})`);
|
|
3893
|
+
skipped.push({ slug, reason: "up_to_date" });
|
|
3894
|
+
} else {
|
|
3895
|
+
slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
|
|
3896
|
+
await rm(targetDir, { recursive: true, force: true });
|
|
3897
|
+
await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
|
|
3898
|
+
updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
|
|
3899
|
+
slog.success(`Updated ${slug} to v${remoteVersion}`);
|
|
3900
|
+
}
|
|
3901
|
+
} else {
|
|
3902
|
+
if (!await pathExists(skillsDir)) {
|
|
3903
|
+
outputError("no_skills_dir", `Skills directory not found: ${skillsDir}`);
|
|
3904
|
+
}
|
|
3905
|
+
const entries = await readdir2(skillsDir, { withFileTypes: true });
|
|
3906
|
+
for (const entry of entries) {
|
|
3907
|
+
if (!entry.isDirectory()) continue;
|
|
3908
|
+
const slug = entry.name;
|
|
3909
|
+
const skillMdPath = join8(skillsDir, slug, "SKILL.md");
|
|
3910
|
+
if (!await pathExists(skillMdPath)) {
|
|
3911
|
+
skipped.push({ slug, reason: "no_skill_md" });
|
|
3912
|
+
continue;
|
|
3913
|
+
}
|
|
3914
|
+
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3915
|
+
const { frontmatter } = parseSkillMd(raw);
|
|
3916
|
+
const localVersion = frontmatter.version || "0.0.0";
|
|
3917
|
+
const authorLogin = frontmatter.author;
|
|
3918
|
+
if (!authorLogin) {
|
|
3919
|
+
skipped.push({ slug, reason: "no_author_in_frontmatter" });
|
|
3920
|
+
continue;
|
|
3921
|
+
}
|
|
3922
|
+
try {
|
|
3923
|
+
const remote = await client.get(skillApiPath(authorLogin, slug));
|
|
3924
|
+
const remoteVersion = remote.version || "0.0.0";
|
|
3925
|
+
if (remoteVersion === localVersion) {
|
|
3926
|
+
skipped.push({ slug, reason: "up_to_date" });
|
|
3927
|
+
} else {
|
|
3928
|
+
slog.info(`Updating ${slug}: v${localVersion} \u2192 v${remoteVersion}...`);
|
|
3929
|
+
await rm(join8(skillsDir, slug), { recursive: true, force: true });
|
|
3930
|
+
await downloadAndInstallSkill(client, authorLogin, slug, skillsDir);
|
|
3931
|
+
updated.push({ slug, name: remote.name, old_version: localVersion, new_version: remoteVersion });
|
|
3932
|
+
}
|
|
3933
|
+
} catch (err) {
|
|
3934
|
+
failed.push({ slug, error: err.message });
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
slog.success(`Update complete: ${updated.length} updated, ${skipped.length} skipped, ${failed.length} failed`);
|
|
3939
|
+
outputJson({ success: true, updated, skipped, failed });
|
|
3940
|
+
} catch (err) {
|
|
3941
|
+
if (err instanceof PlatformApiError) {
|
|
3942
|
+
outputError(err.errorCode, err.message);
|
|
3943
|
+
}
|
|
3944
|
+
outputError("update_failed", err.message);
|
|
3945
|
+
}
|
|
3946
|
+
});
|
|
3947
|
+
skills.command("remove <slug> [path]").description("Remove a locally installed skill").action(async (slug, pathArg) => {
|
|
3948
|
+
try {
|
|
3949
|
+
const { skillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
3950
|
+
const targetDir = join8(skillsDir, slug);
|
|
3951
|
+
if (!await pathExists(targetDir)) {
|
|
3952
|
+
outputError("not_installed", `Skill "${slug}" is not installed at ${targetDir}`);
|
|
3953
|
+
}
|
|
3954
|
+
await rm(targetDir, { recursive: true, force: true });
|
|
3955
|
+
slog.success(`Removed skill: ${slug}`);
|
|
3956
|
+
outputJson({ success: true, removed: slug, path: targetDir });
|
|
3957
|
+
} catch (err) {
|
|
3958
|
+
outputError("remove_failed", err.message);
|
|
3959
|
+
}
|
|
3960
|
+
});
|
|
3961
|
+
skills.command("installed [path]").description("List locally installed skills").option("--check-updates", "Check for available updates").option("--human", "Human-readable table output").action(async (pathArg, opts) => {
|
|
3962
|
+
try {
|
|
3963
|
+
const { skillsDir } = await resolveSkillsRootAsync(pathArg);
|
|
3964
|
+
if (!await pathExists(skillsDir)) {
|
|
3965
|
+
if (opts.human) {
|
|
3966
|
+
slog.info(`No skills directory found at ${skillsDir}`);
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3969
|
+
outputJson({ skills_dir: skillsDir, skills: [] });
|
|
3970
|
+
return;
|
|
3971
|
+
}
|
|
3972
|
+
const entries = await readdir2(skillsDir, { withFileTypes: true });
|
|
3973
|
+
const skills2 = [];
|
|
3974
|
+
for (const entry of entries) {
|
|
3975
|
+
if (!entry.isDirectory()) continue;
|
|
3976
|
+
const slug = entry.name;
|
|
3977
|
+
const skillMdPath = join8(skillsDir, slug, "SKILL.md");
|
|
3978
|
+
if (!await pathExists(skillMdPath)) continue;
|
|
3979
|
+
const raw = await readFile4(skillMdPath, "utf-8");
|
|
3980
|
+
const { frontmatter } = parseSkillMd(raw);
|
|
3981
|
+
const skillInfo = {
|
|
3982
|
+
slug,
|
|
3983
|
+
name: frontmatter.name || slug,
|
|
3984
|
+
version: frontmatter.version || "0.0.0",
|
|
3985
|
+
author: frontmatter.author
|
|
3986
|
+
};
|
|
3987
|
+
if (opts.checkUpdates && skillInfo.author) {
|
|
3988
|
+
try {
|
|
3989
|
+
const client = createClient();
|
|
3990
|
+
const remote = await client.get(skillApiPath(skillInfo.author, slug));
|
|
3991
|
+
skillInfo.remote_version = remote.version || "0.0.0";
|
|
3992
|
+
skillInfo.has_update = skillInfo.remote_version !== skillInfo.version;
|
|
3993
|
+
} catch {
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
skills2.push(skillInfo);
|
|
3997
|
+
}
|
|
3998
|
+
if (opts.human) {
|
|
3999
|
+
if (skills2.length === 0) {
|
|
4000
|
+
slog.info("No skills installed.");
|
|
4001
|
+
return;
|
|
4002
|
+
}
|
|
4003
|
+
const columns = [
|
|
4004
|
+
{ key: "name", label: "NAME", width: 24 },
|
|
4005
|
+
{ key: "version", label: "VERSION", width: 12 },
|
|
4006
|
+
{ key: "author", label: "AUTHOR", width: 16 }
|
|
4007
|
+
];
|
|
4008
|
+
if (opts.checkUpdates) {
|
|
4009
|
+
columns.push({ key: "update", label: "UPDATE", width: 14 });
|
|
4010
|
+
}
|
|
4011
|
+
const rows = skills2.map((s) => ({
|
|
4012
|
+
name: s.name,
|
|
4013
|
+
version: s.version,
|
|
4014
|
+
author: s.author || "\u2014",
|
|
4015
|
+
update: s.has_update ? `${GREEN}${s.remote_version}${RESET}` : "\u2014"
|
|
4016
|
+
}));
|
|
4017
|
+
slog.banner("Installed Skills");
|
|
4018
|
+
console.log(renderTable(columns, rows));
|
|
4019
|
+
return;
|
|
4020
|
+
}
|
|
4021
|
+
outputJson({ skills_dir: skillsDir, skills: skills2 });
|
|
4022
|
+
} catch (err) {
|
|
4023
|
+
outputError("installed_failed", err.message);
|
|
4024
|
+
}
|
|
4025
|
+
});
|
|
3750
4026
|
}
|
|
3751
4027
|
|
|
3752
4028
|
// src/commands/discover.ts
|