Skip to content

Commit 2c53b06

Browse files
committed
refactor(inventory): 优化技能与代理配置代码格式和检测逻辑
- 统一代码格式,增加多处代码块的换行和缩进保持一致 - 调整技能安装目标列表的格式,提升可读性 - 修复解压缩逻辑中异常抛出格式,增强异常信息规范 - 优化归一化文件名过滤条件表达式格式 - 修改配置文件检测逻辑,兼容环境变量和旧版配置方案 - 增强对 Bailian 相关模型提供者的检测逻辑支持 - 规范代理详情字段生成方法的代码风格 - 调整 MCP 写回相关函数的格式,提升可维护性 - 改进技能和代理详情函数参数格式,统一参数拆分显示 - 修复单元测试中路径和 JSON 写入格式,增加不同配置场景测试覆盖 - 确保软链接技能目录被正确识别为安装来源 - 增加多代理配置文件和技能安装的检测测试用例,提升测试精准度
1 parent adc89f6 commit 2c53b06

2 files changed

Lines changed: 138 additions & 21 deletions

File tree

packages/commands/src/commands/config/inventory.ts

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ function skillRoots(home: string): Array<{ source: string; dir: string }> {
144144
{ source: "openclaw", dir: join(home, ".openclaw", "workspace", "skills") },
145145
{ source: "hermes", dir: join(home, ".hermes", "skills") },
146146
{ source: "gemini", dir: join(home, ".gemini", "skills") },
147-
{ source: "antigravity", dir: join(home, ".gemini", "antigravity", "skills") },
147+
{
148+
source: "antigravity",
149+
dir: join(home, ".gemini", "antigravity", "skills"),
150+
},
148151
{ source: "windsurf", dir: join(home, ".windsurf", "skills") },
149152
{ source: "windsurf", dir: join(home, ".codeium", "windsurf", "skills") },
150153
{ source: "qoderwork", dir: join(home, ".qoderwork", "skills") },
@@ -228,21 +231,43 @@ export function getSkillDetail(id: string, home: string = homedir()): SkillDetai
228231
// ---- Skill install (upload a .zip and unpack it into a skill root) ----
229232

230233
/** Allow-listed skill install targets: source id -> label + path segments. */
231-
const SKILL_INSTALL_TARGETS: Array<{ source: string; label: string; sub: string[] }> = [
232-
{ source: "global", label: "All agents (~/.agents/skills)", sub: [".agents", "skills"] },
234+
const SKILL_INSTALL_TARGETS: Array<{
235+
source: string;
236+
label: string;
237+
sub: string[];
238+
}> = [
239+
{
240+
source: "global",
241+
label: "All agents (~/.agents/skills)",
242+
sub: [".agents", "skills"],
243+
},
233244
{ source: "claude-code", label: "Claude Code", sub: [".claude", "skills"] },
234245
{ source: "qwen-code", label: "Qwen Code", sub: [".qwen", "skills"] },
235246
{ source: "codex", label: "Codex", sub: [".codex", "skills"] },
236-
{ source: "opencode", label: "OpenCode", sub: [".config", "opencode", "skills"] },
247+
{
248+
source: "opencode",
249+
label: "OpenCode",
250+
sub: [".config", "opencode", "skills"],
251+
},
237252
{ source: "openclaw", label: "OpenClaw", sub: [".openclaw", "skills"] },
238253
{ source: "qoderwork", label: "QoderWork", sub: [".qoderwork", "skills"] },
239-
{ source: "windsurf", label: "Windsurf", sub: [".codeium", "windsurf", "skills"] },
254+
{
255+
source: "windsurf",
256+
label: "Windsurf",
257+
sub: [".codeium", "windsurf", "skills"],
258+
},
240259
{ source: "gemini", label: "Gemini", sub: [".gemini", "skills"] },
241260
];
242261

243262
/** The list of install targets exposed to the UI (source + human label). */
244-
export function skillInstallTargets(): Array<{ source: string; label: string }> {
245-
return SKILL_INSTALL_TARGETS.map((t) => ({ source: t.source, label: t.label }));
263+
export function skillInstallTargets(): Array<{
264+
source: string;
265+
label: string;
266+
}> {
267+
return SKILL_INSTALL_TARGETS.map((t) => ({
268+
source: t.source,
269+
label: t.label,
270+
}));
246271
}
247272

248273
function skillInstallRoot(source: string, home: string): string | null {
@@ -511,7 +536,11 @@ function mcpWriteTarget(source: string, scope: string, home: string): McpWriteTa
511536
projectScoped: false,
512537
};
513538
if (source === "cursor")
514-
return { file: join(home, ".cursor", "mcp.json"), mapKey: "mcpServers", projectScoped: false };
539+
return {
540+
file: join(home, ".cursor", "mcp.json"),
541+
mapKey: "mcpServers",
542+
projectScoped: false,
543+
};
515544
if (source === "windsurf")
516545
return {
517546
file: join(home, ".codeium", "windsurf", "mcp_config.json"),
@@ -537,7 +566,11 @@ function mcpWriteTarget(source: string, scope: string, home: string): McpWriteTa
537566
projectScoped: false,
538567
};
539568
if (source === "claude-desktop")
540-
return { file: claudeDesktopConfigPath(home), mapKey: "mcpServers", projectScoped: false };
569+
return {
570+
file: claudeDesktopConfigPath(home),
571+
mapKey: "mcpServers",
572+
projectScoped: false,
573+
};
541574
return null;
542575
}
543576

@@ -643,9 +676,14 @@ const AGENT_PROBES: AgentProbe[] = [
643676
{
644677
id: "claude-code",
645678
label: "Claude Code",
646-
paths: (h) => [join(h, ".claude", "settings.json"), join(h, ".claude.json")],
679+
// The agent writer honors CLAUDE_CONFIG_DIR, so probe the same location.
680+
paths: (h) => [
681+
join(process.env.CLAUDE_CONFIG_DIR || join(h, ".claude"), "settings.json"),
682+
join(h, ".claude.json"),
683+
],
647684
detect: (h) => {
648-
const env = asRecord(readJsonSafe(join(h, ".claude", "settings.json"))?.env);
685+
const configDir = process.env.CLAUDE_CONFIG_DIR || join(h, ".claude");
686+
const env = asRecord(readJsonSafe(join(configDir, "settings.json"))?.env);
649687
const baseUrl =
650688
env && typeof env.ANTHROPIC_BASE_URL === "string" ? env.ANTHROPIC_BASE_URL : undefined;
651689
const model =
@@ -660,9 +698,19 @@ const AGENT_PROBES: AgentProbe[] = [
660698
detect: (h) => {
661699
const settings = readJsonSafe(join(h, ".qwen", "settings.json"));
662700
const providers = asRecord(settings?.modelProviders);
701+
// The writer brands entries either "bailian-cli" (legacy) or with a
702+
// "[Bailian] <model>" display name.
663703
const hasBailian = providers
664704
? Object.values(providers).some(
665-
(list) => Array.isArray(list) && list.some((e) => asRecord(e)?.name === "bailian-cli"),
705+
(list) =>
706+
Array.isArray(list) &&
707+
list.some((entry) => {
708+
const name = asRecord(entry)?.name;
709+
return (
710+
typeof name === "string" &&
711+
(name === "bailian-cli" || name.startsWith("[Bailian]"))
712+
);
713+
}),
666714
)
667715
: false;
668716
const model = asRecord(settings?.model);
@@ -718,10 +766,21 @@ const AGENT_PROBES: AgentProbe[] = [
718766
return { configured: false };
719767
}
720768
const providers = Array.isArray(config?.custom_providers) ? config.custom_providers : [];
721-
const configured = providers.some((p) => asRecord(p)?.name === "bailian-cli");
769+
const legacyConfigured = providers.some(
770+
(provider) => asRecord(provider)?.name === "bailian-cli",
771+
);
772+
// The writer now emits the official flat `model.*` block (provider
773+
// "custom" + a DashScope/Token Plan base_url); keep detecting legacy
774+
// custom_providers entries written by older CLI versions.
722775
const model = asRecord(config?.model);
776+
const flatConfigured = Boolean(
777+
model &&
778+
model.provider === "custom" &&
779+
typeof model.base_url === "string" &&
780+
model.base_url.includes("aliyuncs.com"),
781+
);
723782
return {
724-
configured,
783+
configured: legacyConfigured || flatConfigured,
725784
model: model && typeof model.default === "string" ? model.default : undefined,
726785
};
727786
},
@@ -937,7 +996,11 @@ export function getAgentDetail(id: string, home: string = homedir()): AgentDetai
937996
const settings: AgentSettingsFile[] = installed
938997
? paths
939998
.filter((p) => existsSync(p))
940-
.map((p) => ({ path: p, lang: langForPath(p), text: readText(p) ?? "" }))
999+
.map((p) => ({
1000+
path: p,
1001+
lang: langForPath(p),
1002+
text: readText(p) ?? "",
1003+
}))
9411004
.filter((s) => s.text.trim())
9421005
: [];
9431006
return {

packages/commands/tests/inventory.test.ts

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,27 @@ test("listMcpServers 汇总 codex(toml) 与 claude(json) 的 MCP 定义", () =>
110110
".claude.json",
111111
JSON.stringify({
112112
mcpServers: { web: { url: "https://example.com/mcp", type: "sse" } },
113-
projects: { "/proj": { mcpServers: { local: { command: "python", args: ["s.py"] } } } },
113+
projects: {
114+
"/proj": {
115+
mcpServers: { local: { command: "python", args: ["s.py"] } },
116+
},
117+
},
114118
}),
115119
);
116120

117121
const servers = listMcpServers(home);
118122
const byName = Object.fromEntries(servers.map((s) => [s.name, s]));
119-
expect(byName.repl).toMatchObject({ source: "codex", transport: "stdio", origin: "local" });
123+
expect(byName.repl).toMatchObject({
124+
source: "codex",
125+
transport: "stdio",
126+
origin: "local",
127+
});
120128
expect(byName.repl.detail).toContain("node repl.js");
121-
expect(byName.web).toMatchObject({ source: "claude-code", transport: "sse", scope: "global" });
129+
expect(byName.web).toMatchObject({
130+
source: "claude-code",
131+
transport: "sse",
132+
scope: "global",
133+
});
122134
expect(byName.local).toMatchObject({
123135
source: "claude-code",
124136
transport: "stdio",
@@ -139,10 +151,35 @@ test("listAgents 报告安装与已连接 bailian-cli 的状态", () => {
139151
write(
140152
home,
141153
".claude/settings.json",
142-
JSON.stringify({ env: { ANTHROPIC_BASE_URL: "https://x", ANTHROPIC_MODEL: "qwen3-max" } }),
154+
JSON.stringify({
155+
env: { ANTHROPIC_BASE_URL: "https://x", ANTHROPIC_MODEL: "qwen3-max" },
156+
}),
143157
);
144158
// Codex: installed but NOT configured (no bailian-cli provider).
145159
write(home, ".codex/config.toml", 'model = "gpt-5"\n');
160+
// Qwen Code: configured via the new "[Bailian] <model>" display name.
161+
write(
162+
home,
163+
".qwen/settings.json",
164+
JSON.stringify({
165+
modelProviders: {
166+
openai: [{ id: "qwen3-coder-plus", name: "[Bailian] qwen3-coder-plus" }],
167+
},
168+
model: { name: "qwen3-coder-plus" },
169+
}),
170+
);
171+
// Hermes: configured via the official flat model block (no custom_providers).
172+
write(
173+
home,
174+
".hermes/config.yaml",
175+
[
176+
"model:",
177+
" default: qwen3-max",
178+
" provider: custom",
179+
" base_url: https://dashscope.aliyuncs.com/compatible-mode/v1",
180+
" api_key: sk-test",
181+
].join("\n"),
182+
);
146183

147184
const agents = listAgents(home);
148185
const byId = Object.fromEntries(agents.map((a) => [a.id, a]));
@@ -153,8 +190,25 @@ test("listAgents 报告安装与已连接 bailian-cli 的状态", () => {
153190
model: "qwen3-max",
154191
origin: "local",
155192
});
156-
expect(byId.codex).toMatchObject({ installed: true, configured: false, model: "gpt-5" });
157-
expect(byId.opencode).toMatchObject({ installed: false, configured: false });
193+
expect(byId.codex).toMatchObject({
194+
installed: true,
195+
configured: false,
196+
model: "gpt-5",
197+
});
198+
expect(byId["qwen-code"]).toMatchObject({
199+
installed: true,
200+
configured: true,
201+
model: "qwen3-coder-plus",
202+
});
203+
expect(byId.hermes).toMatchObject({
204+
installed: true,
205+
configured: true,
206+
model: "qwen3-max",
207+
});
208+
expect(byId.opencode).toMatchObject({
209+
installed: false,
210+
configured: false,
211+
});
158212
// Always reports all six known frameworks.
159213
expect(agents).toHaveLength(6);
160214
});

0 commit comments

Comments
 (0)