mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
Merge pull request #143 from modelstudioai/feat/skill-init-commend
feat: add skill init & opt commend flags
This commit is contained in:
@@ -93,6 +93,7 @@ import {
|
||||
skillUpdate,
|
||||
skillRemove,
|
||||
skillList,
|
||||
skillInit,
|
||||
managedAgentInit,
|
||||
managedAgentValidate,
|
||||
managedAgentPlan,
|
||||
@@ -211,6 +212,7 @@ export const commands: Record<string, AnyCommand> = {
|
||||
"skill update": skillUpdate,
|
||||
"skill remove": skillRemove,
|
||||
"skill list": skillList,
|
||||
"skill init": skillInit,
|
||||
"managed-agent init": managedAgentInit,
|
||||
"managed-agent validate": managedAgentValidate,
|
||||
"managed-agent plan": managedAgentPlan,
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
detectInstalledAgents,
|
||||
fetchSkillsIndex,
|
||||
getSkillRegistryBaseUrl,
|
||||
@@ -28,22 +27,31 @@ const INSTALL_CONCURRENCY = 3;
|
||||
export default defineCommand({
|
||||
description: "Install skills from the Bailian skill registry into local agents",
|
||||
auth: "none",
|
||||
usageArgs: "--name <all|name,...>",
|
||||
usageArgs: "--all | --name <name,...>",
|
||||
flags: {
|
||||
all: {
|
||||
type: "switch",
|
||||
description: "Install all skills from the registry",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
valueHint: "<all|name,...>",
|
||||
description: "Skills to install: all or comma-separated skill names",
|
||||
required: true,
|
||||
valueHint: "<name,...>",
|
||||
description: "Comma-separated skill names to install",
|
||||
},
|
||||
},
|
||||
exampleArgs: ["--name all", "--name spark-video,bailian-model-recommend"],
|
||||
validate(flags) {
|
||||
if (flags.all && flags.name) return "Use either --all or --name, not both";
|
||||
if (!flags.all && !flags.name)
|
||||
return "Specify --all to install everything or --name <name,...> for specific skills";
|
||||
return undefined;
|
||||
},
|
||||
exampleArgs: ["--all", "--name spark-video,bailian-model-recommend"],
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const requested = parseSkillNames(ctx.flags.name, false);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const index = await fetchSkillsIndex();
|
||||
const remoteNames = Object.keys(index.skills);
|
||||
const names = requested === "all" ? remoteNames : requested;
|
||||
const parsed = ctx.flags.all ? "all" : parseSkillNames(ctx.flags.name, false);
|
||||
const names = parsed === "all" ? remoteNames : parsed;
|
||||
|
||||
const lock = readSkillLock();
|
||||
const agents = detectInstalledAgents();
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectInstalledAgents,
|
||||
fetchSkillsIndex,
|
||||
getSkillRegistryBaseUrl,
|
||||
installSkillWithFanout,
|
||||
readSkillLock,
|
||||
runWithConcurrency,
|
||||
writeSkillLock,
|
||||
} from "bailian-cli-core";
|
||||
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
|
||||
|
||||
interface InitOutcome {
|
||||
name: string;
|
||||
status: "installed" | "failed";
|
||||
publishedAt?: string;
|
||||
agents?: string[];
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/** Prefix used to identify first-party Bailian skills in the registry. */
|
||||
const BAILIAN_PREFIX = "bailian-";
|
||||
|
||||
/** Max number of skills downloading/installing at the same time. */
|
||||
const INIT_CONCURRENCY = 3;
|
||||
|
||||
export default defineCommand({
|
||||
description: "Install all bailian-* skills (one-shot bootstrap for new environments)",
|
||||
auth: "none",
|
||||
usageArgs: "",
|
||||
exampleArgs: [""],
|
||||
notes: [
|
||||
"Fetches the registry index and installs every skill whose name starts with bailian-",
|
||||
"Equivalent to: bl skill add --all (filtered to bailian-* skills)",
|
||||
],
|
||||
async run(ctx) {
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const index = await fetchSkillsIndex();
|
||||
|
||||
// Discover all bailian-* skills from the live registry index
|
||||
const names = Object.keys(index.skills).filter((name) => name.startsWith(BAILIAN_PREFIX));
|
||||
|
||||
const lock = readSkillLock();
|
||||
const agents = detectInstalledAgents();
|
||||
|
||||
const tasks = names.map((name) => async (): Promise<InitOutcome> => {
|
||||
const entry = index.skills[name];
|
||||
try {
|
||||
const record = await installSkillWithFanout(
|
||||
name,
|
||||
entry,
|
||||
agents,
|
||||
lock.skills[name]?.links ?? [],
|
||||
);
|
||||
lock.skills[name] = record.lockEntry;
|
||||
return {
|
||||
name,
|
||||
status: "installed",
|
||||
publishedAt: entry.publishedAt,
|
||||
agents: record.linkedAgents,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
name,
|
||||
status: "failed",
|
||||
reason: err instanceof Error ? err.message : String(err),
|
||||
};
|
||||
}
|
||||
});
|
||||
const results = await runWithConcurrency(tasks, INIT_CONCURRENCY);
|
||||
writeSkillLock(lock);
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(
|
||||
{
|
||||
registry: getSkillRegistryBaseUrl(),
|
||||
agents: agents.map((agent) => agent.id),
|
||||
skills: results,
|
||||
},
|
||||
format,
|
||||
);
|
||||
} else if (results.length === 0) {
|
||||
emitBare("No bailian-* skills found in the registry.");
|
||||
} else {
|
||||
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((result) => result.status === "failed");
|
||||
if (failed.length > 0) {
|
||||
throw new BailianError(
|
||||
`${failed.length}/${results.length} skill(s) failed to install`,
|
||||
ExitCode.GENERAL,
|
||||
"Check the reason for failed skills in the output; network failures can be retried with bl skill init",
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
computeSkillStatuses,
|
||||
fetchSkillsIndex,
|
||||
getSkillRegistryBaseUrl,
|
||||
@@ -24,7 +23,7 @@ export default defineCommand({
|
||||
"STATUS: installed | outdated | not-installed | missing (lock has it, dir deleted) | untracked (dir exists, not managed)",
|
||||
],
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
// Three-way reconciliation: live remote index × skill-lock.json (installation facts) × disk
|
||||
const index = await fetchSkillsIndex();
|
||||
const lock = readSkillLock();
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
listSkillDirsOnDisk,
|
||||
parseSkillNames,
|
||||
readSkillLock,
|
||||
@@ -34,7 +33,7 @@ export default defineCommand({
|
||||
exampleArgs: ["--name spark-video", "--name all"],
|
||||
async run(ctx) {
|
||||
// Purely local operation: no remote access, works offline
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const requested = parseSkillNames(ctx.flags.name, false);
|
||||
const lock = readSkillLock();
|
||||
const names = requested === "all" ? Object.keys(lock.skills) : requested;
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
detectInstalledAgents,
|
||||
fanOutSkillToAgents,
|
||||
fetchSkillsIndex,
|
||||
@@ -29,19 +28,27 @@ const UPDATE_CONCURRENCY = 3;
|
||||
export default defineCommand({
|
||||
description: "Update installed skills to the latest registry versions",
|
||||
auth: "none",
|
||||
usageArgs: "[--name <all|name,...>]",
|
||||
usageArgs: "[--all] [--name <name,...>]",
|
||||
flags: {
|
||||
all: {
|
||||
type: "switch",
|
||||
description: "Update all installed skills (default when neither --all nor --name is given)",
|
||||
},
|
||||
name: {
|
||||
type: "string",
|
||||
valueHint: "<all|name,...>",
|
||||
description:
|
||||
"Skills to update: all (default, only changed ones) or comma-separated names (force update installed skills)",
|
||||
valueHint: "<name,...>",
|
||||
description: "Comma-separated skill names to update (must be already installed)",
|
||||
},
|
||||
},
|
||||
exampleArgs: ["", "--name spark-video"],
|
||||
validate(flags) {
|
||||
if (flags.all && flags.name) return "Use either --all or --name, not both";
|
||||
return undefined;
|
||||
},
|
||||
exampleArgs: ["", "--all", "--name spark-video"],
|
||||
async run(ctx) {
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
const requested = parseSkillNames(ctx.flags.name, true);
|
||||
const format = ctx.settings.outputExplicit ? ctx.settings.output : "json";
|
||||
const updateAll = ctx.flags.all || !ctx.flags.name;
|
||||
const requested = updateAll ? "all" : parseSkillNames(ctx.flags.name, false);
|
||||
const index = await fetchSkillsIndex();
|
||||
const lock = readSkillLock();
|
||||
const disk = new Set(listSkillDirsOnDisk());
|
||||
|
||||
@@ -117,3 +117,4 @@ export { default as skillAdd } from "./commands/skill/add.ts";
|
||||
export { default as skillUpdate } from "./commands/skill/update.ts";
|
||||
export { default as skillRemove } from "./commands/skill/remove.ts";
|
||||
export { default as skillList } from "./commands/skill/list.ts";
|
||||
export { default as skillInit } from "./commands/skill/init.ts";
|
||||
|
||||
@@ -17,12 +17,14 @@ describe("e2e: skill", () => {
|
||||
test("skill add --help exits successfully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, ["skill", "add", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--all/);
|
||||
expect(stderr).toMatch(/--name/);
|
||||
});
|
||||
|
||||
test("skill update --help exits successfully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, ["skill", "update", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--all/);
|
||||
expect(stderr).toMatch(/--name/);
|
||||
});
|
||||
|
||||
@@ -37,18 +39,37 @@ describe("e2e: skill", () => {
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/list|registry/i);
|
||||
});
|
||||
|
||||
test("skill init --help exits successfully", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, ["skill", "init", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/bailian/i);
|
||||
});
|
||||
});
|
||||
|
||||
// Local-only cases: auth "none" + validation happens before any network access, no gating needed
|
||||
describe("e2e: skill (local, no credentials)", () => {
|
||||
test("skill add without --name errors as usage error (2)", async () => {
|
||||
test("skill add without --all or --name errors as usage error (2)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, [
|
||||
"skill",
|
||||
"add",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--name|Usage:/i);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--all|--name|Usage:/i);
|
||||
});
|
||||
|
||||
test("skill add with both --all and --name errors as usage error (2)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, [
|
||||
"skill",
|
||||
"add",
|
||||
"--all",
|
||||
"--name",
|
||||
"spark-video",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--all|--name|either/i);
|
||||
});
|
||||
|
||||
test("skill remove without --name errors as usage error (2)", async () => {
|
||||
@@ -61,19 +82,6 @@ describe("e2e: skill (local, no credentials)", () => {
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/--name|Usage:/i);
|
||||
});
|
||||
|
||||
test("skill add rejects mixing all with specific names (2)", async () => {
|
||||
// parseSkillNames throws UsageError before fetchSkillsIndex — offline-safe
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(SKILL_ROUTES, [
|
||||
"skill",
|
||||
"add",
|
||||
"--name",
|
||||
"all,spark-video",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(`${stdout}\n${stderr}`).toMatch(/all/i);
|
||||
});
|
||||
|
||||
test("skill remove of a not-installed skill fails with reason (1)", async () => {
|
||||
const configDir = makeTempConfigDir();
|
||||
const { stdout, exitCode } = await runCommandE2e(
|
||||
|
||||
@@ -165,6 +165,7 @@ export const SKILL_ROUTES: E2eRouteExports = {
|
||||
"skill update": "skillUpdate",
|
||||
"skill remove": "skillRemove",
|
||||
"skill list": "skillList",
|
||||
"skill init": "skillInit",
|
||||
};
|
||||
|
||||
export const MANAGED_AGENT_ROUTES: E2eRouteExports = {
|
||||
|
||||
@@ -52,6 +52,7 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `bl quota request` | Request a temporary quota increase | [quota.md](quota.md) |
|
||||
| `bl search web` | Search the web using DashScope MCP WebSearch service | [search.md](search.md) |
|
||||
| `bl skill add` | Install skills from the Bailian skill registry into local agents | [skill.md](skill.md) |
|
||||
| `bl skill init` | Install all bailian-\* skills (one-shot bootstrap for new environments) | [skill.md](skill.md) |
|
||||
| `bl skill list` | List registry skills and diff against local installs | [skill.md](skill.md) |
|
||||
| `bl skill remove` | Remove locally installed skills (registry is untouched) | [skill.md](skill.md) |
|
||||
| `bl skill update` | Update installed skills to the latest registry versions | [skill.md](skill.md) |
|
||||
@@ -86,7 +87,7 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `plugin` | `install`, `link`, `list`, `remove` | [plugin.md](plugin.md) |
|
||||
| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) |
|
||||
| `search` | `web` | [search.md](search.md) |
|
||||
| `skill` | `add`, `list`, `remove`, `update` | [skill.md](skill.md) |
|
||||
| `skill` | `add`, `init`, `list`, `remove`, `update` | [skill.md](skill.md) |
|
||||
| `text` | `chat` | [text.md](text.md) |
|
||||
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) |
|
||||
| `update` | `(root)` | [update.md](update.md) |
|
||||
|
||||
@@ -7,12 +7,13 @@ Index: [index.md](index.md)
|
||||
|
||||
## Commands in this group
|
||||
|
||||
| Command | Description |
|
||||
| ----------------- | ---------------------------------------------------------------- |
|
||||
| `bl skill add` | Install skills from the Bailian skill registry into local agents |
|
||||
| `bl skill list` | List registry skills and diff against local installs |
|
||||
| `bl skill remove` | Remove locally installed skills (registry is untouched) |
|
||||
| `bl skill update` | Update installed skills to the latest registry versions |
|
||||
| Command | Description |
|
||||
| ----------------- | ----------------------------------------------------------------------- |
|
||||
| `bl skill add` | Install skills from the Bailian skill registry into local agents |
|
||||
| `bl skill init` | Install all bailian-\* skills (one-shot bootstrap for new environments) |
|
||||
| `bl skill list` | List registry skills and diff against local installs |
|
||||
| `bl skill remove` | Remove locally installed skills (registry is untouched) |
|
||||
| `bl skill update` | Update installed skills to the latest registry versions |
|
||||
|
||||
## Command details
|
||||
|
||||
@@ -22,24 +23,48 @@ Index: [index.md](index.md)
|
||||
| --------------- | ---------------------------------------------------------------- |
|
||||
| **Name** | `skill add` |
|
||||
| **Description** | Install skills from the Bailian skill registry into local agents |
|
||||
| **Usage** | `bl skill add --name <all\|name,...>` |
|
||||
| **Usage** | `bl skill add --all \| --name <name,...>` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------ | ------ | -------- | ----------------------------------------------------- |
|
||||
| `--name <all\|name,...>` | string | yes | Skills to install: all or comma-separated skill names |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------- | ------ | -------- | -------------------------------------- |
|
||||
| `--all` | switch | no | Install all skills from the registry |
|
||||
| `--name <name,...>` | string | no | Comma-separated skill names to install |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl skill add --name all
|
||||
bl skill add --all
|
||||
```
|
||||
|
||||
```bash
|
||||
bl skill add --name spark-video,bailian-model-recommend
|
||||
```
|
||||
|
||||
### `bl skill init`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------------------------------------- |
|
||||
| **Name** | `skill init` |
|
||||
| **Description** | Install all bailian-\* skills (one-shot bootstrap for new environments) |
|
||||
| **Usage** | `bl skill init` |
|
||||
|
||||
#### Flags
|
||||
|
||||
_No command-specific flags._
|
||||
|
||||
#### Notes
|
||||
|
||||
- Fetches the registry index and installs every skill whose name starts with bailian-
|
||||
- Equivalent to: bl skill add --all (filtered to bailian-\* skills)
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl skill init
|
||||
```
|
||||
|
||||
### `bl skill list`
|
||||
|
||||
| Field | Value |
|
||||
@@ -96,13 +121,14 @@ bl skill remove --name all
|
||||
| --------------- | ------------------------------------------------------- |
|
||||
| **Name** | `skill update` |
|
||||
| **Description** | Update installed skills to the latest registry versions |
|
||||
| **Usage** | `bl skill update [--name <all\|name,...>]` |
|
||||
| **Usage** | `bl skill update [--all] [--name <name,...>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| 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) |
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------- | ------ | -------- | ---------------------------------------------------------------------------- |
|
||||
| `--all` | switch | no | Update all installed skills (default when neither --all nor --name is given) |
|
||||
| `--name <name,...>` | string | no | Comma-separated skill names to update (must be already installed) |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -110,6 +136,10 @@ bl skill remove --name all
|
||||
bl skill update
|
||||
```
|
||||
|
||||
```bash
|
||||
bl skill update --all
|
||||
```
|
||||
|
||||
```bash
|
||||
bl skill update --name spark-video
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user