mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
feat: opt bl skill update commend, keep it atom
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user