Skip to content

Commit 67b7fa3

Browse files
committed
feat: opt bl skill update commend, keep it atom
1 parent bd17c27 commit 67b7fa3

4 files changed

Lines changed: 69 additions & 48 deletions

File tree

packages/commands/src/commands/skill/add.ts

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
writeSkillLock,
1313
} from "bailian-cli-core";
1414
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
15-
import { parseSkillNames } from "./shared.ts";
15+
import { parseSkillNames, runWithConcurrency } from "./shared.ts";
1616

1717
interface AddOutcome {
1818
name: string;
@@ -25,26 +25,6 @@ interface AddOutcome {
2525
/** Max number of skills downloading/installing at the same time. */
2626
const INSTALL_CONCURRENCY = 3;
2727

28-
/**
29-
* Run async task factories with a bounded concurrency pool.
30-
* Returns results in the same order as the input tasks array.
31-
*/
32-
async function runWithConcurrency<T>(tasks: Array<() => Promise<T>>, limit: number): Promise<T[]> {
33-
const results: T[] = new Array(tasks.length);
34-
let nextIndex = 0;
35-
36-
async function worker(): Promise<void> {
37-
while (nextIndex < tasks.length) {
38-
const currentIndex = nextIndex++;
39-
results[currentIndex] = await tasks[currentIndex]();
40-
}
41-
}
42-
43-
const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => worker());
44-
await Promise.all(workers);
45-
return results;
46-
}
47-
4828
export default defineCommand({
4929
description: "Install skills from the Bailian skill registry into local agents",
5030
auth: "none",
@@ -106,24 +86,28 @@ export default defineCommand({
10686

10787
if (format === "json") {
10888
emitResult(
109-
{ registry: getSkillRegistryBaseUrl(), agents: agents.map((a) => a.id), skills: results },
89+
{
90+
registry: getSkillRegistryBaseUrl(),
91+
agents: agents.map((agent) => agent.id),
92+
skills: results,
93+
},
11094
format,
11195
);
11296
} else if (results.length === 0) {
11397
emitBare("Skill registry is empty; no skills to install.");
11498
} else {
115-
const rows = results.map((r) => [
116-
r.name,
117-
r.status,
118-
r.publishedAt ? r.publishedAt.slice(0, 10) : "-",
119-
r.status === "installed" ? r.agents?.join(", ") || "-" : (r.reason ?? "-"),
99+
const rows = results.map((result) => [
100+
result.name,
101+
result.status,
102+
result.publishedAt ? result.publishedAt.slice(0, 10) : "-",
103+
result.status === "installed" ? result.agents?.join(", ") || "-" : (result.reason ?? "-"),
120104
]);
121105
for (const line of formatTable(["NAME", "STATUS", "PUBLISHED", "AGENTS / REASON"], rows)) {
122106
emitBare(line);
123107
}
124108
}
125109

126-
const failed = results.filter((r) => r.status === "failed");
110+
const failed = results.filter((result) => result.status === "failed");
127111
if (failed.length > 0) {
128112
throw new BailianError(
129113
`${failed.length}/${results.length} skill(s) failed to install`,

packages/commands/src/commands/skill/shared.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,26 @@ export function parseSkillNames(raw: string | undefined, defaultAll: boolean): s
2828
}
2929
return parts;
3030
}
31+
32+
/**
33+
* Run async task factories with a bounded concurrency pool.
34+
* Returns results in the same order as the input tasks array.
35+
*/
36+
export async function runWithConcurrency<T>(
37+
tasks: Array<() => Promise<T>>,
38+
limit: number,
39+
): Promise<T[]> {
40+
const results: T[] = new Array(tasks.length);
41+
let nextIndex = 0;
42+
43+
async function worker(): Promise<void> {
44+
while (nextIndex < tasks.length) {
45+
const currentIndex = nextIndex++;
46+
results[currentIndex] = await tasks[currentIndex]();
47+
}
48+
}
49+
50+
const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => worker());
51+
await Promise.all(workers);
52+
return results;
53+
}

packages/commands/src/commands/skill/update.ts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
writeSkillLock,
1414
} from "bailian-cli-core";
1515
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
16-
import { parseSkillNames } from "./shared.ts";
16+
import { parseSkillNames, runWithConcurrency } from "./shared.ts";
1717

1818
interface UpdateOutcome {
1919
name: string;
@@ -22,6 +22,9 @@ interface UpdateOutcome {
2222
reason?: string;
2323
}
2424

25+
/** Max number of skills downloading/installing at the same time. */
26+
const UPDATE_CONCURRENCY = 3;
27+
2528
export default defineCommand({
2629
description: "Update installed skills to the latest registry versions",
2730
auth: "none",
@@ -31,7 +34,7 @@ export default defineCommand({
3134
type: "string",
3235
valueHint: "<all|name,...>",
3336
description:
34-
"Skills to update: all (default, only changed ones) or comma-separated names (force reinstall)",
37+
"Skills to update: all (default, only changed ones) or comma-separated names (force update installed skills)",
3538
},
3639
},
3740
exampleArgs: ["", "--name spark-video"],
@@ -63,16 +66,25 @@ export default defineCommand({
6366
targets.push(name);
6467
}
6568
} else {
66-
// Explicit names = force reinstall (equivalent to add if not yet installed)
67-
targets.push(...requested);
69+
// Explicit names: only update skills that are already installed; reject uninstalled ones
70+
for (const name of requested) {
71+
if (!lock.skills[name]) {
72+
results.push({
73+
name,
74+
status: "failed",
75+
reason: "not installed; run bl skill add --name " + name + " first",
76+
});
77+
continue;
78+
}
79+
targets.push(name);
80+
}
6881
}
6982

7083
const agents = detectInstalledAgents();
71-
for (const name of targets) {
84+
const tasks = targets.map((name) => async (): Promise<UpdateOutcome> => {
7285
const entry = index.skills[name];
7386
if (!entry) {
74-
results.push({ name, status: "failed", reason: "skill not found in registry" });
75-
continue;
87+
return { name, status: "failed", reason: "skill not found in registry" };
7688
}
7789
try {
7890
await installSkill(name, entry);
@@ -86,34 +98,36 @@ export default defineCommand({
8698
...(entry.description ? { description: entry.description } : {}),
8799
links: effective.map((link) => link.path),
88100
};
89-
results.push({ name, status: "updated", publishedAt: entry.publishedAt });
101+
return { name, status: "updated", publishedAt: entry.publishedAt };
90102
} catch (err) {
91-
results.push({
103+
return {
92104
name,
93105
status: "failed",
94106
reason: err instanceof Error ? err.message : String(err),
95-
});
107+
};
96108
}
97-
}
109+
});
110+
const updateResults = await runWithConcurrency(tasks, UPDATE_CONCURRENCY);
111+
results.push(...updateResults);
98112
writeSkillLock(lock);
99113

100114
if (format === "json") {
101115
emitResult({ registry: getSkillRegistryBaseUrl(), skills: results }, format);
102116
} else if (results.length === 0) {
103117
emitBare("No skills installed locally; run bl skill add first.");
104118
} else {
105-
const rows = results.map((r) => [
106-
r.name,
107-
r.status,
108-
r.publishedAt ? r.publishedAt.slice(0, 10) : "-",
109-
r.reason ?? "-",
119+
const rows = results.map((result) => [
120+
result.name,
121+
result.status,
122+
result.publishedAt ? result.publishedAt.slice(0, 10) : "-",
123+
result.reason ?? "-",
110124
]);
111125
for (const line of formatTable(["NAME", "STATUS", "PUBLISHED", "REASON"], rows)) {
112126
emitBare(line);
113127
}
114128
}
115129

116-
const failed = results.filter((r) => r.status === "failed");
130+
const failed = results.filter((result) => result.status === "failed");
117131
if (failed.length > 0) {
118132
throw new BailianError(
119133
`${failed.length} skill(s) failed to update`,

skills/bailian-cli/reference/skill.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ bl skill remove --name all
100100

101101
#### Flags
102102

103-
| Flag | Type | Required | Description |
104-
| ------------------------ | ------ | -------- | --------------------------------------------------------------------------------------------- |
105-
| `--name <all\|name,...>` | string | no | Skills to update: all (default, only changed ones) or comma-separated names (force reinstall) |
103+
| Flag | Type | Required | Description |
104+
| ------------------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------- |
105+
| `--name <all\|name,...>` | string | no | Skills to update: all (default, only changed ones) or comma-separated names (force update installed skills) |
106106

107107
#### Examples
108108

0 commit comments

Comments
 (0)