feat: opt bl skill update commend, keep it atom

This commit is contained in:
故璃
2026-07-27 16:02:27 +08:00
parent bd17c27023
commit 67b7fa30a7
4 changed files with 69 additions and 48 deletions
+12 -28
View File
@@ -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<T>(tasks: Array<() => Promise<T>>, limit: number): Promise<T[]> {
const results: T[] = new Array(tasks.length);
let nextIndex = 0;
async function worker(): Promise<void> {
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`,
@@ -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<T>(
tasks: Array<() => Promise<T>>,
limit: number,
): Promise<T[]> {
const results: T[] = new Array(tasks.length);
let nextIndex = 0;
async function worker(): Promise<void> {
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;
}
+31 -17
View File
@@ -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: "<all|name,...>",
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<UpdateOutcome> => {
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`,
+3 -3
View File
@@ -100,9 +100,9 @@ bl skill remove --name all
#### Flags
| Flag | Type | Required | Description |
| ------------------------ | ------ | -------- | --------------------------------------------------------------------------------------------- |
| `--name <all\|name,...>` | string | no | Skills to update: all (default, only changed ones) or comma-separated names (force reinstall) |
| Flag | Type | Required | Description |
| ------------------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| `--name <all\|name,...>` | string | no | Skills to update: all (default, only changed ones) or comma-separated names (force update installed skills) |
#### Examples