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/README.md +2 -2
- package/dist/_chunks/command.mjs +3 -5
- package/dist/_chunks/libs/@clack/core.mjs +28 -2
- package/dist/_chunks/libs/@clack/prompts.mjs +32 -2
- package/dist/_chunks/mcp.mjs +19 -87
- package/dist/_chunks/receipt.mjs +1 -1
- package/dist/_chunks/registry.mjs +83 -0
- package/dist/cli.mjs +822 -129
- package/package.json +1 -1
- package/dist/_chunks/files.mjs +0 -89
- package/dist/_chunks/manager.mjs +0 -436
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]}
|
|
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: [
|
|
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$
|
|
3600
|
-
spinner$
|
|
3621
|
+
const spinner$4 = spinner();
|
|
3622
|
+
spinner$4.start("正在解析源...");
|
|
3601
3623
|
const parsed = parseSource(source);
|
|
3602
|
-
spinner$
|
|
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$
|
|
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$
|
|
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$
|
|
3648
|
+
spinner$4.start("正在验证本地路径...");
|
|
3627
3649
|
if (!existsSync(parsed.localPath)) {
|
|
3628
|
-
spinner$
|
|
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$
|
|
3633
|
-
spinner$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3663
|
-
spinner$
|
|
3664
|
-
spinner$
|
|
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$
|
|
3672
|
-
spinner$
|
|
3673
|
-
spinner$
|
|
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$
|
|
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$
|
|
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$
|
|
3821
|
+
spinner$4.start("正在加载 Agent...");
|
|
3800
3822
|
const installedAgents = await detectInstalledAgents();
|
|
3801
3823
|
const totalAgents = Object.keys(agents).length;
|
|
3802
|
-
spinner$
|
|
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$
|
|
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$
|
|
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$
|
|
4712
|
-
spinner$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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("
|
|
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$
|
|
7219
|
-
spinner$
|
|
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$
|
|
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({
|
|
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({
|
|
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(
|
|
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
|
|
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
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
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
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
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:
|
|
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 };
|