From 67b7fa30a76e57a4589ef549623ce090122ec6bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=95=85=E7=92=83?= Date: Mon, 27 Jul 2026 16:02:27 +0800 Subject: [PATCH] feat: opt bl skill update commend, keep it atom --- packages/commands/src/commands/skill/add.ts | 40 +++++----------- .../commands/src/commands/skill/shared.ts | 23 +++++++++ .../commands/src/commands/skill/update.ts | 48 ++++++++++++------- skills/bailian-cli/reference/skill.md | 6 +-- 4 files changed, 69 insertions(+), 48 deletions(-) diff --git a/packages/commands/src/commands/skill/add.ts b/packages/commands/src/commands/skill/add.ts index ce0b37a..1cea7ff 100644 --- a/packages/commands/src/commands/skill/add.ts +++ b/packages/commands/src/commands/skill/add.ts @@ -12,7 +12,7 @@ import { writeSkillLock, } from "bailian-cli-core"; import { emitBare, emitResult, formatTable } from "bailian-cli-runtime"; -import { parseSkillNames } from "./shared.ts"; +import { parseSkillNames, runWithConcurrency } from "./shared.ts"; interface AddOutcome { name: string; @@ -25,26 +25,6 @@ interface AddOutcome { /** Max number of skills downloading/installing at the same time. */ const INSTALL_CONCURRENCY = 3; -/** - * Run async task factories with a bounded concurrency pool. - * Returns results in the same order as the input tasks array. - */ -async function runWithConcurrency(tasks: Array<() => Promise>, limit: number): Promise { - const results: T[] = new Array(tasks.length); - let nextIndex = 0; - - async function worker(): Promise { - while (nextIndex < tasks.length) { - const currentIndex = nextIndex++; - results[currentIndex] = await tasks[currentIndex](); - } - } - - const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => worker()); - await Promise.all(workers); - return results; -} - export default defineCommand({ description: "Install skills from the Bailian skill registry into local agents", auth: "none", @@ -106,24 +86,28 @@ export default defineCommand({ if (format === "json") { emitResult( - { registry: getSkillRegistryBaseUrl(), agents: agents.map((a) => a.id), skills: results }, + { + registry: getSkillRegistryBaseUrl(), + agents: agents.map((agent) => agent.id), + skills: results, + }, format, ); } else if (results.length === 0) { emitBare("Skill registry is empty; no skills to install."); } else { - const rows = results.map((r) => [ - r.name, - r.status, - r.publishedAt ? r.publishedAt.slice(0, 10) : "-", - r.status === "installed" ? r.agents?.join(", ") || "-" : (r.reason ?? "-"), + const rows = results.map((result) => [ + result.name, + result.status, + result.publishedAt ? result.publishedAt.slice(0, 10) : "-", + result.status === "installed" ? result.agents?.join(", ") || "-" : (result.reason ?? "-"), ]); for (const line of formatTable(["NAME", "STATUS", "PUBLISHED", "AGENTS / REASON"], rows)) { emitBare(line); } } - const failed = results.filter((r) => r.status === "failed"); + const failed = results.filter((result) => result.status === "failed"); if (failed.length > 0) { throw new BailianError( `${failed.length}/${results.length} skill(s) failed to install`, diff --git a/packages/commands/src/commands/skill/shared.ts b/packages/commands/src/commands/skill/shared.ts index e2c6f01..839fee2 100644 --- a/packages/commands/src/commands/skill/shared.ts +++ b/packages/commands/src/commands/skill/shared.ts @@ -28,3 +28,26 @@ export function parseSkillNames(raw: string | undefined, defaultAll: boolean): s } return parts; } + +/** + * Run async task factories with a bounded concurrency pool. + * Returns results in the same order as the input tasks array. + */ +export async function runWithConcurrency( + tasks: Array<() => Promise>, + limit: number, +): Promise { + const results: T[] = new Array(tasks.length); + let nextIndex = 0; + + async function worker(): Promise { + while (nextIndex < tasks.length) { + const currentIndex = nextIndex++; + results[currentIndex] = await tasks[currentIndex](); + } + } + + const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => worker()); + await Promise.all(workers); + return results; +} diff --git a/packages/commands/src/commands/skill/update.ts b/packages/commands/src/commands/skill/update.ts index 4a3cff0..068868c 100644 --- a/packages/commands/src/commands/skill/update.ts +++ b/packages/commands/src/commands/skill/update.ts @@ -13,7 +13,7 @@ import { writeSkillLock, } from "bailian-cli-core"; import { emitBare, emitResult, formatTable } from "bailian-cli-runtime"; -import { parseSkillNames } from "./shared.ts"; +import { parseSkillNames, runWithConcurrency } from "./shared.ts"; interface UpdateOutcome { name: string; @@ -22,6 +22,9 @@ interface UpdateOutcome { reason?: string; } +/** Max number of skills downloading/installing at the same time. */ +const UPDATE_CONCURRENCY = 3; + export default defineCommand({ description: "Update installed skills to the latest registry versions", auth: "none", @@ -31,7 +34,7 @@ export default defineCommand({ type: "string", valueHint: "", description: - "Skills to update: all (default, only changed ones) or comma-separated names (force reinstall)", + "Skills to update: all (default, only changed ones) or comma-separated names (force update installed skills)", }, }, exampleArgs: ["", "--name spark-video"], @@ -63,16 +66,25 @@ export default defineCommand({ targets.push(name); } } else { - // Explicit names = force reinstall (equivalent to add if not yet installed) - targets.push(...requested); + // Explicit names: only update skills that are already installed; reject uninstalled ones + for (const name of requested) { + if (!lock.skills[name]) { + results.push({ + name, + status: "failed", + reason: "not installed; run bl skill add --name " + name + " first", + }); + continue; + } + targets.push(name); + } } const agents = detectInstalledAgents(); - for (const name of targets) { + const tasks = targets.map((name) => async (): Promise => { const entry = index.skills[name]; if (!entry) { - results.push({ name, status: "failed", reason: "skill not found in registry" }); - continue; + return { name, status: "failed", reason: "skill not found in registry" }; } try { await installSkill(name, entry); @@ -86,15 +98,17 @@ export default defineCommand({ ...(entry.description ? { description: entry.description } : {}), links: effective.map((link) => link.path), }; - results.push({ name, status: "updated", publishedAt: entry.publishedAt }); + return { name, status: "updated", publishedAt: entry.publishedAt }; } catch (err) { - results.push({ + return { name, status: "failed", reason: err instanceof Error ? err.message : String(err), - }); + }; } - } + }); + const updateResults = await runWithConcurrency(tasks, UPDATE_CONCURRENCY); + results.push(...updateResults); writeSkillLock(lock); if (format === "json") { @@ -102,18 +116,18 @@ export default defineCommand({ } else if (results.length === 0) { emitBare("No skills installed locally; run bl skill add first."); } else { - const rows = results.map((r) => [ - r.name, - r.status, - r.publishedAt ? r.publishedAt.slice(0, 10) : "-", - r.reason ?? "-", + const rows = results.map((result) => [ + result.name, + result.status, + result.publishedAt ? result.publishedAt.slice(0, 10) : "-", + result.reason ?? "-", ]); for (const line of formatTable(["NAME", "STATUS", "PUBLISHED", "REASON"], rows)) { emitBare(line); } } - const failed = results.filter((r) => r.status === "failed"); + const failed = results.filter((result) => result.status === "failed"); if (failed.length > 0) { throw new BailianError( `${failed.length} skill(s) failed to update`, diff --git a/skills/bailian-cli/reference/skill.md b/skills/bailian-cli/reference/skill.md index 2cd45d8..7020f0f 100644 --- a/skills/bailian-cli/reference/skill.md +++ b/skills/bailian-cli/reference/skill.md @@ -100,9 +100,9 @@ bl skill remove --name all #### Flags -| Flag | Type | Required | Description | -| ------------------------ | ------ | -------- | --------------------------------------------------------------------------------------------- | -| `--name ` | string | no | Skills to update: all (default, only changed ones) or comma-separated names (force reinstall) | +| Flag | Type | Required | Description | +| ------------------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------- | +| `--name ` | string | no | Skills to update: all (default, only changed ones) or comma-separated names (force update installed skills) | #### Examples