bfi-cli 0.1.101 → 0.1.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { __exportAll, __toESM } from "./_chunks/rolldown-runtime.mjs";
3
3
  import { isCancel } from "./_chunks/libs/@clack/core.mjs";
4
- import { cancel, confirm, groupMultiselect, intro, log, multiselect as multiselect$1, note, outro, select, spinner } from "./_chunks/libs/@clack/prompts.mjs";
4
+ import { cancel, confirm, groupMultiselect, intro, log, multiselect as multiselect$1, note, outro, password, select, spinner } from "./_chunks/libs/@clack/prompts.mjs";
5
5
  import { require_picocolors } from "./_chunks/libs/picocolors.mjs";
6
6
  import { addSkillToLocalLock, addSkillToLock, computeSkillFolderHash, discoverSkills, dismissPrompt, filterSkills, getAllLockedSkills, getGitHubToken, getLastSelectedAgents, getSkillDisplayName, getSkillFromLock, isPromptDismissed, parseFrontmatter, parseSkillMd, readLocalLock, readSkillLock, removeSkillFromLock, sanitizeMetadata, saveSelectedAgents, stripTerminalEscapes } from "./_chunks/skills.mjs";
7
7
  import { PluginRegistryClient, parsePackageIntent } from "./_chunks/intent.mjs";
@@ -9,25 +9,29 @@ import { loadGlobalConfig, loadLockfile, loadProjectConfig, resolveConfig, saveG
9
9
  import { esm_default } from "./_chunks/libs/simple-git.mjs";
10
10
  import { EVE_SUBAGENTS_DIR, agents, detectInstalledAgents, getEveSubagents, getNonUniversalAgents, getUniversalAgents, getVisibleUniversalAgents, isUniversalAgent } from "./_chunks/agents.mjs";
11
11
  import { packageSkillName } from "./_chunks/package-skill-identity.mjs";
12
+ import { entriesFromJson, mapStrings, normalizeConfig, object, resolveVariables, validName, variablePattern, variables } from "./_chunks/model.mjs";
12
13
  import { createRequire } from "node:module";
13
14
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
14
15
  import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
15
16
  import { fileURLToPath } from "url";
16
17
  import { createHash, randomUUID } from "node:crypto";
17
- import { stripVTControlCharacters } from "node:util";
18
- import fs from "node:fs";
19
- import path$1 from "node:path";
18
+ import { isDeepStrictEqual, stripVTControlCharacters } from "node:util";
19
+ import fs, { constants, existsSync as existsSync$1 } from "node:fs";
20
+ import path$1, { dirname as dirname$1, join as join$1, parse, resolve as resolve$1 } from "node:path";
20
21
  import { homedir, platform, tmpdir } from "os";
21
22
  import * as readline from "readline";
22
23
  import { Writable } from "stream";
23
24
  import https from "node:https";
24
25
  import http from "node:http";
26
+ import { lstat, mkdir, open, rename, unlink } from "node:fs/promises";
25
27
  import zlib, { gunzipSync, inflateRawSync } from "node:zlib";
26
- import os$1 from "node:os";
28
+ import os$1, { homedir as homedir$1 } from "node:os";
27
29
  import { promisify } from "util";
28
30
  import { execFile, spawn, spawnSync } from "child_process";
29
- import { access, chmod, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rename, rm, stat, symlink, writeFile } from "fs/promises";
31
+ import { access as access$1, chmod, cp as cp$1, lstat as lstat$1, mkdir as mkdir$1, mkdtemp, readFile as readFile$1, readdir as readdir$1, readlink, realpath as realpath$1, rename as rename$1, rm as rm$1, stat, symlink, writeFile as writeFile$1 } from "fs/promises";
30
32
  import { determineAgent } from "@vercel/detect-agent";
33
+ import { applyEdits, getNodeValue, modify, parseTree } from "jsonc-parser";
34
+ import { parse as parse$1, stringify } from "smol-toml";
31
35
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
32
36
  const GIT_HOST = process.env.BFI_GIT_HOST || "git.yyrd.com";
33
37
  const GIT_URL = `https://${GIT_HOST}`;
@@ -513,7 +517,7 @@ function installationArgumentError(args) {
513
517
  "--skills-only"
514
518
  ];
515
519
  const found = command.find((argument) => reserved.includes(argument.split("=")[0]));
516
- if (found) return `当前命令不支持 ${found.split("=")[0]};${found.split("=")[0]} 仅用于技能组合完整安装(bfi skills package install)。现有安装不会静默忽略此参数。`;
520
+ if (found) return `当前命令不支持 ${found.split("=")[0]}。安装范围请使用 -g/--global;完整安装请从技能组合入口并显式提供 --content-plan/--from-archive/--operation。现有安装不会静默忽略此参数。`;
517
521
  }
518
522
  var api_exports = /* @__PURE__ */ __exportAll({ SkillHubAPI: () => SkillHubAPI });
519
523
  var SkillHubAPI = class {
@@ -721,6 +725,10 @@ var SkillHubAPI = class {
721
725
  const body = (await this.request("GET", `/namespaces/${namespace}/packages/${slug}`, { headers: { "X-BFI-Capabilities": getCapabilities("").capabilities.join(",") } })).body;
722
726
  return body.data || body;
723
727
  }
728
+ async getPackageMcps(namespace, slug) {
729
+ const body = (await this.request("GET", `/namespaces/${namespace}/packages/${slug}/mcps`)).body;
730
+ return body.data || body;
731
+ }
724
732
  async listComponentFiles(namespace, slug, componentId) {
725
733
  const body = (await this.request("GET", `/namespaces/${namespace}/packages/${slug}/components/${componentId}/files`)).body;
726
734
  return body.data || body;
@@ -1219,11 +1227,11 @@ function createGitClient(extraEnv) {
1219
1227
  }).env(env);
1220
1228
  }
1221
1229
  async function resetTempDir(dir) {
1222
- await rm(dir, {
1230
+ await rm$1(dir, {
1223
1231
  recursive: true,
1224
1232
  force: true
1225
1233
  }).catch(() => {});
1226
- await mkdir(dir, { recursive: true });
1234
+ await mkdir$1(dir, { recursive: true });
1227
1235
  }
1228
1236
  async function tryGhClone(repo, tempDir, ref) {
1229
1237
  let cloneTarget = repo.slug;
@@ -1288,7 +1296,7 @@ async function cloneRepo(url, ref) {
1288
1296
  const isTimeout = errorMessage.includes("block timeout") || errorMessage.includes("timed out");
1289
1297
  const isAuthError = isAuthFailure(errorMessage);
1290
1298
  if (isTimeout) {
1291
- await rm(tempDir, {
1299
+ await rm$1(tempDir, {
1292
1300
  recursive: true,
1293
1301
  force: true
1294
1302
  }).catch(() => {});
@@ -1305,7 +1313,7 @@ async function cloneRepo(url, ref) {
1305
1313
  return tempDir;
1306
1314
  } catch {}
1307
1315
  }
1308
- await rm(tempDir, {
1316
+ await rm$1(tempDir, {
1309
1317
  recursive: true,
1310
1318
  force: true
1311
1319
  }).catch(() => {});
@@ -1317,13 +1325,26 @@ async function cleanupTempDir(dir) {
1317
1325
  const normalizedDir = normalize(resolve(dir));
1318
1326
  const normalizedTmpDir = normalize(resolve(tmpdir()));
1319
1327
  if (!normalizedDir.startsWith(normalizedTmpDir + sep) && normalizedDir !== normalizedTmpDir) throw new Error("Attempted to clean up directory outside of temp directory");
1320
- await rm(dir, {
1328
+ await rm$1(dir, {
1321
1329
  recursive: true,
1322
1330
  force: true
1323
1331
  });
1324
1332
  }
1325
1333
  const AGENTS_DIR = ".agents";
1326
1334
  const SKILLS_SUBDIR = "skills";
1335
+ var installer_exports = /* @__PURE__ */ __exportAll({
1336
+ getAgentBaseDir: () => getAgentBaseDir,
1337
+ getCanonicalPath: () => getCanonicalPath,
1338
+ getCanonicalSkillsDir: () => getCanonicalSkillsDir,
1339
+ getEveSubagentSkillsDir: () => getEveSubagentSkillsDir,
1340
+ getInstallPath: () => getInstallPath,
1341
+ installBlobSkillForAgent: () => installBlobSkillForAgent,
1342
+ installSkillForAgent: () => installSkillForAgent,
1343
+ installWellKnownSkillForAgent: () => installWellKnownSkillForAgent,
1344
+ isSkillInstalled: () => isSkillInstalled,
1345
+ listInstalledSkills: () => listInstalledSkills,
1346
+ sanitizeName: () => sanitizeName
1347
+ });
1327
1348
  function sanitizeName(name) {
1328
1349
  return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
1329
1350
  }
@@ -1368,7 +1389,7 @@ function resolveSymlinkTarget(linkPath, linkTarget) {
1368
1389
  }
1369
1390
  async function cleanAndCreateDirectory(path) {
1370
1391
  try {
1371
- await rm(path, {
1392
+ await rm$1(path, {
1372
1393
  recursive: true,
1373
1394
  force: true
1374
1395
  });
@@ -1383,7 +1404,7 @@ async function classifyPathEntry(path) {
1383
1404
  return (await stat(path)).isDirectory() ? "dir" : "blocker";
1384
1405
  } catch {
1385
1406
  try {
1386
- await lstat(path);
1407
+ await lstat$1(path);
1387
1408
  return "blocker";
1388
1409
  } catch {
1389
1410
  return "missing";
@@ -1403,32 +1424,32 @@ async function findBlockingEntry(path) {
1403
1424
  }
1404
1425
  async function ensureDirectory(path) {
1405
1426
  try {
1406
- await mkdir(path, { recursive: true });
1427
+ await mkdir$1(path, { recursive: true });
1407
1428
  return;
1408
1429
  } catch (err) {
1409
1430
  if (!isErrnoException(err) || err.code !== "ENOTDIR" && err.code !== "EEXIST" && err.code !== "ELOOP" && err.code !== "ENOENT") throw err;
1410
1431
  }
1411
1432
  const blockingEntry = await findBlockingEntry(path);
1412
1433
  if (!blockingEntry) {
1413
- await mkdir(path, { recursive: true });
1434
+ await mkdir$1(path, { recursive: true });
1414
1435
  return;
1415
1436
  }
1416
1437
  const backupPath = `${blockingEntry}.bak-${Date.now()}`;
1417
1438
  try {
1418
- await rename(blockingEntry, backupPath);
1439
+ await rename$1(blockingEntry, backupPath);
1419
1440
  } catch (renameErr) {
1420
1441
  const reason = renameErr instanceof Error ? renameErr.message : String(renameErr);
1421
1442
  throw new Error(`无法创建目录 ${path}:${blockingEntry} 存在但不是可用目录(可能是文件或失效的链接),且自动移开失败(${reason})。请手动删除或重命名该条目后重试。`);
1422
1443
  }
1423
1444
  console.warn(`检测到 ${blockingEntry} 不是可用目录(可能是文件或失效的链接),已重命名为 ${backupPath}`);
1424
- await mkdir(path, { recursive: true });
1445
+ await mkdir$1(path, { recursive: true });
1425
1446
  }
1426
1447
  async function resolveParentSymlinks(path) {
1427
1448
  const resolved = resolve(path);
1428
1449
  const dir = dirname(resolved);
1429
1450
  const base = basename(resolved);
1430
1451
  try {
1431
- const realDir = await realpath(dir);
1452
+ const realDir = await realpath$1(dir);
1432
1453
  return join(realDir, base);
1433
1454
  } catch {
1434
1455
  return resolved;
@@ -1438,21 +1459,21 @@ async function createSymlink(target, linkPath) {
1438
1459
  try {
1439
1460
  const resolvedTarget = resolve(target);
1440
1461
  const resolvedLinkPath = resolve(linkPath);
1441
- const [realTarget, realLinkPath] = await Promise.all([realpath(resolvedTarget).catch(() => resolvedTarget), realpath(resolvedLinkPath).catch(() => resolvedLinkPath)]);
1462
+ const [realTarget, realLinkPath] = await Promise.all([realpath$1(resolvedTarget).catch(() => resolvedTarget), realpath$1(resolvedLinkPath).catch(() => resolvedLinkPath)]);
1442
1463
  if (realTarget === realLinkPath) return true;
1443
1464
  if (await resolveParentSymlinks(target) === await resolveParentSymlinks(linkPath)) return true;
1444
1465
  try {
1445
- if ((await lstat(linkPath)).isSymbolicLink()) {
1466
+ if ((await lstat$1(linkPath)).isSymbolicLink()) {
1446
1467
  if (resolveSymlinkTarget(linkPath, await readlink(linkPath)) === resolvedTarget) return true;
1447
- await rm(linkPath);
1448
- } else await rm(linkPath, { recursive: true });
1468
+ await rm$1(linkPath);
1469
+ } else await rm$1(linkPath, { recursive: true });
1449
1470
  } catch (err) {
1450
1471
  if (err && typeof err === "object" && "code" in err && err.code === "ELOOP") try {
1451
- await rm(linkPath, { force: true });
1472
+ await rm$1(linkPath, { force: true });
1452
1473
  } catch {}
1453
1474
  }
1454
1475
  const linkDir = dirname(linkPath);
1455
- await mkdir(linkDir, { recursive: true });
1476
+ await mkdir$1(linkDir, { recursive: true });
1456
1477
  const realLinkDir = await resolveParentSymlinks(linkDir);
1457
1478
  const relativePath = relative(realLinkDir, target);
1458
1479
  const symlinkType = platform() === "win32" ? "junction" : void 0;
@@ -1585,18 +1606,18 @@ function stripIgnoredEveFrontmatter(raw) {
1585
1606
  return `---\n${keys.map((key) => `${key}: ${JSON.stringify(eveData[key])}`).join("\n")}\n---\n${content.replace(/^\r?\n/u, "")}`;
1586
1607
  }
1587
1608
  async function copyDirectory(src, dest, agentType) {
1588
- await mkdir(dest, { recursive: true });
1589
- const entries = await readdir(src, { withFileTypes: true });
1609
+ await mkdir$1(dest, { recursive: true });
1610
+ const entries = await readdir$1(src, { withFileTypes: true });
1590
1611
  await Promise.all(entries.filter((entry) => !isExcluded$1(entry.name, entry.isDirectory())).map(async (entry) => {
1591
1612
  const srcPath = join(src, entry.name);
1592
1613
  const destPath = join(dest, entry.name);
1593
1614
  if (entry.isDirectory()) await copyDirectory(srcPath, destPath, agentType);
1594
1615
  else try {
1595
1616
  if (agentType === "eve" && entry.name.toLowerCase() === "skill.md") {
1596
- await writeFile(destPath, stripIgnoredEveFrontmatter(await readFile(srcPath, "utf-8")));
1617
+ await writeFile$1(destPath, stripIgnoredEveFrontmatter(await readFile$1(srcPath, "utf-8")));
1597
1618
  return;
1598
1619
  }
1599
- await cp(srcPath, destPath, {
1620
+ await cp$1(srcPath, destPath, {
1600
1621
  dereference: true,
1601
1622
  recursive: true
1602
1623
  });
@@ -1628,7 +1649,7 @@ async function isSkillInstalled(skillName, agentType, options = {}) {
1628
1649
  const skillDir = join(targetBase, sanitized);
1629
1650
  if (!isPathSafe$1(targetBase, skillDir)) return false;
1630
1651
  try {
1631
- await access(skillDir);
1652
+ await access$1(skillDir);
1632
1653
  return true;
1633
1654
  } catch {
1634
1655
  return false;
@@ -1684,8 +1705,8 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1684
1705
  const fullPath = join(targetDir, filePath);
1685
1706
  if (!isPathSafe$1(targetDir, fullPath)) continue;
1686
1707
  const parentDir = dirname(fullPath);
1687
- if (parentDir !== targetDir) await mkdir(parentDir, { recursive: true });
1688
- await writeFile(fullPath, agentType === "eve" && basename(filePath).toLowerCase() === "skill.md" && typeof content === "string" ? stripIgnoredEveFrontmatter(content) : content);
1708
+ if (parentDir !== targetDir) await mkdir$1(parentDir, { recursive: true });
1709
+ await writeFile$1(fullPath, agentType === "eve" && basename(filePath).toLowerCase() === "skill.md" && typeof content === "string" ? stripIgnoredEveFrontmatter(content) : content);
1689
1710
  }
1690
1711
  }
1691
1712
  try {
@@ -1756,11 +1777,11 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
1756
1777
  };
1757
1778
  try {
1758
1779
  await ensureDirectory(agentBase);
1759
- await rm(flatSkillPath, {
1780
+ await rm$1(flatSkillPath, {
1760
1781
  recursive: true,
1761
1782
  force: true
1762
1783
  });
1763
- await writeFile(flatSkillPath, getEveFlatSkillMarkdown(skill.files), "utf-8");
1784
+ await writeFile$1(flatSkillPath, getEveFlatSkillMarkdown(skill.files), "utf-8");
1764
1785
  return {
1765
1786
  success: true,
1766
1787
  path: flatSkillPath,
@@ -1795,8 +1816,8 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
1795
1816
  const fullPath = join(targetDir, file.path);
1796
1817
  if (!isPathSafe$1(targetDir, fullPath)) continue;
1797
1818
  const parentDir = dirname(fullPath);
1798
- if (parentDir !== targetDir) await mkdir(parentDir, { recursive: true });
1799
- await writeFile(fullPath, agentType === "eve" && basename(file.path).toLowerCase() === "skill.md" ? stripIgnoredEveFrontmatter(file.contents) : file.contents, "utf-8");
1819
+ if (parentDir !== targetDir) await mkdir$1(parentDir, { recursive: true });
1820
+ await writeFile$1(fullPath, agentType === "eve" && basename(file.path).toLowerCase() === "skill.md" ? stripIgnoredEveFrontmatter(file.contents) : file.contents, "utf-8");
1800
1821
  }
1801
1822
  }
1802
1823
  try {
@@ -1901,7 +1922,7 @@ async function listInstalledSkills(options = {}) {
1901
1922
  }
1902
1923
  }
1903
1924
  for (const scope of scopes) try {
1904
- const entries = await readdir(scope.path, { withFileTypes: true });
1925
+ const entries = await readdir$1(scope.path, { withFileTypes: true });
1905
1926
  for (const entry of entries) {
1906
1927
  const skillDir = join(scope.path, entry.name);
1907
1928
  if (!await isDirEntryOrSymlinkToDir(entry, skillDir)) continue;
@@ -1945,13 +1966,13 @@ async function listInstalledSkills(options = {}) {
1945
1966
  const agentSkillDir = join(agentBase, possibleName);
1946
1967
  if (!isPathSafe$1(agentBase, agentSkillDir)) continue;
1947
1968
  try {
1948
- await access(agentSkillDir);
1969
+ await access$1(agentSkillDir);
1949
1970
  found = true;
1950
1971
  break;
1951
1972
  } catch {}
1952
1973
  }
1953
1974
  if (!found) try {
1954
- const agentEntries = await readdir(agentBase, { withFileTypes: true });
1975
+ const agentEntries = await readdir$1(agentBase, { withFileTypes: true });
1955
1976
  for (const agentEntry of agentEntries) {
1956
1977
  const candidateDir = join(agentBase, agentEntry.name);
1957
1978
  if (!await isDirEntryOrSymlinkToDir(agentEntry, candidateDir)) continue;
@@ -3107,13 +3128,14 @@ async function selectAgentsInteractive(options) {
3107
3128
  hint: options.global ? agents[a].globalSkillsDir : agents[a].skillsDir
3108
3129
  }));
3109
3130
  let lastSelected = [];
3110
- try {
3131
+ if (options.useMemory !== false) try {
3111
3132
  lastSelected = await getLastSelectedAgents() || [];
3112
3133
  } catch {}
3134
+ const baseSelection = lastSelected.length > 0 ? lastSelected : options.preselectDetected === false ? [] : installedAgents;
3113
3135
  const selected = await searchMultiselect({
3114
3136
  message: "请选择要安装到的 Agent:",
3115
3137
  items: otherChoices,
3116
- initialSelected: [.../* @__PURE__ */ new Set([...installedAgents, ...lastSelected])].filter((a) => otherAgents.includes(a) && !universalAgents.includes(a)),
3138
+ initialSelected: [...new Set(baseSelection)].filter((a) => otherAgents.includes(a) && !universalAgents.includes(a)),
3117
3139
  lockedSection: universalSection
3118
3140
  });
3119
3141
  if (!isCancelled(selected)) try {
@@ -3596,10 +3618,10 @@ async function runAdd(args, options = {}) {
3596
3618
  else if (!process.stdin.isTTY) showInstallTip();
3597
3619
  let tempDir = null;
3598
3620
  try {
3599
- const spinner$3 = spinner();
3600
- spinner$3.start("正在解析源...");
3621
+ const spinner$4 = spinner();
3622
+ spinner$4.start("正在解析源...");
3601
3623
  const parsed = parseSource(source);
3602
- spinner$3.stop(`源:${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
3624
+ spinner$4.stop(`源:${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
3603
3625
  const ownerRepoRaw = getOwnerRepo(parsed);
3604
3626
  const repoPrivacyPromise = (() => {
3605
3627
  if (!ownerRepoRaw) return Promise.resolve(null);
@@ -3608,7 +3630,7 @@ async function runAdd(args, options = {}) {
3608
3630
  return isRepoPrivate(ownerRepo.owner, ownerRepo.repo).catch(() => null);
3609
3631
  })();
3610
3632
  if (parsed.type === "well-known") {
3611
- await handleWellKnownSkills(source, parsed.url, options, spinner$3);
3633
+ await handleWellKnownSkills(source, parsed.url, options, spinner$4);
3612
3634
  return;
3613
3635
  }
3614
3636
  if (parsed.skillFilter) {
@@ -3619,18 +3641,18 @@ async function runAdd(args, options = {}) {
3619
3641
  let skills;
3620
3642
  let blobResult = null;
3621
3643
  if (parsed.type === "skillhub") {
3622
- const result = await installFromSkillHub(parsed, options, spinner$3);
3644
+ const result = await installFromSkillHub(parsed, options, spinner$4);
3623
3645
  skills = result.skills;
3624
3646
  tempDir = result.tempDir;
3625
3647
  } else if (parsed.type === "local") {
3626
- spinner$3.start("正在验证本地路径...");
3648
+ spinner$4.start("正在验证本地路径...");
3627
3649
  if (!existsSync(parsed.localPath)) {
3628
- spinner$3.stop(import_picocolors.default.red("路径未找到"));
3650
+ spinner$4.stop(import_picocolors.default.red("路径未找到"));
3629
3651
  outro(import_picocolors.default.red(`本地路径不存在:${parsed.localPath}`));
3630
3652
  process.exit(1);
3631
3653
  }
3632
- spinner$3.stop("本地路径验证通过");
3633
- spinner$3.start("正在发现技能...");
3654
+ spinner$4.stop("本地路径验证通过");
3655
+ spinner$4.start("正在发现技能...");
3634
3656
  skills = await discoverSkills(parsed.localPath, parsed.subpath, {
3635
3657
  includeInternal,
3636
3658
  fullDepth: options.fullDepth
@@ -3645,7 +3667,7 @@ async function runAdd(args, options = {}) {
3645
3667
  const owner = ownerRepo?.split("/")[0]?.toLowerCase();
3646
3668
  const isSelfHostedRepo = !!ownerRepo && Object.hasOwn(BLOB_ALLOWED_REPOS, ownerRepo.toLowerCase());
3647
3669
  if (ownerRepo && owner && (isSelfHostedRepo || BLOB_ALLOWED_OWNERS.includes(owner))) {
3648
- spinner$3.start("正在获取技能...");
3670
+ spinner$4.start("正在获取技能...");
3649
3671
  blobResult = await tryBlobInstall(ownerRepo, {
3650
3672
  subpath: parsed.subpath,
3651
3673
  skillFilter: parsed.skillFilter,
@@ -3653,36 +3675,36 @@ async function runAdd(args, options = {}) {
3653
3675
  getToken: getGitHubToken,
3654
3676
  includeInternal
3655
3677
  });
3656
- if (!blobResult) spinner$3.stop(import_picocolors.default.dim("Falling back to clone..."));
3678
+ if (!blobResult) spinner$4.stop(import_picocolors.default.dim("Falling back to clone..."));
3657
3679
  }
3658
3680
  if (blobResult) {
3659
3681
  skills = blobResult.skills;
3660
- spinner$3.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
3682
+ spinner$4.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
3661
3683
  } else {
3662
- tempDir = await cloneWithGitHubFallback(parsed.url, options.branch || parsed.ref, spinner$3);
3663
- spinner$3.stop("仓库克隆完成");
3664
- spinner$3.start("正在发现技能...");
3684
+ tempDir = await cloneWithGitHubFallback(parsed.url, options.branch || parsed.ref, spinner$4);
3685
+ spinner$4.stop("仓库克隆完成");
3686
+ spinner$4.start("正在发现技能...");
3665
3687
  skills = await discoverSkills(tempDir, parsed.subpath, {
3666
3688
  includeInternal,
3667
3689
  fullDepth: options.fullDepth
3668
3690
  });
3669
3691
  }
3670
3692
  } else {
3671
- tempDir = await cloneWithGitHubFallback(parsed.url, options.branch || parsed.ref, spinner$3);
3672
- spinner$3.stop("仓库克隆完成");
3673
- spinner$3.start("正在发现技能...");
3693
+ tempDir = await cloneWithGitHubFallback(parsed.url, options.branch || parsed.ref, spinner$4);
3694
+ spinner$4.stop("仓库克隆完成");
3695
+ spinner$4.start("正在发现技能...");
3674
3696
  skills = await discoverSkills(tempDir, parsed.subpath, {
3675
3697
  includeInternal,
3676
3698
  fullDepth: options.fullDepth
3677
3699
  });
3678
3700
  }
3679
3701
  if (skills.length === 0) {
3680
- spinner$3.stop(import_picocolors.default.red("未找到技能"));
3702
+ spinner$4.stop(import_picocolors.default.red("未找到技能"));
3681
3703
  outro(import_picocolors.default.red("未找到有效技能。技能需要包含 name 和 description 的 SKILL.md 文件。"));
3682
3704
  await cleanup(tempDir);
3683
3705
  process.exit(1);
3684
3706
  }
3685
- if (!blobResult) spinner$3.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
3707
+ if (!blobResult) spinner$4.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
3686
3708
  if (options.list) {
3687
3709
  console.log();
3688
3710
  log.step(import_picocolors.default.bold("可用技能"));
@@ -3796,10 +3818,10 @@ async function runAdd(args, options = {}) {
3796
3818
  }
3797
3819
  targetAgents = options.agent;
3798
3820
  } else {
3799
- spinner$3.start("正在加载 Agent...");
3821
+ spinner$4.start("正在加载 Agent...");
3800
3822
  const installedAgents = await detectInstalledAgents();
3801
3823
  const totalAgents = Object.keys(agents).length;
3802
- spinner$3.stop(`${totalAgents} agents`);
3824
+ spinner$4.stop(`${totalAgents} agents`);
3803
3825
  if (installedAgents.includes("eve") && (options.yes || !agentResult.isAgent)) {
3804
3826
  const useEve = options.yes ? true : await confirm({
3805
3827
  message: formatEveInstallPromptMessage(selectedSkills),
@@ -4001,7 +4023,7 @@ async function runAdd(args, options = {}) {
4001
4023
  process.exit(0);
4002
4024
  }
4003
4025
  }
4004
- spinner$3.start("正在安装技能...");
4026
+ spinner$4.start("正在安装技能...");
4005
4027
  const results = [];
4006
4028
  for (const skill of selectedSkills) for (const target of installTargets) {
4007
4029
  const { agent, subagent } = target;
@@ -4038,7 +4060,7 @@ async function runAdd(args, options = {}) {
4038
4060
  ...result
4039
4061
  });
4040
4062
  }
4041
- spinner$3.stop("安装完成");
4063
+ spinner$4.stop("安装完成");
4042
4064
  console.log();
4043
4065
  const successful = results.filter((r) => r.success);
4044
4066
  const failed = results.filter((r) => !r.success);
@@ -4708,12 +4730,12 @@ async function removeCommand(skillNames, options) {
4708
4730
  }
4709
4731
  const isGlobal = options.global ?? false;
4710
4732
  const cwd = process.cwd();
4711
- const spinner$2 = spinner();
4712
- spinner$2.start("正在扫描已安装的技能...");
4733
+ const spinner$3 = spinner();
4734
+ spinner$3.start("正在扫描已安装的技能...");
4713
4735
  const skillNamesSet = /* @__PURE__ */ new Set();
4714
4736
  const scanDir = async (dir) => {
4715
4737
  try {
4716
- const entries = await readdir(dir, { withFileTypes: true });
4738
+ const entries = await readdir$1(dir, { withFileTypes: true });
4717
4739
  for (const entry of entries) if (entry.isDirectory()) skillNamesSet.add(entry.name);
4718
4740
  } catch (err) {
4719
4741
  if (err instanceof Error && err.code !== "ENOENT") log.warn(`Could not scan directory ${dir}: ${err.message}`);
@@ -4728,7 +4750,7 @@ async function removeCommand(skillNames, options) {
4728
4750
  for (const subagent of getEveSubagents(cwd)) await scanDir(getEveSubagentSkillsDir(subagent, cwd));
4729
4751
  }
4730
4752
  const installedSkills = Array.from(skillNamesSet).sort();
4731
- spinner$2.stop(`发现 ${installedSkills.length} 个已安装技能`);
4753
+ spinner$3.stop(`发现 ${installedSkills.length} 个已安装技能`);
4732
4754
  if (installedSkills.length === 0) {
4733
4755
  outro(import_picocolors.default.yellow("没有可卸载的技能。"));
4734
4756
  return { removed: [] };
@@ -4770,7 +4792,7 @@ async function removeCommand(skillNames, options) {
4770
4792
  if (options.agent && options.agent.length > 0) targetAgents = options.agent;
4771
4793
  else {
4772
4794
  targetAgents = Object.keys(agents);
4773
- spinner$2.stop(`Targeting ${targetAgents.length} potential agent(s)`);
4795
+ spinner$3.stop(`Targeting ${targetAgents.length} potential agent(s)`);
4774
4796
  }
4775
4797
  const { assertLegacyUnmanaged } = await import("./_chunks/ownership.mjs");
4776
4798
  await assertLegacyUnmanaged(selectedSkills.flatMap((skill) => [getCanonicalPath(skill, {
@@ -4791,7 +4813,7 @@ async function removeCommand(skillNames, options) {
4791
4813
  process.exit(0);
4792
4814
  }
4793
4815
  }
4794
- spinner$2.start("正在卸载技能...");
4816
+ spinner$3.start("正在卸载技能...");
4795
4817
  const results = [];
4796
4818
  for (const skillName of selectedSkills) try {
4797
4819
  const canonicalPath = getCanonicalPath(skillName, {
@@ -4814,7 +4836,7 @@ async function removeCommand(skillNames, options) {
4814
4836
  for (const pathToCleanup of pathsToCleanup) {
4815
4837
  if (pathToCleanup === canonicalPath) continue;
4816
4838
  try {
4817
- if (await lstat(pathToCleanup).catch(() => null)) await rm(pathToCleanup, {
4839
+ if (await lstat$1(pathToCleanup).catch(() => null)) await rm$1(pathToCleanup, {
4818
4840
  recursive: true,
4819
4841
  force: true
4820
4842
  });
@@ -4831,12 +4853,12 @@ async function removeCommand(skillNames, options) {
4831
4853
  global: isGlobal,
4832
4854
  cwd
4833
4855
  });
4834
- if (await lstat(path).catch(() => null)) {
4856
+ if (await lstat$1(path).catch(() => null)) {
4835
4857
  isStillUsed = true;
4836
4858
  break;
4837
4859
  }
4838
4860
  }
4839
- if (!isStillUsed) await rm(canonicalPath, {
4861
+ if (!isStillUsed) await rm$1(canonicalPath, {
4840
4862
  recursive: true,
4841
4863
  force: true
4842
4864
  });
@@ -4857,7 +4879,7 @@ async function removeCommand(skillNames, options) {
4857
4879
  error: err instanceof Error ? err.message : String(err)
4858
4880
  });
4859
4881
  }
4860
- spinner$2.stop("卸载完成");
4882
+ spinner$3.stop("卸载完成");
4861
4883
  const successful = results.filter((r) => r.success);
4862
4884
  const failed = results.filter((r) => !r.success);
4863
4885
  if (successful.length > 0) {
@@ -5579,14 +5601,14 @@ async function materializeUseSkill(skill) {
5579
5601
  const tempRoot = await mkdtemp(join(tmpdir(), "skills-use-"));
5580
5602
  const skillDir = join(tempRoot, sanitizeName(skill.directoryName || skill.name));
5581
5603
  if (!isPathSafe(tempRoot, skillDir)) throw new Error("Invalid skill name: potential path traversal detected");
5582
- await mkdir(skillDir, { recursive: true });
5604
+ await mkdir$1(skillDir, { recursive: true });
5583
5605
  if (skill.kind === "blob") await writeSnapshotFiles(skillDir, skill.files);
5584
5606
  else if (skill.kind === "well-known") await writeMapFiles(skillDir, skill.files);
5585
5607
  else await copySkillDirectory(skill.path, skillDir);
5586
5608
  return {
5587
5609
  tempRoot,
5588
5610
  skillDir,
5589
- skillMd: skill.rawContent ?? await readFile(join(skillDir, "SKILL.md"), "utf-8"),
5611
+ skillMd: skill.rawContent ?? await readFile$1(join(skillDir, "SKILL.md"), "utf-8"),
5590
5612
  hasSupportingFiles: await containsSupportingFiles(skillDir, skillDir)
5591
5613
  };
5592
5614
  }
@@ -5802,13 +5824,13 @@ async function writeMapFiles(targetDir, files) {
5802
5824
  async function writeSafeFile(targetDir, filePath, contents) {
5803
5825
  const fullPath = join(targetDir, filePath);
5804
5826
  if (!isPathSafe(targetDir, fullPath)) return;
5805
- await mkdir(dirname(fullPath), { recursive: true });
5806
- if (typeof contents === "string") await writeFile(fullPath, contents, "utf-8");
5807
- else await writeFile(fullPath, contents);
5827
+ await mkdir$1(dirname(fullPath), { recursive: true });
5828
+ if (typeof contents === "string") await writeFile$1(fullPath, contents, "utf-8");
5829
+ else await writeFile$1(fullPath, contents);
5808
5830
  }
5809
5831
  async function copySkillDirectory(src, dest) {
5810
- await mkdir(dest, { recursive: true });
5811
- const entries = await readdir(src, { withFileTypes: true });
5832
+ await mkdir$1(dest, { recursive: true });
5833
+ const entries = await readdir$1(src, { withFileTypes: true });
5812
5834
  await Promise.all(entries.filter((entry) => !isExcluded(entry.name, entry.isDirectory())).map(async (entry) => {
5813
5835
  const srcPath = join(src, entry.name);
5814
5836
  const destPath = join(dest, entry.name);
@@ -5818,7 +5840,7 @@ async function copySkillDirectory(src, dest) {
5818
5840
  return;
5819
5841
  }
5820
5842
  try {
5821
- await cp(srcPath, destPath, {
5843
+ await cp$1(srcPath, destPath, {
5822
5844
  dereference: true,
5823
5845
  recursive: true
5824
5846
  });
@@ -5832,7 +5854,7 @@ async function copySkillDirectory(src, dest) {
5832
5854
  }));
5833
5855
  }
5834
5856
  async function containsSupportingFiles(rootDir, currentDir) {
5835
- const entries = await readdir(currentDir, { withFileTypes: true });
5857
+ const entries = await readdir$1(currentDir, { withFileTypes: true });
5836
5858
  for (const entry of entries) {
5837
5859
  const entryPath = join(currentDir, entry.name);
5838
5860
  const relPath = relative(rootDir, entryPath).split(sep).join("/");
@@ -6962,6 +6984,517 @@ function maybePrintAnalyticsNotice(env = process.env) {
6962
6984
  console.log();
6963
6985
  } catch {}
6964
6986
  }
6987
+ function checkNode(node) {
6988
+ if (node.type === "object") {
6989
+ const keys = (node.children ?? []).map((child) => String(child.children?.[0]?.value));
6990
+ if (keys.length !== new Set(keys).size || keys.some((key) => [
6991
+ "__proto__",
6992
+ "prototype",
6993
+ "constructor"
6994
+ ].includes(key))) throw new Error("配置包含重复键或不安全键,拒绝修改。");
6995
+ }
6996
+ node.children?.forEach(checkNode);
6997
+ }
6998
+ function readDocument(text, format) {
6999
+ if (!text.trim()) return {};
7000
+ try {
7001
+ if (format === "toml") return object(parse$1(text));
7002
+ const errors = [];
7003
+ const tree = parseTree(text, errors, {
7004
+ allowTrailingComma: true,
7005
+ disallowComments: false
7006
+ });
7007
+ if (!tree || errors.length) throw new Error();
7008
+ checkNode(tree);
7009
+ return object(JSON.parse(JSON.stringify(getNodeValue(tree))));
7010
+ } catch {
7011
+ throw new Error("现有配置解析失败,未修改任何内容。请先修复配置格式或重复键。");
7012
+ }
7013
+ }
7014
+ function serversIn(document, path) {
7015
+ let current = document;
7016
+ for (const key of path) {
7017
+ if (current[key] === void 0) return {};
7018
+ current = object(current[key], "MCP 配置节点");
7019
+ }
7020
+ return current;
7021
+ }
7022
+ function tablePath(line) {
7023
+ if (!/^\s*\[[^[]/.test(line)) return;
7024
+ try {
7025
+ let value = parse$1(`${line}\n__bfi_table_marker__ = true\n`);
7026
+ const path = [];
7027
+ while (value && typeof value === "object") {
7028
+ const entries = Object.entries(value);
7029
+ if (entries.length !== 1) return;
7030
+ const [key, next] = entries[0];
7031
+ if (key === "__bfi_table_marker__") return path;
7032
+ path.push(key);
7033
+ value = next;
7034
+ }
7035
+ } catch {
7036
+ return;
7037
+ }
7038
+ }
7039
+ function editToml(text, replacements) {
7040
+ const lines = text.split(/(?<=\n)/);
7041
+ const starts = [];
7042
+ let offset = 0;
7043
+ for (const line of lines) {
7044
+ if (/^\s*\[/.test(line)) starts.push({
7045
+ offset,
7046
+ path: tablePath(line.trim())
7047
+ });
7048
+ offset += line.length;
7049
+ }
7050
+ const existing = serversIn(readDocument(text, "toml"), ["mcp_servers"]);
7051
+ const ranges = [];
7052
+ for (const name of Object.keys(replacements)) {
7053
+ const matches = starts.flatMap((start, index) => start.path?.[0] === "mcp_servers" && start.path[1] === name ? [[start.offset, starts[index + 1]?.offset ?? text.length]] : []);
7054
+ if (existing[name] !== void 0 && !matches.length) throw new Error("目标 MCP 使用内联或复杂 TOML 定义,无法安全增量更新;请先改为独立 [mcp_servers.\"name\"] 表。");
7055
+ ranges.push(...matches);
7056
+ }
7057
+ let result = text;
7058
+ for (const [start, end] of ranges.sort((left, right) => right[0] - left[0])) result = result.slice(0, start) + result.slice(end);
7059
+ for (const [name, config] of Object.entries(replacements)) if (config !== void 0) result += `${result.endsWith("\n") || !result ? "" : "\n"}\n${stringify({ mcp_servers: { [name]: config } }).replace(/^\[mcp_servers\]\r?\n/m, "")}`;
7060
+ if (!readDocument(result, "toml").mcp_servers) result += "\n[mcp_servers]\n";
7061
+ return result;
7062
+ }
7063
+ function editDocument(text, format, path, replacements) {
7064
+ serversIn(readDocument(text, format), path);
7065
+ const expected = readDocument(text, format);
7066
+ let parent = expected;
7067
+ for (const key of path) parent = object(parent[key] ?? (parent[key] = {}));
7068
+ for (const [name, value] of Object.entries(replacements)) if (value === void 0) delete parent[name];
7069
+ else parent[name] = value;
7070
+ let result = text.trim() ? text : format === "jsonc" ? "{}\n" : "";
7071
+ if (format === "toml") result = editToml(result, replacements);
7072
+ else for (const [name, value] of Object.entries(replacements)) result = applyEdits(result, modify(result, [...path, name], value, { formattingOptions: {
7073
+ insertSpaces: true,
7074
+ tabSize: 2,
7075
+ eol: text.includes("\r\n") ? "\r\n" : "\n"
7076
+ } }));
7077
+ if (!isDeepStrictEqual(readDocument(result, format), expected)) throw new Error("配置保留校验失败,未写入;请使用客户端原生配置处理复杂结构。");
7078
+ return result;
7079
+ }
7080
+ const agentIds = [
7081
+ "claude-code",
7082
+ "codex",
7083
+ "cursor",
7084
+ "qwen-code",
7085
+ "gemini-cli",
7086
+ "zcode",
7087
+ "codebuddy",
7088
+ "kimi-code",
7089
+ "opencode"
7090
+ ];
7091
+ const aliases = {
7092
+ claude: "claude-code",
7093
+ qwen: "qwen-code",
7094
+ gemini: "gemini-cli",
7095
+ kimi: "kimi-code",
7096
+ "kimi-cli": "kimi-code",
7097
+ "codebuddy-code": "codebuddy"
7098
+ };
7099
+ function preferred(paths) {
7100
+ return paths.find((filename) => existsSync$1(filename)) ?? paths[0];
7101
+ }
7102
+ function targetFor(name, scope = "user", options = {}) {
7103
+ const agent = aliases[name] ?? name;
7104
+ if (!agentIds.includes(agent)) throw new Error(`尚未支持该 Agent 的自动配置。支持:${agentIds.join(", ")}。WorkBuddy/IDE 专有配置请使用原生导入,不猜测路径。`);
7105
+ const home = options.home ?? homedir$1();
7106
+ const cwd = options.cwd ?? process.cwd();
7107
+ const env = options.env ?? process.env;
7108
+ const user = scope === "user";
7109
+ const paths = {
7110
+ "claude-code": [user ? join$1(home, ".claude.json") : join$1(cwd, ".mcp.json")],
7111
+ codex: [user ? join$1(env.CODEX_HOME ?? join$1(home, ".codex"), "config.toml") : join$1(cwd, ".codex/config.toml")],
7112
+ cursor: [join$1(user ? home : cwd, ".cursor/mcp.json")],
7113
+ "qwen-code": [join$1(user ? home : cwd, ".qwen/settings.json")],
7114
+ "gemini-cli": [join$1(user ? home : cwd, ".gemini/settings.json")],
7115
+ zcode: [join$1(user ? home : cwd, user ? ".zcode/cli/config.json" : ".zcode/config.json")],
7116
+ codebuddy: user ? [
7117
+ join$1(home, ".codebuddy/.mcp.json"),
7118
+ join$1(home, ".codebuddy/mcp.json"),
7119
+ join$1(home, ".codebuddy.json")
7120
+ ] : [join$1(cwd, ".mcp.json"), join$1(cwd, "mcp.json")],
7121
+ "kimi-code": [join$1(user ? env.KIMI_CODE_HOME ?? join$1(home, ".kimi-code") : join$1(cwd, ".kimi-code"), "mcp.json")],
7122
+ opencode: [join$1(user ? env.XDG_CONFIG_HOME ? join$1(env.XDG_CONFIG_HOME, "opencode") : join$1(home, ".config/opencode") : cwd, "opencode.jsonc"), join$1(user ? env.XDG_CONFIG_HOME ? join$1(env.XDG_CONFIG_HOME, "opencode") : join$1(home, ".config/opencode") : cwd, "opencode.json")]
7123
+ };
7124
+ if (agent === "claude-code" && env.CLAUDE_CONFIG_DIR && !options.config) throw new Error("设置了 CLAUDE_CONFIG_DIR;请用 --config 明确 MCP 文件,避免改错作用域。");
7125
+ if (agent === "kimi-code" && user && !env.KIMI_CODE_HOME && !existsSync$1(join$1(home, ".kimi-code")) && existsSync$1(join$1(home, ".kimi/mcp.json")) && !options.config) throw new Error("检测到旧版 Kimi 配置,请核对版本后用 --config 明确文件;本适配器面向 .kimi-code 布局。");
7126
+ return {
7127
+ agent,
7128
+ scope,
7129
+ path: resolve$1(options.config ?? preferred(paths[agent])),
7130
+ format: agent === "codex" ? "toml" : "jsonc",
7131
+ key: agent === "codex" ? ["mcp_servers"] : agent === "opencode" ? ["mcp"] : agent === "zcode" ? ["mcp", "servers"] : ["mcpServers"]
7132
+ };
7133
+ }
7134
+ function hasLiteralSecrets(config) {
7135
+ const literal = (value) => !/^Bearer \$\{(?:user_config\.)?[A-Za-z_][A-Za-z0-9_]*\}$/i.test(value) && Boolean(value.replace(variablePattern, (_whole, _name, fallback) => fallback ?? "").trim());
7136
+ const sensitive = /token|secret|password|api.?key|authorization|credential|private.?key|x-pm-(ak|sk)/i;
7137
+ if (Object.entries(config.headers ?? {}).some(([key, value]) => !/^(accept|content-type|user-agent|x-pm-env|x-pm-product-line)$/i.test(key) && literal(value))) return true;
7138
+ if (Object.entries(config.env ?? {}).some(([key, value]) => sensitive.test(key) && literal(value))) return true;
7139
+ if ((config.args ?? []).some((value, index, args) => /^--?(token|password|secret|api-key)(=|$)/i.test(value) && literal(value.includes("=") ? value.split("=").slice(1).join("=") : args[index + 1] ?? ""))) return true;
7140
+ if (config.url) {
7141
+ if ([...new URL(config.url.replace(variablePattern, "__BFI_VARIABLE__")).searchParams].some(([key, value]) => sensitive.test(key) && value.replaceAll("__BFI_VARIABLE__", ""))) return true;
7142
+ }
7143
+ return false;
7144
+ }
7145
+ function encodeServer(config, target, bindings = {}, materialize = false, environment = process.env) {
7146
+ if (config.cwd && ![
7147
+ "codex",
7148
+ "qwen-code",
7149
+ "gemini-cli",
7150
+ "kimi-code"
7151
+ ].includes(target.agent)) throw new Error("此宿主的 MCP cwd 字段未支持或未确证;请提供不依赖 cwd 的启动配置。");
7152
+ if (Object.values(bindings).some((name) => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))) throw new Error("环境变量绑定名称无效。");
7153
+ if (materialize && target.scope !== "user") throw new Error("凭据物化仅允许用户级配置,项目级配置只能保存引用。");
7154
+ if (hasLiteralSecrets(config) && !materialize) throw new Error("源配置可能含明文凭据;请改为环境变量占位符,或显式选择用户级 --materialize-inputs。");
7155
+ let value = materialize ? resolveVariables(config, bindings, environment) : config;
7156
+ if (variables(value).length && ["zcode", "kimi-code"].includes(target.agent)) throw new Error("该宿主的通用变量插值未确证。请使用用户级 --materialize-inputs,或原生秘密配置;不会保存无效占位符。");
7157
+ if (!materialize && target.agent !== "codex") value = mapStrings(value, (text) => text.replace(variablePattern, (_whole, key, fallback) => {
7158
+ const name = bindings[key] ?? key;
7159
+ if (fallback !== void 0 && !["claude-code", "codebuddy"].includes(target.agent)) throw new Error("此宿主不支持带默认值的变量引用。");
7160
+ if (target.agent === "cursor") return `\${env:${name}}`;
7161
+ if (target.agent === "opencode") return `{env:${name}}`;
7162
+ return `\${${name}${fallback === void 0 ? "" : `:-${fallback}`}}`;
7163
+ }));
7164
+ const { type, ...rest } = value;
7165
+ if (target.agent === "codex") {
7166
+ if (type === "sse") throw new Error("Codex 不支持旧 SSE 配置;请提供 Streamable HTTP 服务。");
7167
+ const result = {};
7168
+ if (type === "stdio") {
7169
+ result.command = value.command;
7170
+ if (value.args) result.args = value.args;
7171
+ if (value.cwd) result.cwd = value.cwd;
7172
+ const env = {};
7173
+ const inherited = [];
7174
+ for (const [name, text] of Object.entries(value.env ?? {})) {
7175
+ const match = /^\$\{(?:user_config\.)?([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(text);
7176
+ if (match && (bindings[match[1]] ?? match[1]) === name) inherited.push(name);
7177
+ else if (text.includes("${")) throw new Error("Codex env_vars 不能重命名或拼接变量;请使用同名环境变量或用户级物化。");
7178
+ else env[name] = text;
7179
+ }
7180
+ if (Object.keys(env).length) result.env = env;
7181
+ if (inherited.length) result.env_vars = inherited;
7182
+ if ([
7183
+ value.command,
7184
+ value.cwd,
7185
+ ...value.args ?? []
7186
+ ].some((item) => item?.includes("${"))) throw new Error("Codex command/args/cwd 不支持变量插值。");
7187
+ } else {
7188
+ result.url = value.url;
7189
+ if (value.url?.includes("${")) throw new Error("Codex URL 不支持变量插值。");
7190
+ const literal = {};
7191
+ const inherited = {};
7192
+ for (const [header, text] of Object.entries(value.headers ?? {})) {
7193
+ const match = /^\$\{(?:user_config\.)?([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(text);
7194
+ const bearer = /^Bearer \$\{(?:user_config\.)?([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(text);
7195
+ if (match) inherited[header] = bindings[match[1]] ?? match[1];
7196
+ else if (header.toLowerCase() === "authorization" && bearer) result.bearer_token_env_var = bindings[bearer[1]] ?? bearer[1];
7197
+ else if (text.includes("${")) throw new Error("Codex header 只支持整值环境引用或 Bearer 引用,不支持拼接。");
7198
+ else literal[header] = text;
7199
+ }
7200
+ if (Object.keys(literal).length) result.http_headers = literal;
7201
+ if (Object.keys(inherited).length) result.env_http_headers = inherited;
7202
+ }
7203
+ return result;
7204
+ }
7205
+ if (target.agent === "opencode") {
7206
+ if (value.cwd) throw new Error("OpenCode MCP 无独立 cwd 字段;请提供绝对启动路径。");
7207
+ return type === "stdio" ? {
7208
+ type: "local",
7209
+ command: [value.command, ...value.args ?? []],
7210
+ ...value.env ? { environment: value.env } : {}
7211
+ } : {
7212
+ type: "remote",
7213
+ url: value.url,
7214
+ ...value.headers ? { headers: value.headers } : {}
7215
+ };
7216
+ }
7217
+ if (["qwen-code", "gemini-cli"].includes(target.agent)) {
7218
+ const { url, ...fields } = rest;
7219
+ return {
7220
+ ...fields,
7221
+ ...url ? { [type === "http" ? "httpUrl" : "url"]: url } : {}
7222
+ };
7223
+ }
7224
+ if (target.agent === "cursor") return rest;
7225
+ if (target.agent === "kimi-code") return {
7226
+ ...rest,
7227
+ ...type === "sse" ? { transport: "sse" } : {}
7228
+ };
7229
+ return {
7230
+ type,
7231
+ ...rest
7232
+ };
7233
+ }
7234
+ const managedKeys = /* @__PURE__ */ new Set([
7235
+ "type",
7236
+ "transport",
7237
+ "command",
7238
+ "args",
7239
+ "env",
7240
+ "environment",
7241
+ "cwd",
7242
+ "url",
7243
+ "httpUrl",
7244
+ "headers",
7245
+ "http_headers",
7246
+ "env_http_headers",
7247
+ "env_vars",
7248
+ "bearer_token_env_var"
7249
+ ]);
7250
+ function mergeServer(existing, desired) {
7251
+ if (existing === void 0) return desired;
7252
+ const previous = object(existing, "已有 MCP");
7253
+ const extras = Object.fromEntries(Object.entries(previous).filter(([key]) => !managedKeys.has(key)));
7254
+ if ([
7255
+ "oauth",
7256
+ "auth",
7257
+ "headersHelper",
7258
+ "http_headers_helper",
7259
+ "bearerTokenEnvVar",
7260
+ "bearer_token"
7261
+ ].some((key) => extras[key] !== void 0) && [
7262
+ "url",
7263
+ "httpUrl",
7264
+ "command",
7265
+ "type",
7266
+ "transport"
7267
+ ].some((key) => previous[key] !== desired[key])) throw new Error("已有服务器包含宿主专有认证设置,不能安全更换启动目标;请在客户端中处理。");
7268
+ return {
7269
+ ...extras,
7270
+ ...desired
7271
+ };
7272
+ }
7273
+ function decodeServer(raw, target) {
7274
+ const source = object(raw, "MCP");
7275
+ if (source.cwd && ![
7276
+ "codex",
7277
+ "qwen-code",
7278
+ "gemini-cli",
7279
+ "kimi-code"
7280
+ ].includes(target.agent)) throw new Error("此宿主 cwd 语义未确证,请使用客户端原生测试。");
7281
+ if (source.enabled === false || source.disabled === true) throw new Error("此 MCP 已在客户端停用;测试不会绕过停用设置。");
7282
+ if ([
7283
+ "oauth",
7284
+ "auth",
7285
+ "headersHelper",
7286
+ "http_headers_helper",
7287
+ "bearer_token",
7288
+ "bearerTokenEnvVar"
7289
+ ].some((key) => source[key] !== void 0)) throw new Error("此服务器使用宿主专有认证;请用客户端原生连接测试。");
7290
+ let result = Object.fromEntries(Object.entries(source).filter(([key]) => managedKeys.has(key)));
7291
+ if (target.agent === "codex") {
7292
+ const { env_vars, env_http_headers, http_headers, bearer_token_env_var, ...rest } = result;
7293
+ result = rest;
7294
+ if (source.command) {
7295
+ const env = { ...object(source.env ?? {}) };
7296
+ if (env_vars !== void 0 && (!Array.isArray(env_vars) || !env_vars.every((item) => typeof item === "string"))) throw new Error("不支持的 Codex env_vars 结构。");
7297
+ for (const name of env_vars ?? []) env[name] = `\${${name}}`;
7298
+ if (Object.keys(env).length) result.env = env;
7299
+ } else {
7300
+ const headers = { ...object(http_headers ?? {}) };
7301
+ for (const [name, variable] of Object.entries(object(env_http_headers ?? {}))) {
7302
+ if (typeof variable !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(variable)) throw new Error("无效环境变量引用。");
7303
+ headers[name] = `\${${variable}}`;
7304
+ }
7305
+ if (typeof bearer_token_env_var === "string") headers.Authorization = `Bearer \${${bearer_token_env_var}}`;
7306
+ if (Object.keys(headers).length) result.headers = headers;
7307
+ }
7308
+ } else if (target.agent === "opencode") {
7309
+ if (source.type === "local") {
7310
+ if (!Array.isArray(source.command) || !source.command.length) throw new Error("OpenCode command 必须为非空数组。");
7311
+ result = {
7312
+ type: "stdio",
7313
+ command: source.command[0],
7314
+ args: source.command.slice(1),
7315
+ ...source.environment ? { env: source.environment } : {}
7316
+ };
7317
+ } else result = {
7318
+ type: "http",
7319
+ url: source.url,
7320
+ ...source.headers ? { headers: source.headers } : {}
7321
+ };
7322
+ result = mapStrings(result, (text) => text.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, "${$1}"));
7323
+ } else if (["qwen-code", "gemini-cli"].includes(target.agent)) {
7324
+ const { httpUrl, ...rest } = result;
7325
+ result = {
7326
+ ...rest,
7327
+ ...httpUrl ? {
7328
+ type: "http",
7329
+ url: httpUrl
7330
+ } : source.url ? { type: "sse" } : {}
7331
+ };
7332
+ } else if (target.agent === "cursor") result = mapStrings(result, (text) => text.replace(/\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, "${$1}"));
7333
+ return normalizeConfig(result);
7334
+ }
7335
+ async function checkPath(filename) {
7336
+ let current = resolve$1(filename);
7337
+ while (current !== parse(current).root) {
7338
+ try {
7339
+ const stat = await lstat(current);
7340
+ if (stat.isSymbolicLink() || current === resolve$1(filename) && (!stat.isFile() || stat.nlink > 1)) throw new Error("配置路径含符号链接、特殊文件或硬链接,拒绝写入。");
7341
+ } catch (error) {
7342
+ if (error.code !== "ENOENT") throw error;
7343
+ }
7344
+ current = dirname$1(current);
7345
+ }
7346
+ }
7347
+ async function readConfig(filename) {
7348
+ await checkPath(filename);
7349
+ try {
7350
+ const handle = await open(filename, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
7351
+ try {
7352
+ const stat = await handle.stat();
7353
+ if (!stat.isFile() || stat.size > 2097152) throw new Error("配置文件不是普通文件或超过 2 MiB。");
7354
+ return await handle.readFile("utf8");
7355
+ } finally {
7356
+ await handle.close();
7357
+ }
7358
+ } catch (error) {
7359
+ if (error.code === "ENOENT") return;
7360
+ throw error;
7361
+ }
7362
+ }
7363
+ async function identity(filename) {
7364
+ try {
7365
+ const stat = await lstat(filename, { bigint: true });
7366
+ return `${stat.dev}:${stat.ino}:${stat.mode}:${stat.mtimeNs}:${stat.size}`;
7367
+ } catch (error) {
7368
+ if (error.code === "ENOENT") return;
7369
+ throw error;
7370
+ }
7371
+ }
7372
+ async function writeConfig(filename, previous, next, expectedIdentity) {
7373
+ if (previous === next) return;
7374
+ await checkPath(filename);
7375
+ const originalIdentity = expectedIdentity ?? await identity(filename);
7376
+ if (previous !== void 0 && ((await lstat(filename)).mode & 128) === 0) throw new Error("配置文件只读,不放宽其权限。");
7377
+ await mkdir(dirname$1(filename), {
7378
+ recursive: true,
7379
+ mode: 448
7380
+ });
7381
+ const lockPath = `${filename}.bfi-mcp.lock`;
7382
+ const temporary = `${filename}.bfi-mcp-${randomUUID()}.tmp`;
7383
+ const backup = previous === void 0 ? void 0 : `${filename}.bfi-mcp-${Date.now()}-${randomUUID()}.bak`;
7384
+ let lock;
7385
+ try {
7386
+ lock = await open(lockPath, "wx", 384);
7387
+ } catch {
7388
+ throw new Error("无法取得配置写入锁。可能有其它安装正在进行;请核对残留 .bfi-mcp.lock 后重试。");
7389
+ }
7390
+ try {
7391
+ await checkPath(filename);
7392
+ if (await readConfig(filename) !== previous || await identity(filename) !== originalIdentity) throw new Error("配置已被其它进程修改,请重新预览并重试。");
7393
+ if (backup) {
7394
+ const handle = await open(backup, "wx", 384);
7395
+ try {
7396
+ await handle.writeFile(previous, "utf8");
7397
+ await handle.sync();
7398
+ } finally {
7399
+ await handle.close();
7400
+ }
7401
+ }
7402
+ const handle = await open(temporary, "wx", 384);
7403
+ try {
7404
+ await handle.writeFile(next, "utf8");
7405
+ await handle.sync();
7406
+ } finally {
7407
+ await handle.close();
7408
+ }
7409
+ await checkPath(filename);
7410
+ if (await readConfig(filename) !== previous || await identity(filename) !== originalIdentity) throw new Error("写入前检测到并发修改,已取消。");
7411
+ await rename(temporary, filename);
7412
+ return backup;
7413
+ } finally {
7414
+ await unlink(temporary).catch(() => {});
7415
+ await lock.close().catch(() => {});
7416
+ await unlink(lockPath).catch(() => {});
7417
+ }
7418
+ }
7419
+ async function loadTarget(target) {
7420
+ const fileIdentity = await identity(target.path);
7421
+ const text = await readConfig(target.path);
7422
+ if (await identity(target.path) !== fileIdentity) throw new Error("读取时配置发生变化,请重试。");
7423
+ return {
7424
+ text,
7425
+ fileIdentity,
7426
+ servers: serversIn(readDocument(text ?? "", target.format), target.key)
7427
+ };
7428
+ }
7429
+ async function planInstall(target, entries, options = {}) {
7430
+ if (!entries.length || entries.length > 100) throw new Error("每次需要选择 1–100 个 MCP。");
7431
+ const names = entries.map((entry) => validName(entry.registrationName));
7432
+ if (new Set(names).size !== names.length) throw new Error("安装列表包含重复名称。");
7433
+ const knownVariables = new Set(entries.flatMap((entry) => variables(entry.config)));
7434
+ if (Object.keys(options.bindings ?? {}).some((key) => !knownVariables.has(key))) throw new Error("环境绑定包含未使用的变量名,请检查 --input-env。");
7435
+ for (const [server, bindings] of Object.entries(options.bindingsByServer ?? {})) {
7436
+ const entry = entries.find((candidate) => candidate.registrationName === server);
7437
+ if (!entry || Object.keys(bindings).some((key) => !variables(entry.config).includes(key))) throw new Error("服务器环境绑定包含未使用的名称。");
7438
+ }
7439
+ const { text, fileIdentity, servers } = await loadTarget(target);
7440
+ const replacements = {};
7441
+ const changes = [];
7442
+ for (const entry of entries) {
7443
+ if (entry.runtime?.source === "upload") throw new Error(`MCP ${entry.registrationName} 需要上传运行包。请先下载检查并准备本地启动路径,再用 --file 安装本地配置。`);
7444
+ const name = entry.registrationName;
7445
+ const bindings = {
7446
+ ...options.bindings,
7447
+ ...options.bindingsByServer?.[name]
7448
+ };
7449
+ const desired = encodeServer(entry.config, target, bindings, options.materialize, options.environment);
7450
+ const merged = mergeServer(servers[name], desired);
7451
+ const action = servers[name] === void 0 ? "add" : isDeepStrictEqual(servers[name], merged) ? "unchanged" : "replace";
7452
+ if (action === "replace" && !options.replace) throw new Error(`同名 MCP ${name} 配置不同;未修改任何服务器。确认要替换其连接配置时使用 --replace。`);
7453
+ if (action !== "unchanged") replacements[name] = merged;
7454
+ changes.push({
7455
+ name,
7456
+ action,
7457
+ transport: entry.config.type,
7458
+ variables: variables(entry.config).map((key) => bindings[key] ?? key)
7459
+ });
7460
+ }
7461
+ return {
7462
+ target,
7463
+ previous: text,
7464
+ next: Object.keys(replacements).length ? editDocument(text ?? "", target.format, target.key, replacements) : text,
7465
+ fileIdentity,
7466
+ changes
7467
+ };
7468
+ }
7469
+ async function planRemove(target, names) {
7470
+ if (!names.length || names.length > 100) throw new Error("请明确指定 1–100 个要移除的 MCP。");
7471
+ const { text, fileIdentity, servers } = await loadTarget(target);
7472
+ const replacements = {};
7473
+ const changes = [...new Set(names)].map((name) => {
7474
+ validName(name);
7475
+ const action = Object.hasOwn(servers, name) ? "remove" : "absent";
7476
+ if (action === "remove") replacements[name] = void 0;
7477
+ return {
7478
+ name,
7479
+ action
7480
+ };
7481
+ });
7482
+ return {
7483
+ target,
7484
+ previous: text,
7485
+ next: Object.keys(replacements).length ? editDocument(text ?? "", target.format, target.key, replacements) : text,
7486
+ fileIdentity,
7487
+ changes
7488
+ };
7489
+ }
7490
+ async function applyPlan(plan) {
7491
+ if (plan.previous === plan.next || plan.next === void 0) return { changed: false };
7492
+ if (await identity(plan.target.path) !== plan.fileIdentity) throw new Error("配置在预览后发生变化,请重新运行;未写入。");
7493
+ return {
7494
+ changed: true,
7495
+ backup: await writeConfig(plan.target.path, plan.previous, plan.next, plan.fileIdentity)
7496
+ };
7497
+ }
6965
7498
  var package_exports = /* @__PURE__ */ __exportAll({ runPackage: () => runPackage });
6966
7499
  const RESET$3 = "\x1B[0m";
6967
7500
  const BOLD$1 = "\x1B[1m";
@@ -7036,7 +7569,7 @@ function printPackageHelp() {
7036
7569
  console.log(` ${DIM$3}bfi skills package search <query>${RESET$3} 搜索包`);
7037
7570
  console.log();
7038
7571
  console.log(`${BOLD$1}服务端管理:${RESET$3}`);
7039
- console.log(` ${DIM$3}bfi skills package create <slug>${RESET$3} 创建包`);
7572
+ console.log(` ${DIM$3}bfi skills package create <slug> --skill <ref>${RESET$3} 创建包(至少一个技能)`);
7040
7573
  console.log(` ${DIM$3}bfi skills package add-skills <ref> [skills...]${RESET$3} 添加技能到包`);
7041
7574
  console.log(` ${DIM$3}bfi skills package remove-skills <ref> [skills...]${RESET$3} 从包移除技能`);
7042
7575
  console.log();
@@ -7053,6 +7586,7 @@ function printPackageHelp() {
7053
7586
  console.log(` -g, --global 安装到主目录而非当前项目`);
7054
7587
  console.log(` --name <name> 显示名称(create 时)`);
7055
7588
  console.log(` --description <t> 描述(create 时)`);
7589
+ console.log(` --skill <ref> create 时的关联技能,至少一个,可重复`);
7056
7590
  console.log(` --json JSON 输出`);
7057
7591
  console.log();
7058
7592
  }
@@ -7131,7 +7665,7 @@ function buildTargetSummaryLines(targets, installMode) {
7131
7665
  const subagentNames = targets.filter((t) => t.subagent).map(targetDisplayName);
7132
7666
  const { universal, symlinked } = splitAgentsByType(rootAgents);
7133
7667
  if (installMode === "symlink") {
7134
- if (universal.length > 0) lines.push(` ${import_picocolors.default.green("共享目录:")} ${formatList(universal, 3)}`);
7668
+ if (universal.length > 0) lines.push(` ${import_picocolors.default.green("共享目录(共用 .agents/skills):")} ${formatList(universal, 3)}`);
7135
7669
  if (symlinked.length > 0) lines.push(` ${import_picocolors.default.dim("符号链接 →")} ${formatList(symlinked)}`);
7136
7670
  if (subagentNames.length > 0) lines.push(` ${import_picocolors.default.dim("copy →")} ${formatList(subagentNames)}`);
7137
7671
  } else {
@@ -7188,8 +7722,47 @@ async function handleInstall(args, api, globalOpts) {
7188
7722
  console.log(` ${DIM$3}• ${skill.displayName || skill.slug}${RESET$3} ${DIM$3}${ref}${RESET$3}`);
7189
7723
  }
7190
7724
  console.log();
7725
+ let mcpNames = [];
7726
+ let mcpEntries = [];
7727
+ try {
7728
+ mcpNames = ((await api.getPackageMcps(namespace, slug))?.mcps ?? []).filter((entry) => entry?.mcpEntryId).map((entry) => entry.lastKnownName).filter((name) => typeof name === "string" && name.length > 0);
7729
+ } catch {}
7730
+ if (mcpNames.length > 0) {
7731
+ try {
7732
+ const { catalogEntries } = await import("./_chunks/registry.mjs");
7733
+ const { registry } = resolveConfig(globalOpts);
7734
+ mcpEntries = await catalogEntries(registry, mcpNames);
7735
+ } catch {}
7736
+ console.log(`${BOLD$1}关联 MCP(${mcpNames.length}):${RESET$3}`);
7737
+ for (const name of mcpNames) {
7738
+ const entry = mcpEntries.find((item) => item.registrationName === name);
7739
+ const keys = entry ? variables(entry.config) : [];
7740
+ console.log(` ${DIM$3}• ${name}${keys.length > 0 ? ` 变量:${keys.join("、")}` : ""}${RESET$3}`);
7741
+ }
7742
+ if (mcpEntries.length === 0) console.log(` ${YELLOW$1}变量信息暂不可用,安装时会读取组合包内容。${RESET$3}`);
7743
+ console.log();
7744
+ }
7745
+ const bindingsByServer = {};
7746
+ const envNames = [];
7747
+ const interactive = !isYes && !!process.stdin.isTTY && !!process.stdout.isTTY;
7748
+ const promptBinding = async (entry, key) => {
7749
+ let value;
7750
+ do {
7751
+ value = await password({ message: `${entry.registrationName}.${key}(输入不回显)` });
7752
+ if (isCancel(value)) {
7753
+ cancel("安装已取消");
7754
+ process.exit(0);
7755
+ }
7756
+ } while (!String(value).trim());
7757
+ const envName = `${entry.registrationName}_${key}`.replace(/[^A-Za-z0-9_]/g, "_").toUpperCase().replace(/^(?=[0-9])/, "_");
7758
+ process.env[envName] = String(value);
7759
+ (bindingsByServer[entry.registrationName] ??= {})[key] = envName;
7760
+ if (!envNames.includes(envName)) envNames.push(envName);
7761
+ };
7762
+ if (interactive) for (const entry of mcpEntries) for (const key of variables(entry.config)) await promptBinding(entry, key);
7191
7763
  let targetAgents;
7192
7764
  let installGlobally;
7765
+ let mcpGloballyOverride;
7193
7766
  let installMode;
7194
7767
  let eveSubagentTargets = [void 0];
7195
7768
  if (requestedAgents) {
@@ -7215,11 +7788,11 @@ async function handleInstall(args, api, globalOpts) {
7215
7788
  console.log(`${DIM$3}范围:${installGlobally ? "全局" : "项目"} | 方式:${installMode === "symlink" ? "符号链接" : "复制"}${RESET$3}`);
7216
7789
  console.log();
7217
7790
  } else {
7218
- const spinner$1 = spinner();
7219
- spinner$1.start("正在加载 Agent...");
7791
+ const spinner$2 = spinner();
7792
+ spinner$2.start("正在加载 Agent...");
7220
7793
  const installedAgents = await detectInstalledAgents();
7221
7794
  const totalAgents = Object.keys(agents).length;
7222
- spinner$1.stop(`${totalAgents} agents`);
7795
+ spinner$2.stop(`${totalAgents} agents`);
7223
7796
  const skillNames = skills.map((s) => s.displayName || s.slug);
7224
7797
  const skillSubject = skillNames.length <= 3 ? skillNames.map((n) => import_picocolors.default.cyan(n)).join(", ") : `${skills.length} 个技能`;
7225
7798
  if (installedAgents.includes("eve")) {
@@ -7235,7 +7808,11 @@ async function handleInstall(args, api, globalOpts) {
7235
7808
  targetAgents = ["eve"];
7236
7809
  console.log(`${DIM$3}安装到:eve agent${RESET$3}`);
7237
7810
  } else {
7238
- const selected = await selectAgentsInteractive({ global: false });
7811
+ const selected = await selectAgentsInteractive({
7812
+ global: false,
7813
+ useMemory: false,
7814
+ preselectDetected: false
7815
+ });
7239
7816
  if (isCancel(selected)) {
7240
7817
  cancel("安装已取消");
7241
7818
  process.exit(0);
@@ -7257,7 +7834,11 @@ async function handleInstall(args, api, globalOpts) {
7257
7834
  const firstAgent = installedAgents[0];
7258
7835
  console.log(`${DIM$3}安装到:${agents[firstAgent].displayName}${RESET$3}`);
7259
7836
  } else {
7260
- const selected = await selectAgentsInteractive({ global: false });
7837
+ const selected = await selectAgentsInteractive({
7838
+ global: false,
7839
+ useMemory: false,
7840
+ preselectDetected: false
7841
+ });
7261
7842
  if (isCancel(selected)) {
7262
7843
  cancel("安装已取消");
7263
7844
  process.exit(0);
@@ -7334,7 +7915,7 @@ async function handleInstall(args, api, globalOpts) {
7334
7915
  const overwriteChecks = await Promise.all(skills.flatMap((skill) => installTargets.map(async (target) => ({
7335
7916
  skillName: skill.slug,
7336
7917
  target,
7337
- installed: await isSkillInstalled(packageSkillName(skill.namespace || namespace, skill.slug), target.agent, {
7918
+ installed: await isSkillInstalled(skill.slug, target.agent, {
7338
7919
  global: installGlobally,
7339
7920
  eveSubagent: target.subagent
7340
7921
  })
@@ -7344,6 +7925,17 @@ async function handleInstall(args, api, globalOpts) {
7344
7925
  if (!overwriteStatus.has(skillName)) overwriteStatus.set(skillName, /* @__PURE__ */ new Map());
7345
7926
  overwriteStatus.get(skillName).set(targetKey(target), installed);
7346
7927
  }
7928
+ if (envNames.length > 0 && !installGlobally) {
7929
+ const choice = await confirm({
7930
+ message: "项目级配置不能保存明文变量。是否将关联 MCP 写入用户级配置并保存输入值?",
7931
+ initialValue: true
7932
+ });
7933
+ if (isCancel(choice)) {
7934
+ cancel("安装已取消");
7935
+ process.exit(0);
7936
+ }
7937
+ mcpGloballyOverride = !!choice;
7938
+ }
7347
7939
  const summaryLines = [];
7348
7940
  for (const skill of skills) {
7349
7941
  if (summaryLines.length > 0) summaryLines.push("");
@@ -7361,41 +7953,128 @@ async function handleInstall(args, api, globalOpts) {
7361
7953
  process.exit(0);
7362
7954
  }
7363
7955
  }
7364
- const { runAdd } = await Promise.resolve().then(() => add_exports);
7956
+ const mcpGlobally = mcpGloballyOverride ?? installGlobally;
7957
+ const { mkdtemp, readdir, realpath, rm } = await import("node:fs/promises");
7958
+ const { join } = await import("node:path");
7959
+ const { extractZip } = await Promise.resolve().then(() => zip_exports);
7960
+ const { discoverSkills } = await import("./_chunks/skills.mjs").then((n) => n.skills_exports);
7961
+ const { installSkillForAgent } = await Promise.resolve().then(() => installer_exports);
7365
7962
  const installedSkills = [];
7366
7963
  const nonEveAgents = targetAgents.filter((a) => a !== "eve");
7367
- const runAddBaseOpts = {
7368
- registry: globalOpts.registry,
7369
- token: globalOpts.token,
7370
- packageSkill: true,
7371
- global: installGlobally,
7372
- copy: installMode === "copy",
7373
- yes: true
7374
- };
7375
- if (nonEveAgents.length > 0) runAddBaseOpts.agent = nonEveAgents;
7376
- if (targetAgents.includes("eve")) runAddBaseOpts.subagent = eveSubagentTargets.map((s) => s ?? "root");
7377
- for (const skill of skills) {
7378
- const skillRef = `@${skill.namespace || namespace}/${skill.slug}`;
7379
- console.log(` ${DIM$3}正在安装 ${skillRef}...${RESET$3}`);
7964
+ const universalTarget = targetAgents.find((agent) => isUniversalAgent(agent));
7965
+ const installTargetsForInstall = [
7966
+ ...universalTarget ? [{ agent: universalTarget }] : [],
7967
+ ...targetAgents.filter((agent) => agent !== "eve" && !isUniversalAgent(agent)).map((agent) => ({ agent })),
7968
+ ...targetAgents.includes("eve") ? eveSubagentTargets.map((subagent) => ({
7969
+ agent: "eve",
7970
+ subagent
7971
+ })) : []
7972
+ ];
7973
+ const work = await mkdtemp(join(await realpath(os$1.tmpdir()), "bfi-package-"));
7974
+ let mcpFile;
7975
+ try {
7976
+ const archive = join(work, "package.zip");
7977
+ const unpack = join(work, "content");
7978
+ const spinner$1 = spinner();
7979
+ spinner$1.start("正在下载组合包...");
7980
+ await api.downloadPackage(namespace, slug, archive);
7981
+ spinner$1.message("正在解压组合包...");
7982
+ await extractZip(archive, unpack);
7983
+ spinner$1.stop("组合包已就绪,开始安装技能");
7984
+ const entries = await readdir(unpack, { withFileTypes: true });
7985
+ if (entries.some((entry) => entry.isFile() && entry.name === "mcp.json")) mcpFile = join(unpack, "mcp.json");
7986
+ const directories = entries.filter((entry) => entry.isDirectory());
7987
+ if (directories.length === 0) throw new Error("组合包中没有技能目录");
7988
+ const installSpinner = spinner();
7989
+ installSpinner.start("正在安装技能...");
7990
+ const failures = [];
7991
+ for (const directory of directories) {
7992
+ const skill = skills.find((entry) => entry.slug === directory.name);
7993
+ const skillRef = skill ? `@${skill.namespace || namespace}/${skill.slug}` : directory.name;
7994
+ installSpinner.message(`正在安装 ${directory.name}`);
7995
+ try {
7996
+ const found = (await discoverSkills(join(unpack, directory.name)))[0];
7997
+ if (!found) throw new Error("未找到有效的 SKILL.md");
7998
+ const results = [];
7999
+ for (const target of installTargetsForInstall) results.push(await installSkillForAgent(found, target.agent, {
8000
+ global: installGlobally,
8001
+ cwd: process.cwd(),
8002
+ mode: installMode,
8003
+ eveSubagent: target.subagent
8004
+ }));
8005
+ const failed = results.filter((result) => !result.success);
8006
+ if (failed.length > 0) throw new Error(failed.map((result) => result.error).filter(Boolean).join(";") || "安装失败");
8007
+ installedSkills.push({
8008
+ ref: skillRef,
8009
+ name: found.name || directory.name
8010
+ });
8011
+ } catch (error) {
8012
+ failures.push(`${directory.name}:${error.message}`);
8013
+ }
8014
+ }
8015
+ installSpinner.stop(`已安装 ${installedSkills.length}/${directories.length} 个技能`);
8016
+ for (const failure of failures) log.error(failure);
8017
+ } catch (error) {
8018
+ await rm(work, {
8019
+ recursive: true,
8020
+ force: true
8021
+ });
8022
+ throw error;
8023
+ }
8024
+ let mcpJsonText;
8025
+ if (mcpFile) try {
8026
+ mcpJsonText = await readConfig(mcpFile);
8027
+ } catch {
8028
+ mcpJsonText = void 0;
8029
+ }
8030
+ await rm(work, {
8031
+ recursive: true,
8032
+ force: true
8033
+ });
8034
+ if (installedSkills.length > 0) {
8035
+ let entries = [];
7380
8036
  try {
7381
- await runAdd([skillRef], runAddBaseOpts);
7382
- const targets = targetAgents.flatMap((agent) => agent === "eve" ? eveSubagentTargets.map((subagent) => ({
7383
- agent,
7384
- subagent
7385
- })) : [{
7386
- agent,
7387
- subagent: void 0
7388
- }]);
7389
- const verified = await Promise.all(targets.map((target) => isSkillInstalled(packageSkillName(skill.namespace || namespace, skill.slug), target.agent, {
7390
- global: installGlobally,
7391
- eveSubagent: target.subagent
7392
- })));
7393
- if (verified.length === 0 || verified.some((result) => !result)) throw new Error("Skill installation could not be verified");
7394
- console.log(` ${GREEN$2}✓ ${skill.slug}${RESET$3}`);
7395
- installedSkills.push(skillRef);
7396
- } catch (error) {
7397
- console.log(` ${RED$1}✗ ${skill.slug}:${error.message}${RESET$3}`);
8037
+ if (mcpJsonText) entries = entriesFromJson(readDocument(mcpJsonText, "jsonc"));
8038
+ } catch {
8039
+ entries = [];
7398
8040
  }
8041
+ if (entries.length === 0) entries = mcpEntries;
8042
+ if (entries.length > 0) {
8043
+ const scope = mcpGlobally ? "user" : "project";
8044
+ if (interactive) {
8045
+ for (const entry of entries) for (const key of variables(entry.config)) if (!bindingsByServer[entry.registrationName]?.[key]) await promptBinding(entry, key);
8046
+ }
8047
+ const supported = nonEveAgents.filter((agent) => agentIds.includes(agent));
8048
+ const skipped = nonEveAgents.filter((agent) => !agentIds.includes(agent));
8049
+ const lines = [];
8050
+ let installedMcps = 0;
8051
+ for (const agent of supported) try {
8052
+ const plan = await planInstall(targetFor(agent, scope), entries, {
8053
+ bindingsByServer,
8054
+ materialize: scope === "user",
8055
+ environment: process.env
8056
+ });
8057
+ const result = await applyPlan(plan);
8058
+ const added = plan.changes.filter((change) => change.action === "add").length;
8059
+ const replaced = plan.changes.filter((change) => change.action === "replace").length;
8060
+ const unchanged = plan.changes.filter((change) => change.action === "unchanged").length;
8061
+ lines.push(`${agent.padEnd(14)} ${[
8062
+ added > 0 ? `新增 ${added}` : "",
8063
+ replaced > 0 ? `更新 ${replaced}` : "",
8064
+ unchanged > 0 ? `未变 ${unchanged}` : "",
8065
+ result.changed ? "" : "无变化"
8066
+ ].filter(Boolean).join(" · ")}`);
8067
+ installedMcps++;
8068
+ } catch (error) {
8069
+ lines.push(`${agent.padEnd(14)} ✗ ${error.message}`);
8070
+ }
8071
+ note(lines.join("\n"), `关联 MCP(${installedMcps}/${supported.length} 个 Agent)`);
8072
+ if (skipped.length > 0) log.info(`未支持自动写入 MCP 的 Agent:${skipped.join(", ")}`);
8073
+ if (scope === "project") {
8074
+ const projectVariables = [...new Set(entries.flatMap((entry) => variables(entry.config).map((key) => bindingsByServer[entry.registrationName]?.[key] ?? key)))];
8075
+ if (projectVariables.length > 0) log.info(`项目级 MCP 配置只保存变量引用:${projectVariables.map((name) => "${" + name + "}").join("、")}。请在启动 Agent 前导出这些环境变量;需要保存输入值时可在提示中选择写入用户级配置。`);
8076
+ }
8077
+ } else if (mcpNames.length > 0) log.warn("组合声明的 MCP 未能加载,请稍后重试或使用 bfi mcp install 单独安装。");
7399
8078
  }
7400
8079
  if (installedSkills.length === 0) {
7401
8080
  console.log(`${YELLOW$1}没有确认安装成功的技能,未创建包安装记录${RESET$3}`);
@@ -7412,14 +8091,14 @@ async function handleInstall(args, api, globalOpts) {
7412
8091
  version: pkgInfo.version || "latest",
7413
8092
  skillCount: installedSkills.length
7414
8093
  };
7415
- for (const skillRef of installedSkills) {
8094
+ for (const { ref: skillRef, name } of installedSkills) {
7416
8095
  const identity = parseSkillRef(skillRef);
7417
8096
  const existing = lock.skills[skillRef];
7418
8097
  lock.skills[skillRef] = {
7419
8098
  ...existing,
7420
8099
  namespace: identity.namespace,
7421
8100
  slug: identity.slug,
7422
- installedName: packageSkillName(identity.namespace, identity.slug),
8101
+ installedName: name,
7423
8102
  version: existing?.version || "latest",
7424
8103
  package: {
7425
8104
  namespace,
@@ -7428,7 +8107,7 @@ async function handleInstall(args, api, globalOpts) {
7428
8107
  };
7429
8108
  }
7430
8109
  saveLockfile(cwd, lock);
7431
- for (const skillRef of installedSkills) await reportInstallEventForSkill(api, {
8110
+ for (const { ref: skillRef } of installedSkills) await reportInstallEventForSkill(api, {
7432
8111
  skillId: skills.find((entry) => `@${entry.namespace || namespace}/${entry.slug}` === skillRef)?.skillId,
7433
8112
  agentTarget: targetAgents[0]
7434
8113
  });
@@ -7589,19 +8268,33 @@ async function handleSearch(args, api, globalOpts) {
7589
8268
  async function handleCreate(args, api, globalOpts) {
7590
8269
  const slug = args[0];
7591
8270
  if (!slug) {
7592
- console.error("用法:bfi skills package create <slug> [options]");
8271
+ console.error("用法:bfi skills package create <slug> --skill <@空间/技能> [--skill ...] [options]");
7593
8272
  process.exit(1);
7594
8273
  }
7595
8274
  const namespace = getOptionValue(args, "--namespace") || globalOpts.namespace || "global";
7596
8275
  const name = getOptionValue(args, "--name");
7597
8276
  const description = getOptionValue(args, "--description");
7598
8277
  const visibility = getOptionValue(args, "--visibility") || "private";
8278
+ const skillRefs = [];
8279
+ for (let index = 0; index < args.length; index++) if (args[index] === "--skill" && args[index + 1]) skillRefs.push(args[++index]);
8280
+ if (skillRefs.length === 0) {
8281
+ console.error(`${RED$1}技能组合至少需要关联一个技能,请使用 --skill <@空间/技能> 指定。${RESET$3}`);
8282
+ process.exit(1);
8283
+ }
8284
+ const skills = skillRefs.map((ref) => {
8285
+ const identity = parseSkillRef(ref, namespace);
8286
+ return {
8287
+ namespace: identity.namespace,
8288
+ slug: identity.slug
8289
+ };
8290
+ });
7599
8291
  try {
7600
8292
  const result = await api.createPackage(namespace, {
7601
8293
  slug,
7602
8294
  displayName: name || slug,
7603
8295
  description: description || "",
7604
- visibility: visibility.toUpperCase()
8296
+ visibility: visibility.toUpperCase(),
8297
+ skills
7605
8298
  });
7606
8299
  console.log(`${GREEN$2}✓ 已创建包 ${toSkillRefString(namespace, slug)}${RESET$3}`);
7607
8300
  const pkg = result.package || result.data || result;
@@ -10261,4 +10954,4 @@ main().catch((e) => {
10261
10954
  console.error(`✗ ${e?.message || e}`);
10262
10955
  process.exit(1);
10263
10956
  });
10264
- export { SkillHubAPI, getAgentBaseDir };
10957
+ export { SkillHubAPI, agentIds, applyPlan, checkPath, decodeServer, getAgentBaseDir, loadTarget, planInstall, planRemove, readConfig, readDocument, serversIn, targetFor, writeConfig };