bfi-cli 0.1.102 → 0.1.104
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 +26 -20
- package/dist/_chunks/command.mjs +1 -4
- package/dist/_chunks/intent.mjs +41 -10
- 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/package-plugin.mjs +349 -72
- package/dist/_chunks/receipt.mjs +1 -1
- package/dist/_chunks/registry.mjs +83 -0
- package/dist/cli.mjs +987 -138
- package/package.json +1 -1
- package/dist/_chunks/client.mjs +0 -107
- package/dist/_chunks/files.mjs +0 -89
- package/dist/_chunks/manager.mjs +0 -436
- package/dist/_chunks/package-skill-identity.mjs +0 -18
package/dist/cli.mjs
CHANGED
|
@@ -1,33 +1,37 @@
|
|
|
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
|
-
import { PluginRegistryClient, parsePackageIntent } from "./_chunks/intent.mjs";
|
|
7
|
+
import { PluginRegistryClient, normalizeRegistry, parsePackageIntent } from "./_chunks/intent.mjs";
|
|
8
8
|
import { loadGlobalConfig, loadLockfile, loadProjectConfig, resolveConfig, saveGlobalConfig, saveLockfile, setConfigValue } from "./_chunks/config.mjs";
|
|
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
|
-
import {
|
|
11
|
+
import { entriesFromJson, mapStrings, normalizeConfig, object, resolveVariables, validName, variablePattern, variables } from "./_chunks/model.mjs";
|
|
12
|
+
import { hash, verifyPlan } from "./_chunks/digest.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, readFile, rename, unlink, writeFile } 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}`;
|
|
@@ -1223,11 +1227,11 @@ function createGitClient(extraEnv) {
|
|
|
1223
1227
|
}).env(env);
|
|
1224
1228
|
}
|
|
1225
1229
|
async function resetTempDir(dir) {
|
|
1226
|
-
await rm(dir, {
|
|
1230
|
+
await rm$1(dir, {
|
|
1227
1231
|
recursive: true,
|
|
1228
1232
|
force: true
|
|
1229
1233
|
}).catch(() => {});
|
|
1230
|
-
await mkdir(dir, { recursive: true });
|
|
1234
|
+
await mkdir$1(dir, { recursive: true });
|
|
1231
1235
|
}
|
|
1232
1236
|
async function tryGhClone(repo, tempDir, ref) {
|
|
1233
1237
|
let cloneTarget = repo.slug;
|
|
@@ -1292,7 +1296,7 @@ async function cloneRepo(url, ref) {
|
|
|
1292
1296
|
const isTimeout = errorMessage.includes("block timeout") || errorMessage.includes("timed out");
|
|
1293
1297
|
const isAuthError = isAuthFailure(errorMessage);
|
|
1294
1298
|
if (isTimeout) {
|
|
1295
|
-
await rm(tempDir, {
|
|
1299
|
+
await rm$1(tempDir, {
|
|
1296
1300
|
recursive: true,
|
|
1297
1301
|
force: true
|
|
1298
1302
|
}).catch(() => {});
|
|
@@ -1309,7 +1313,7 @@ async function cloneRepo(url, ref) {
|
|
|
1309
1313
|
return tempDir;
|
|
1310
1314
|
} catch {}
|
|
1311
1315
|
}
|
|
1312
|
-
await rm(tempDir, {
|
|
1316
|
+
await rm$1(tempDir, {
|
|
1313
1317
|
recursive: true,
|
|
1314
1318
|
force: true
|
|
1315
1319
|
}).catch(() => {});
|
|
@@ -1321,13 +1325,26 @@ async function cleanupTempDir(dir) {
|
|
|
1321
1325
|
const normalizedDir = normalize(resolve(dir));
|
|
1322
1326
|
const normalizedTmpDir = normalize(resolve(tmpdir()));
|
|
1323
1327
|
if (!normalizedDir.startsWith(normalizedTmpDir + sep) && normalizedDir !== normalizedTmpDir) throw new Error("Attempted to clean up directory outside of temp directory");
|
|
1324
|
-
await rm(dir, {
|
|
1328
|
+
await rm$1(dir, {
|
|
1325
1329
|
recursive: true,
|
|
1326
1330
|
force: true
|
|
1327
1331
|
});
|
|
1328
1332
|
}
|
|
1329
1333
|
const AGENTS_DIR = ".agents";
|
|
1330
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
|
+
});
|
|
1331
1348
|
function sanitizeName(name) {
|
|
1332
1349
|
return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
|
|
1333
1350
|
}
|
|
@@ -1372,7 +1389,7 @@ function resolveSymlinkTarget(linkPath, linkTarget) {
|
|
|
1372
1389
|
}
|
|
1373
1390
|
async function cleanAndCreateDirectory(path) {
|
|
1374
1391
|
try {
|
|
1375
|
-
await rm(path, {
|
|
1392
|
+
await rm$1(path, {
|
|
1376
1393
|
recursive: true,
|
|
1377
1394
|
force: true
|
|
1378
1395
|
});
|
|
@@ -1387,7 +1404,7 @@ async function classifyPathEntry(path) {
|
|
|
1387
1404
|
return (await stat(path)).isDirectory() ? "dir" : "blocker";
|
|
1388
1405
|
} catch {
|
|
1389
1406
|
try {
|
|
1390
|
-
await lstat(path);
|
|
1407
|
+
await lstat$1(path);
|
|
1391
1408
|
return "blocker";
|
|
1392
1409
|
} catch {
|
|
1393
1410
|
return "missing";
|
|
@@ -1407,32 +1424,32 @@ async function findBlockingEntry(path) {
|
|
|
1407
1424
|
}
|
|
1408
1425
|
async function ensureDirectory(path) {
|
|
1409
1426
|
try {
|
|
1410
|
-
await mkdir(path, { recursive: true });
|
|
1427
|
+
await mkdir$1(path, { recursive: true });
|
|
1411
1428
|
return;
|
|
1412
1429
|
} catch (err) {
|
|
1413
1430
|
if (!isErrnoException(err) || err.code !== "ENOTDIR" && err.code !== "EEXIST" && err.code !== "ELOOP" && err.code !== "ENOENT") throw err;
|
|
1414
1431
|
}
|
|
1415
1432
|
const blockingEntry = await findBlockingEntry(path);
|
|
1416
1433
|
if (!blockingEntry) {
|
|
1417
|
-
await mkdir(path, { recursive: true });
|
|
1434
|
+
await mkdir$1(path, { recursive: true });
|
|
1418
1435
|
return;
|
|
1419
1436
|
}
|
|
1420
1437
|
const backupPath = `${blockingEntry}.bak-${Date.now()}`;
|
|
1421
1438
|
try {
|
|
1422
|
-
await rename(blockingEntry, backupPath);
|
|
1439
|
+
await rename$1(blockingEntry, backupPath);
|
|
1423
1440
|
} catch (renameErr) {
|
|
1424
1441
|
const reason = renameErr instanceof Error ? renameErr.message : String(renameErr);
|
|
1425
1442
|
throw new Error(`无法创建目录 ${path}:${blockingEntry} 存在但不是可用目录(可能是文件或失效的链接),且自动移开失败(${reason})。请手动删除或重命名该条目后重试。`);
|
|
1426
1443
|
}
|
|
1427
1444
|
console.warn(`检测到 ${blockingEntry} 不是可用目录(可能是文件或失效的链接),已重命名为 ${backupPath}`);
|
|
1428
|
-
await mkdir(path, { recursive: true });
|
|
1445
|
+
await mkdir$1(path, { recursive: true });
|
|
1429
1446
|
}
|
|
1430
1447
|
async function resolveParentSymlinks(path) {
|
|
1431
1448
|
const resolved = resolve(path);
|
|
1432
1449
|
const dir = dirname(resolved);
|
|
1433
1450
|
const base = basename(resolved);
|
|
1434
1451
|
try {
|
|
1435
|
-
const realDir = await realpath(dir);
|
|
1452
|
+
const realDir = await realpath$1(dir);
|
|
1436
1453
|
return join(realDir, base);
|
|
1437
1454
|
} catch {
|
|
1438
1455
|
return resolved;
|
|
@@ -1442,21 +1459,21 @@ async function createSymlink(target, linkPath) {
|
|
|
1442
1459
|
try {
|
|
1443
1460
|
const resolvedTarget = resolve(target);
|
|
1444
1461
|
const resolvedLinkPath = resolve(linkPath);
|
|
1445
|
-
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)]);
|
|
1446
1463
|
if (realTarget === realLinkPath) return true;
|
|
1447
1464
|
if (await resolveParentSymlinks(target) === await resolveParentSymlinks(linkPath)) return true;
|
|
1448
1465
|
try {
|
|
1449
|
-
if ((await lstat(linkPath)).isSymbolicLink()) {
|
|
1466
|
+
if ((await lstat$1(linkPath)).isSymbolicLink()) {
|
|
1450
1467
|
if (resolveSymlinkTarget(linkPath, await readlink(linkPath)) === resolvedTarget) return true;
|
|
1451
|
-
await rm(linkPath);
|
|
1452
|
-
} else await rm(linkPath, { recursive: true });
|
|
1468
|
+
await rm$1(linkPath);
|
|
1469
|
+
} else await rm$1(linkPath, { recursive: true });
|
|
1453
1470
|
} catch (err) {
|
|
1454
1471
|
if (err && typeof err === "object" && "code" in err && err.code === "ELOOP") try {
|
|
1455
|
-
await rm(linkPath, { force: true });
|
|
1472
|
+
await rm$1(linkPath, { force: true });
|
|
1456
1473
|
} catch {}
|
|
1457
1474
|
}
|
|
1458
1475
|
const linkDir = dirname(linkPath);
|
|
1459
|
-
await mkdir(linkDir, { recursive: true });
|
|
1476
|
+
await mkdir$1(linkDir, { recursive: true });
|
|
1460
1477
|
const realLinkDir = await resolveParentSymlinks(linkDir);
|
|
1461
1478
|
const relativePath = relative(realLinkDir, target);
|
|
1462
1479
|
const symlinkType = platform() === "win32" ? "junction" : void 0;
|
|
@@ -1589,18 +1606,18 @@ function stripIgnoredEveFrontmatter(raw) {
|
|
|
1589
1606
|
return `---\n${keys.map((key) => `${key}: ${JSON.stringify(eveData[key])}`).join("\n")}\n---\n${content.replace(/^\r?\n/u, "")}`;
|
|
1590
1607
|
}
|
|
1591
1608
|
async function copyDirectory(src, dest, agentType) {
|
|
1592
|
-
await mkdir(dest, { recursive: true });
|
|
1593
|
-
const entries = await readdir(src, { withFileTypes: true });
|
|
1609
|
+
await mkdir$1(dest, { recursive: true });
|
|
1610
|
+
const entries = await readdir$1(src, { withFileTypes: true });
|
|
1594
1611
|
await Promise.all(entries.filter((entry) => !isExcluded$1(entry.name, entry.isDirectory())).map(async (entry) => {
|
|
1595
1612
|
const srcPath = join(src, entry.name);
|
|
1596
1613
|
const destPath = join(dest, entry.name);
|
|
1597
1614
|
if (entry.isDirectory()) await copyDirectory(srcPath, destPath, agentType);
|
|
1598
1615
|
else try {
|
|
1599
1616
|
if (agentType === "eve" && entry.name.toLowerCase() === "skill.md") {
|
|
1600
|
-
await writeFile(destPath, stripIgnoredEveFrontmatter(await readFile(srcPath, "utf-8")));
|
|
1617
|
+
await writeFile$1(destPath, stripIgnoredEveFrontmatter(await readFile$1(srcPath, "utf-8")));
|
|
1601
1618
|
return;
|
|
1602
1619
|
}
|
|
1603
|
-
await cp(srcPath, destPath, {
|
|
1620
|
+
await cp$1(srcPath, destPath, {
|
|
1604
1621
|
dereference: true,
|
|
1605
1622
|
recursive: true
|
|
1606
1623
|
});
|
|
@@ -1632,7 +1649,7 @@ async function isSkillInstalled(skillName, agentType, options = {}) {
|
|
|
1632
1649
|
const skillDir = join(targetBase, sanitized);
|
|
1633
1650
|
if (!isPathSafe$1(targetBase, skillDir)) return false;
|
|
1634
1651
|
try {
|
|
1635
|
-
await access(skillDir);
|
|
1652
|
+
await access$1(skillDir);
|
|
1636
1653
|
return true;
|
|
1637
1654
|
} catch {
|
|
1638
1655
|
return false;
|
|
@@ -1688,8 +1705,8 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1688
1705
|
const fullPath = join(targetDir, filePath);
|
|
1689
1706
|
if (!isPathSafe$1(targetDir, fullPath)) continue;
|
|
1690
1707
|
const parentDir = dirname(fullPath);
|
|
1691
|
-
if (parentDir !== targetDir) await mkdir(parentDir, { recursive: true });
|
|
1692
|
-
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);
|
|
1693
1710
|
}
|
|
1694
1711
|
}
|
|
1695
1712
|
try {
|
|
@@ -1760,11 +1777,11 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
1760
1777
|
};
|
|
1761
1778
|
try {
|
|
1762
1779
|
await ensureDirectory(agentBase);
|
|
1763
|
-
await rm(flatSkillPath, {
|
|
1780
|
+
await rm$1(flatSkillPath, {
|
|
1764
1781
|
recursive: true,
|
|
1765
1782
|
force: true
|
|
1766
1783
|
});
|
|
1767
|
-
await writeFile(flatSkillPath, getEveFlatSkillMarkdown(skill.files), "utf-8");
|
|
1784
|
+
await writeFile$1(flatSkillPath, getEveFlatSkillMarkdown(skill.files), "utf-8");
|
|
1768
1785
|
return {
|
|
1769
1786
|
success: true,
|
|
1770
1787
|
path: flatSkillPath,
|
|
@@ -1799,8 +1816,8 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
1799
1816
|
const fullPath = join(targetDir, file.path);
|
|
1800
1817
|
if (!isPathSafe$1(targetDir, fullPath)) continue;
|
|
1801
1818
|
const parentDir = dirname(fullPath);
|
|
1802
|
-
if (parentDir !== targetDir) await mkdir(parentDir, { recursive: true });
|
|
1803
|
-
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");
|
|
1804
1821
|
}
|
|
1805
1822
|
}
|
|
1806
1823
|
try {
|
|
@@ -1905,7 +1922,7 @@ async function listInstalledSkills(options = {}) {
|
|
|
1905
1922
|
}
|
|
1906
1923
|
}
|
|
1907
1924
|
for (const scope of scopes) try {
|
|
1908
|
-
const entries = await readdir(scope.path, { withFileTypes: true });
|
|
1925
|
+
const entries = await readdir$1(scope.path, { withFileTypes: true });
|
|
1909
1926
|
for (const entry of entries) {
|
|
1910
1927
|
const skillDir = join(scope.path, entry.name);
|
|
1911
1928
|
if (!await isDirEntryOrSymlinkToDir(entry, skillDir)) continue;
|
|
@@ -1949,13 +1966,13 @@ async function listInstalledSkills(options = {}) {
|
|
|
1949
1966
|
const agentSkillDir = join(agentBase, possibleName);
|
|
1950
1967
|
if (!isPathSafe$1(agentBase, agentSkillDir)) continue;
|
|
1951
1968
|
try {
|
|
1952
|
-
await access(agentSkillDir);
|
|
1969
|
+
await access$1(agentSkillDir);
|
|
1953
1970
|
found = true;
|
|
1954
1971
|
break;
|
|
1955
1972
|
} catch {}
|
|
1956
1973
|
}
|
|
1957
1974
|
if (!found) try {
|
|
1958
|
-
const agentEntries = await readdir(agentBase, { withFileTypes: true });
|
|
1975
|
+
const agentEntries = await readdir$1(agentBase, { withFileTypes: true });
|
|
1959
1976
|
for (const agentEntry of agentEntries) {
|
|
1960
1977
|
const candidateDir = join(agentBase, agentEntry.name);
|
|
1961
1978
|
if (!await isDirEntryOrSymlinkToDir(agentEntry, candidateDir)) continue;
|
|
@@ -3111,13 +3128,14 @@ async function selectAgentsInteractive(options) {
|
|
|
3111
3128
|
hint: options.global ? agents[a].globalSkillsDir : agents[a].skillsDir
|
|
3112
3129
|
}));
|
|
3113
3130
|
let lastSelected = [];
|
|
3114
|
-
try {
|
|
3131
|
+
if (options.useMemory !== false) try {
|
|
3115
3132
|
lastSelected = await getLastSelectedAgents() || [];
|
|
3116
3133
|
} catch {}
|
|
3134
|
+
const baseSelection = lastSelected.length > 0 ? lastSelected : options.preselectDetected === false ? [] : installedAgents;
|
|
3117
3135
|
const selected = await searchMultiselect({
|
|
3118
3136
|
message: "请选择要安装到的 Agent:",
|
|
3119
3137
|
items: otherChoices,
|
|
3120
|
-
initialSelected: [
|
|
3138
|
+
initialSelected: [...new Set(baseSelection)].filter((a) => otherAgents.includes(a) && !universalAgents.includes(a)),
|
|
3121
3139
|
lockedSection: universalSection
|
|
3122
3140
|
});
|
|
3123
3141
|
if (!isCancelled(selected)) try {
|
|
@@ -3542,7 +3560,7 @@ async function installFromSkillHub(parsed, options, spinner) {
|
|
|
3542
3560
|
process.exit(1);
|
|
3543
3561
|
}
|
|
3544
3562
|
if (options.packageSkill) {
|
|
3545
|
-
const { scopePackageSkill } = await
|
|
3563
|
+
const { scopePackageSkill } = await Promise.resolve().then(() => package_skill_identity_exports);
|
|
3546
3564
|
try {
|
|
3547
3565
|
return {
|
|
3548
3566
|
skills: scopePackageSkill(skills, namespace, slug),
|
|
@@ -3600,10 +3618,10 @@ async function runAdd(args, options = {}) {
|
|
|
3600
3618
|
else if (!process.stdin.isTTY) showInstallTip();
|
|
3601
3619
|
let tempDir = null;
|
|
3602
3620
|
try {
|
|
3603
|
-
const spinner$
|
|
3604
|
-
spinner$
|
|
3621
|
+
const spinner$4 = spinner();
|
|
3622
|
+
spinner$4.start("正在解析源...");
|
|
3605
3623
|
const parsed = parseSource(source);
|
|
3606
|
-
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)}` : ""}`);
|
|
3607
3625
|
const ownerRepoRaw = getOwnerRepo(parsed);
|
|
3608
3626
|
const repoPrivacyPromise = (() => {
|
|
3609
3627
|
if (!ownerRepoRaw) return Promise.resolve(null);
|
|
@@ -3612,7 +3630,7 @@ async function runAdd(args, options = {}) {
|
|
|
3612
3630
|
return isRepoPrivate(ownerRepo.owner, ownerRepo.repo).catch(() => null);
|
|
3613
3631
|
})();
|
|
3614
3632
|
if (parsed.type === "well-known") {
|
|
3615
|
-
await handleWellKnownSkills(source, parsed.url, options, spinner$
|
|
3633
|
+
await handleWellKnownSkills(source, parsed.url, options, spinner$4);
|
|
3616
3634
|
return;
|
|
3617
3635
|
}
|
|
3618
3636
|
if (parsed.skillFilter) {
|
|
@@ -3623,18 +3641,18 @@ async function runAdd(args, options = {}) {
|
|
|
3623
3641
|
let skills;
|
|
3624
3642
|
let blobResult = null;
|
|
3625
3643
|
if (parsed.type === "skillhub") {
|
|
3626
|
-
const result = await installFromSkillHub(parsed, options, spinner$
|
|
3644
|
+
const result = await installFromSkillHub(parsed, options, spinner$4);
|
|
3627
3645
|
skills = result.skills;
|
|
3628
3646
|
tempDir = result.tempDir;
|
|
3629
3647
|
} else if (parsed.type === "local") {
|
|
3630
|
-
spinner$
|
|
3648
|
+
spinner$4.start("正在验证本地路径...");
|
|
3631
3649
|
if (!existsSync(parsed.localPath)) {
|
|
3632
|
-
spinner$
|
|
3650
|
+
spinner$4.stop(import_picocolors.default.red("路径未找到"));
|
|
3633
3651
|
outro(import_picocolors.default.red(`本地路径不存在:${parsed.localPath}`));
|
|
3634
3652
|
process.exit(1);
|
|
3635
3653
|
}
|
|
3636
|
-
spinner$
|
|
3637
|
-
spinner$
|
|
3654
|
+
spinner$4.stop("本地路径验证通过");
|
|
3655
|
+
spinner$4.start("正在发现技能...");
|
|
3638
3656
|
skills = await discoverSkills(parsed.localPath, parsed.subpath, {
|
|
3639
3657
|
includeInternal,
|
|
3640
3658
|
fullDepth: options.fullDepth
|
|
@@ -3649,7 +3667,7 @@ async function runAdd(args, options = {}) {
|
|
|
3649
3667
|
const owner = ownerRepo?.split("/")[0]?.toLowerCase();
|
|
3650
3668
|
const isSelfHostedRepo = !!ownerRepo && Object.hasOwn(BLOB_ALLOWED_REPOS, ownerRepo.toLowerCase());
|
|
3651
3669
|
if (ownerRepo && owner && (isSelfHostedRepo || BLOB_ALLOWED_OWNERS.includes(owner))) {
|
|
3652
|
-
spinner$
|
|
3670
|
+
spinner$4.start("正在获取技能...");
|
|
3653
3671
|
blobResult = await tryBlobInstall(ownerRepo, {
|
|
3654
3672
|
subpath: parsed.subpath,
|
|
3655
3673
|
skillFilter: parsed.skillFilter,
|
|
@@ -3657,36 +3675,36 @@ async function runAdd(args, options = {}) {
|
|
|
3657
3675
|
getToken: getGitHubToken,
|
|
3658
3676
|
includeInternal
|
|
3659
3677
|
});
|
|
3660
|
-
if (!blobResult) spinner$
|
|
3678
|
+
if (!blobResult) spinner$4.stop(import_picocolors.default.dim("Falling back to clone..."));
|
|
3661
3679
|
}
|
|
3662
3680
|
if (blobResult) {
|
|
3663
3681
|
skills = blobResult.skills;
|
|
3664
|
-
spinner$
|
|
3682
|
+
spinner$4.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
|
|
3665
3683
|
} else {
|
|
3666
|
-
tempDir = await cloneWithGitHubFallback(parsed.url, options.branch || parsed.ref, spinner$
|
|
3667
|
-
spinner$
|
|
3668
|
-
spinner$
|
|
3684
|
+
tempDir = await cloneWithGitHubFallback(parsed.url, options.branch || parsed.ref, spinner$4);
|
|
3685
|
+
spinner$4.stop("仓库克隆完成");
|
|
3686
|
+
spinner$4.start("正在发现技能...");
|
|
3669
3687
|
skills = await discoverSkills(tempDir, parsed.subpath, {
|
|
3670
3688
|
includeInternal,
|
|
3671
3689
|
fullDepth: options.fullDepth
|
|
3672
3690
|
});
|
|
3673
3691
|
}
|
|
3674
3692
|
} else {
|
|
3675
|
-
tempDir = await cloneWithGitHubFallback(parsed.url, options.branch || parsed.ref, spinner$
|
|
3676
|
-
spinner$
|
|
3677
|
-
spinner$
|
|
3693
|
+
tempDir = await cloneWithGitHubFallback(parsed.url, options.branch || parsed.ref, spinner$4);
|
|
3694
|
+
spinner$4.stop("仓库克隆完成");
|
|
3695
|
+
spinner$4.start("正在发现技能...");
|
|
3678
3696
|
skills = await discoverSkills(tempDir, parsed.subpath, {
|
|
3679
3697
|
includeInternal,
|
|
3680
3698
|
fullDepth: options.fullDepth
|
|
3681
3699
|
});
|
|
3682
3700
|
}
|
|
3683
3701
|
if (skills.length === 0) {
|
|
3684
|
-
spinner$
|
|
3702
|
+
spinner$4.stop(import_picocolors.default.red("未找到技能"));
|
|
3685
3703
|
outro(import_picocolors.default.red("未找到有效技能。技能需要包含 name 和 description 的 SKILL.md 文件。"));
|
|
3686
3704
|
await cleanup(tempDir);
|
|
3687
3705
|
process.exit(1);
|
|
3688
3706
|
}
|
|
3689
|
-
if (!blobResult) spinner$
|
|
3707
|
+
if (!blobResult) spinner$4.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
|
|
3690
3708
|
if (options.list) {
|
|
3691
3709
|
console.log();
|
|
3692
3710
|
log.step(import_picocolors.default.bold("可用技能"));
|
|
@@ -3800,10 +3818,10 @@ async function runAdd(args, options = {}) {
|
|
|
3800
3818
|
}
|
|
3801
3819
|
targetAgents = options.agent;
|
|
3802
3820
|
} else {
|
|
3803
|
-
spinner$
|
|
3821
|
+
spinner$4.start("正在加载 Agent...");
|
|
3804
3822
|
const installedAgents = await detectInstalledAgents();
|
|
3805
3823
|
const totalAgents = Object.keys(agents).length;
|
|
3806
|
-
spinner$
|
|
3824
|
+
spinner$4.stop(`${totalAgents} agents`);
|
|
3807
3825
|
if (installedAgents.includes("eve") && (options.yes || !agentResult.isAgent)) {
|
|
3808
3826
|
const useEve = options.yes ? true : await confirm({
|
|
3809
3827
|
message: formatEveInstallPromptMessage(selectedSkills),
|
|
@@ -4005,7 +4023,7 @@ async function runAdd(args, options = {}) {
|
|
|
4005
4023
|
process.exit(0);
|
|
4006
4024
|
}
|
|
4007
4025
|
}
|
|
4008
|
-
spinner$
|
|
4026
|
+
spinner$4.start("正在安装技能...");
|
|
4009
4027
|
const results = [];
|
|
4010
4028
|
for (const skill of selectedSkills) for (const target of installTargets) {
|
|
4011
4029
|
const { agent, subagent } = target;
|
|
@@ -4042,7 +4060,7 @@ async function runAdd(args, options = {}) {
|
|
|
4042
4060
|
...result
|
|
4043
4061
|
});
|
|
4044
4062
|
}
|
|
4045
|
-
spinner$
|
|
4063
|
+
spinner$4.stop("安装完成");
|
|
4046
4064
|
console.log();
|
|
4047
4065
|
const successful = results.filter((r) => r.success);
|
|
4048
4066
|
const failed = results.filter((r) => !r.success);
|
|
@@ -4712,12 +4730,12 @@ async function removeCommand(skillNames, options) {
|
|
|
4712
4730
|
}
|
|
4713
4731
|
const isGlobal = options.global ?? false;
|
|
4714
4732
|
const cwd = process.cwd();
|
|
4715
|
-
const spinner$
|
|
4716
|
-
spinner$
|
|
4733
|
+
const spinner$3 = spinner();
|
|
4734
|
+
spinner$3.start("正在扫描已安装的技能...");
|
|
4717
4735
|
const skillNamesSet = /* @__PURE__ */ new Set();
|
|
4718
4736
|
const scanDir = async (dir) => {
|
|
4719
4737
|
try {
|
|
4720
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
4738
|
+
const entries = await readdir$1(dir, { withFileTypes: true });
|
|
4721
4739
|
for (const entry of entries) if (entry.isDirectory()) skillNamesSet.add(entry.name);
|
|
4722
4740
|
} catch (err) {
|
|
4723
4741
|
if (err instanceof Error && err.code !== "ENOENT") log.warn(`Could not scan directory ${dir}: ${err.message}`);
|
|
@@ -4732,7 +4750,7 @@ async function removeCommand(skillNames, options) {
|
|
|
4732
4750
|
for (const subagent of getEveSubagents(cwd)) await scanDir(getEveSubagentSkillsDir(subagent, cwd));
|
|
4733
4751
|
}
|
|
4734
4752
|
const installedSkills = Array.from(skillNamesSet).sort();
|
|
4735
|
-
spinner$
|
|
4753
|
+
spinner$3.stop(`发现 ${installedSkills.length} 个已安装技能`);
|
|
4736
4754
|
if (installedSkills.length === 0) {
|
|
4737
4755
|
outro(import_picocolors.default.yellow("没有可卸载的技能。"));
|
|
4738
4756
|
return { removed: [] };
|
|
@@ -4774,7 +4792,7 @@ async function removeCommand(skillNames, options) {
|
|
|
4774
4792
|
if (options.agent && options.agent.length > 0) targetAgents = options.agent;
|
|
4775
4793
|
else {
|
|
4776
4794
|
targetAgents = Object.keys(agents);
|
|
4777
|
-
spinner$
|
|
4795
|
+
spinner$3.stop(`Targeting ${targetAgents.length} potential agent(s)`);
|
|
4778
4796
|
}
|
|
4779
4797
|
const { assertLegacyUnmanaged } = await import("./_chunks/ownership.mjs");
|
|
4780
4798
|
await assertLegacyUnmanaged(selectedSkills.flatMap((skill) => [getCanonicalPath(skill, {
|
|
@@ -4795,7 +4813,7 @@ async function removeCommand(skillNames, options) {
|
|
|
4795
4813
|
process.exit(0);
|
|
4796
4814
|
}
|
|
4797
4815
|
}
|
|
4798
|
-
spinner$
|
|
4816
|
+
spinner$3.start("正在卸载技能...");
|
|
4799
4817
|
const results = [];
|
|
4800
4818
|
for (const skillName of selectedSkills) try {
|
|
4801
4819
|
const canonicalPath = getCanonicalPath(skillName, {
|
|
@@ -4818,7 +4836,7 @@ async function removeCommand(skillNames, options) {
|
|
|
4818
4836
|
for (const pathToCleanup of pathsToCleanup) {
|
|
4819
4837
|
if (pathToCleanup === canonicalPath) continue;
|
|
4820
4838
|
try {
|
|
4821
|
-
if (await lstat(pathToCleanup).catch(() => null)) await rm(pathToCleanup, {
|
|
4839
|
+
if (await lstat$1(pathToCleanup).catch(() => null)) await rm$1(pathToCleanup, {
|
|
4822
4840
|
recursive: true,
|
|
4823
4841
|
force: true
|
|
4824
4842
|
});
|
|
@@ -4835,12 +4853,12 @@ async function removeCommand(skillNames, options) {
|
|
|
4835
4853
|
global: isGlobal,
|
|
4836
4854
|
cwd
|
|
4837
4855
|
});
|
|
4838
|
-
if (await lstat(path).catch(() => null)) {
|
|
4856
|
+
if (await lstat$1(path).catch(() => null)) {
|
|
4839
4857
|
isStillUsed = true;
|
|
4840
4858
|
break;
|
|
4841
4859
|
}
|
|
4842
4860
|
}
|
|
4843
|
-
if (!isStillUsed) await rm(canonicalPath, {
|
|
4861
|
+
if (!isStillUsed) await rm$1(canonicalPath, {
|
|
4844
4862
|
recursive: true,
|
|
4845
4863
|
force: true
|
|
4846
4864
|
});
|
|
@@ -4861,7 +4879,7 @@ async function removeCommand(skillNames, options) {
|
|
|
4861
4879
|
error: err instanceof Error ? err.message : String(err)
|
|
4862
4880
|
});
|
|
4863
4881
|
}
|
|
4864
|
-
spinner$
|
|
4882
|
+
spinner$3.stop("卸载完成");
|
|
4865
4883
|
const successful = results.filter((r) => r.success);
|
|
4866
4884
|
const failed = results.filter((r) => !r.success);
|
|
4867
4885
|
if (successful.length > 0) {
|
|
@@ -5496,7 +5514,20 @@ async function runUpdate(args = []) {
|
|
|
5496
5514
|
totalFail += failCount;
|
|
5497
5515
|
totalFound += foundCount;
|
|
5498
5516
|
}
|
|
5499
|
-
if (options.skills && totalFound === 0)
|
|
5517
|
+
if (options.skills && totalFound === 0) {
|
|
5518
|
+
console.log(`${DIM$9}未找到安装记录,按首次安装重新获取:${options.skills.join(", ")}${RESET$9}`);
|
|
5519
|
+
const { runAdd } = await Promise.resolve().then(() => add_exports);
|
|
5520
|
+
for (const skill of options.skills) try {
|
|
5521
|
+
await runAdd([skill], {
|
|
5522
|
+
yes: true,
|
|
5523
|
+
global: options.global || scope === "global"
|
|
5524
|
+
});
|
|
5525
|
+
totalSuccess += 1;
|
|
5526
|
+
} catch (error) {
|
|
5527
|
+
totalFail += 1;
|
|
5528
|
+
console.log(`${DIM$9}✗ ${skill}:${error instanceof Error ? error.message : "安装失败"}${RESET$9}`);
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5500
5531
|
console.log();
|
|
5501
5532
|
if (totalSuccess > 0) console.log(`${TEXT$7}✓ 已更新 ${totalSuccess} 个技能${RESET$9}`);
|
|
5502
5533
|
if (totalFail > 0) {
|
|
@@ -5583,14 +5614,14 @@ async function materializeUseSkill(skill) {
|
|
|
5583
5614
|
const tempRoot = await mkdtemp(join(tmpdir(), "skills-use-"));
|
|
5584
5615
|
const skillDir = join(tempRoot, sanitizeName(skill.directoryName || skill.name));
|
|
5585
5616
|
if (!isPathSafe(tempRoot, skillDir)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
5586
|
-
await mkdir(skillDir, { recursive: true });
|
|
5617
|
+
await mkdir$1(skillDir, { recursive: true });
|
|
5587
5618
|
if (skill.kind === "blob") await writeSnapshotFiles(skillDir, skill.files);
|
|
5588
5619
|
else if (skill.kind === "well-known") await writeMapFiles(skillDir, skill.files);
|
|
5589
5620
|
else await copySkillDirectory(skill.path, skillDir);
|
|
5590
5621
|
return {
|
|
5591
5622
|
tempRoot,
|
|
5592
5623
|
skillDir,
|
|
5593
|
-
skillMd: skill.rawContent ?? await readFile(join(skillDir, "SKILL.md"), "utf-8"),
|
|
5624
|
+
skillMd: skill.rawContent ?? await readFile$1(join(skillDir, "SKILL.md"), "utf-8"),
|
|
5594
5625
|
hasSupportingFiles: await containsSupportingFiles(skillDir, skillDir)
|
|
5595
5626
|
};
|
|
5596
5627
|
}
|
|
@@ -5806,13 +5837,13 @@ async function writeMapFiles(targetDir, files) {
|
|
|
5806
5837
|
async function writeSafeFile(targetDir, filePath, contents) {
|
|
5807
5838
|
const fullPath = join(targetDir, filePath);
|
|
5808
5839
|
if (!isPathSafe(targetDir, fullPath)) return;
|
|
5809
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
5810
|
-
if (typeof contents === "string") await writeFile(fullPath, contents, "utf-8");
|
|
5811
|
-
else await writeFile(fullPath, contents);
|
|
5840
|
+
await mkdir$1(dirname(fullPath), { recursive: true });
|
|
5841
|
+
if (typeof contents === "string") await writeFile$1(fullPath, contents, "utf-8");
|
|
5842
|
+
else await writeFile$1(fullPath, contents);
|
|
5812
5843
|
}
|
|
5813
5844
|
async function copySkillDirectory(src, dest) {
|
|
5814
|
-
await mkdir(dest, { recursive: true });
|
|
5815
|
-
const entries = await readdir(src, { withFileTypes: true });
|
|
5845
|
+
await mkdir$1(dest, { recursive: true });
|
|
5846
|
+
const entries = await readdir$1(src, { withFileTypes: true });
|
|
5816
5847
|
await Promise.all(entries.filter((entry) => !isExcluded(entry.name, entry.isDirectory())).map(async (entry) => {
|
|
5817
5848
|
const srcPath = join(src, entry.name);
|
|
5818
5849
|
const destPath = join(dest, entry.name);
|
|
@@ -5822,7 +5853,7 @@ async function copySkillDirectory(src, dest) {
|
|
|
5822
5853
|
return;
|
|
5823
5854
|
}
|
|
5824
5855
|
try {
|
|
5825
|
-
await cp(srcPath, destPath, {
|
|
5856
|
+
await cp$1(srcPath, destPath, {
|
|
5826
5857
|
dereference: true,
|
|
5827
5858
|
recursive: true
|
|
5828
5859
|
});
|
|
@@ -5836,7 +5867,7 @@ async function copySkillDirectory(src, dest) {
|
|
|
5836
5867
|
}));
|
|
5837
5868
|
}
|
|
5838
5869
|
async function containsSupportingFiles(rootDir, currentDir) {
|
|
5839
|
-
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
5870
|
+
const entries = await readdir$1(currentDir, { withFileTypes: true });
|
|
5840
5871
|
for (const entry of entries) {
|
|
5841
5872
|
const entryPath = join(currentDir, entry.name);
|
|
5842
5873
|
const relPath = relative(rootDir, entryPath).split(sep).join("/");
|
|
@@ -6966,6 +6997,673 @@ function maybePrintAnalyticsNotice(env = process.env) {
|
|
|
6966
6997
|
console.log();
|
|
6967
6998
|
} catch {}
|
|
6968
6999
|
}
|
|
7000
|
+
function registryUrl(value) {
|
|
7001
|
+
const url = new URL(value);
|
|
7002
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error("无效的 registry 地址。");
|
|
7003
|
+
return url.toString().replace(/\/$/, "");
|
|
7004
|
+
}
|
|
7005
|
+
function credentialForRegistry(registry, config) {
|
|
7006
|
+
return config?.token && registryUrl(registry) === registryUrl(config.registry ?? "https://bfi.yyuap.com:811") ? config.token : void 0;
|
|
7007
|
+
}
|
|
7008
|
+
async function readResponse(response, limit) {
|
|
7009
|
+
const chunks = [];
|
|
7010
|
+
let size = 0;
|
|
7011
|
+
if (!response.body) throw new Error("服务返回空响应。");
|
|
7012
|
+
const reader = response.body.getReader();
|
|
7013
|
+
try {
|
|
7014
|
+
while (true) {
|
|
7015
|
+
const chunk = await reader.read();
|
|
7016
|
+
if (chunk.done) break;
|
|
7017
|
+
size += chunk.value.length;
|
|
7018
|
+
if (size > limit) throw new Error("响应超过安全大小上限。");
|
|
7019
|
+
chunks.push(chunk.value);
|
|
7020
|
+
}
|
|
7021
|
+
} finally {
|
|
7022
|
+
await reader.cancel().catch(() => {});
|
|
7023
|
+
}
|
|
7024
|
+
return Buffer.concat(chunks);
|
|
7025
|
+
}
|
|
7026
|
+
var InstallationClient = class {
|
|
7027
|
+
registry;
|
|
7028
|
+
token;
|
|
7029
|
+
fetcher;
|
|
7030
|
+
constructor(registry, token, fetcher = fetch) {
|
|
7031
|
+
this.registry = registryUrl(registry);
|
|
7032
|
+
this.token = token;
|
|
7033
|
+
this.fetcher = fetcher;
|
|
7034
|
+
}
|
|
7035
|
+
async request(path, method = "GET", body) {
|
|
7036
|
+
const headers = { Accept: "application/json" };
|
|
7037
|
+
if (this.token) headers.Authorization = `Bearer ${this.token}`;
|
|
7038
|
+
if (method === "POST") {
|
|
7039
|
+
headers["Content-Type"] = "application/json";
|
|
7040
|
+
if (!this.token) {
|
|
7041
|
+
const initial = await this.request("/api/web/mcps?size=1");
|
|
7042
|
+
const cookie = initial.headers.get("set-cookie")?.match(/(?:^|,\s*)XSRF-TOKEN=([^;]+)/)?.[1];
|
|
7043
|
+
await initial.body?.cancel();
|
|
7044
|
+
if (!cookie) throw new Error("无法获取匿名 CSRF 凭据,请检查服务配置。");
|
|
7045
|
+
headers.Cookie = `XSRF-TOKEN=${cookie}`;
|
|
7046
|
+
headers["X-XSRF-TOKEN"] = decodeURIComponent(cookie);
|
|
7047
|
+
}
|
|
7048
|
+
}
|
|
7049
|
+
let response;
|
|
7050
|
+
try {
|
|
7051
|
+
response = await this.fetcher(`${this.registry}${path}`, {
|
|
7052
|
+
method,
|
|
7053
|
+
headers,
|
|
7054
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
7055
|
+
redirect: "error",
|
|
7056
|
+
signal: AbortSignal.timeout(12e4)
|
|
7057
|
+
});
|
|
7058
|
+
} catch {
|
|
7059
|
+
throw new Error("网络连接失败或服务重定向;未转发登录凭据。");
|
|
7060
|
+
}
|
|
7061
|
+
if (response.status === 401) throw new Error("登录无效或需要登录,请运行 bfi auth login 后重试。");
|
|
7062
|
+
if (response.status === 403) throw new Error("权限不足或 CSRF 校验失败;不会覆盖现有登录。");
|
|
7063
|
+
if (response.status >= 300 && response.status < 400) throw new Error("不允许服务重定向。");
|
|
7064
|
+
if (!response.ok) throw new Error(`安装服务请求失败(HTTP ${response.status}),请检查权限、计划有效期和服务版本。`);
|
|
7065
|
+
return response;
|
|
7066
|
+
}
|
|
7067
|
+
async plan(response, kind, ref, id) {
|
|
7068
|
+
const envelope = JSON.parse((await readResponse(response, 12 * 1024 ** 2)).toString("utf8"));
|
|
7069
|
+
if (envelope.code !== 0) throw new Error("安装服务拒绝请求。");
|
|
7070
|
+
const plan = verifyPlan(envelope.data);
|
|
7071
|
+
if (registryUrl(plan.registry) !== this.registry || plan.kind !== kind || plan.ref !== ref || id && plan.id !== id) throw new Error("计划与请求来源或资源不匹配。");
|
|
7072
|
+
if (Date.parse(plan.expiresAt) <= Date.now()) throw new Error("内容计划已过期,请重新确认最新内容。");
|
|
7073
|
+
return plan;
|
|
7074
|
+
}
|
|
7075
|
+
async resolve(kind, ref) {
|
|
7076
|
+
return this.plan(await this.request("/api/v1/installation-content-plans", "POST", {
|
|
7077
|
+
kind,
|
|
7078
|
+
ref
|
|
7079
|
+
}), kind, ref);
|
|
7080
|
+
}
|
|
7081
|
+
async get(id, kind, ref) {
|
|
7082
|
+
return this.plan(await this.request(`/api/v1/installation-content-plans/${encodeURIComponent(id)}`), kind, ref, id);
|
|
7083
|
+
}
|
|
7084
|
+
async artifact(plan) {
|
|
7085
|
+
const response = await this.request(`/api/v1/installation-content-plans/${encodeURIComponent(plan.id)}/artifact`);
|
|
7086
|
+
const bytes = await readResponse(response, 510 * 1024 ** 2);
|
|
7087
|
+
if (response.headers.get("X-Archive-SHA256") !== hash(bytes)) throw new Error("下载归档摘要不匹配。");
|
|
7088
|
+
return bytes;
|
|
7089
|
+
}
|
|
7090
|
+
async verifyAccount() {
|
|
7091
|
+
if (!this.token) throw new Error("BFI MCP 需要本机登录态,请先运行 bfi auth login;不会把 Token 写进 MCP 配置。");
|
|
7092
|
+
const response = await this.request("/api/v1/whoami");
|
|
7093
|
+
let envelope;
|
|
7094
|
+
try {
|
|
7095
|
+
envelope = JSON.parse((await readResponse(response, 1048576)).toString("utf8"));
|
|
7096
|
+
} catch {
|
|
7097
|
+
throw new Error("本机 BFI 登录响应无效;未覆盖现有登录。");
|
|
7098
|
+
}
|
|
7099
|
+
if (!envelope || typeof envelope !== "object") throw new Error("本机 BFI 登录响应无效。");
|
|
7100
|
+
if (envelope.code !== void 0 && envelope.code !== 0) throw new Error("本机 BFI 登录态验证失败。");
|
|
7101
|
+
const data = envelope.data ?? envelope;
|
|
7102
|
+
if (!data?.id && !data?.user?.id) throw new Error("本机 BFI 登录响应无效。");
|
|
7103
|
+
}
|
|
7104
|
+
};
|
|
7105
|
+
function printArchiveHeaders(globalOpts) {
|
|
7106
|
+
const { registry } = resolveConfig(globalOpts);
|
|
7107
|
+
const token = typeof globalOpts.token === "string" ? globalOpts.token : credentialForRegistry(registry, loadGlobalConfig());
|
|
7108
|
+
if (!token) throw new Error("BFI_TOKEN_REQUIRED: run bfi auth login first");
|
|
7109
|
+
console.log(JSON.stringify({ Authorization: `Bearer ${token}` }));
|
|
7110
|
+
}
|
|
7111
|
+
async function addClaudeMarketplace(namespace, globalOpts) {
|
|
7112
|
+
const { registry } = resolveConfig(globalOpts);
|
|
7113
|
+
const base = normalizeRegistry(String(registry));
|
|
7114
|
+
const url = `${base}/api/web/namespaces/${encodeURIComponent(namespace)}/plugin-marketplace.json`;
|
|
7115
|
+
const market = `skillhub-${namespace}`;
|
|
7116
|
+
const path = join$1(homedir$1(), ".claude", "settings.json");
|
|
7117
|
+
const existing = await readFile(path, "utf8").then(JSON.parse).catch(() => ({}));
|
|
7118
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) throw new Error("CLAUDE_SETTINGS_INVALID");
|
|
7119
|
+
const known = { ...existing.extraKnownMarketplaces };
|
|
7120
|
+
known[market] = { source: {
|
|
7121
|
+
source: "url",
|
|
7122
|
+
url,
|
|
7123
|
+
headersHelper: `bfi skills package archive-headers --registry ${base}`
|
|
7124
|
+
} };
|
|
7125
|
+
existing.extraKnownMarketplaces = known;
|
|
7126
|
+
await mkdir(dirname$1(path), {
|
|
7127
|
+
recursive: true,
|
|
7128
|
+
mode: 448
|
|
7129
|
+
});
|
|
7130
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
7131
|
+
await writeFile(temporary, JSON.stringify(existing, null, 2) + "\n", { mode: 384 });
|
|
7132
|
+
await rename(temporary, path);
|
|
7133
|
+
return {
|
|
7134
|
+
market,
|
|
7135
|
+
url,
|
|
7136
|
+
settings: path,
|
|
7137
|
+
next: `claude plugin install <plugin>@${market} --scope user`,
|
|
7138
|
+
message: "Claude Code 将通过 headersHelper 使用 bfi 当前登录态读取市场和归档;安装后可用 claude plugin update 更新。"
|
|
7139
|
+
};
|
|
7140
|
+
}
|
|
7141
|
+
var package_skill_identity_exports = /* @__PURE__ */ __exportAll({
|
|
7142
|
+
packageSkillName: () => packageSkillName,
|
|
7143
|
+
scopePackageSkill: () => scopePackageSkill
|
|
7144
|
+
});
|
|
7145
|
+
function packageSkillName(namespace, slug) {
|
|
7146
|
+
if (!namespace || !slug) throw new Error("Namespace and slug are required");
|
|
7147
|
+
return `bfi-skill-${createHash("sha256").update(JSON.stringify([namespace, slug])).digest("hex")}`;
|
|
7148
|
+
}
|
|
7149
|
+
function scopePackageSkill(skills, namespace, slug) {
|
|
7150
|
+
if (skills.length !== 1) throw new Error("A package reference must resolve to exactly one skill");
|
|
7151
|
+
return [{
|
|
7152
|
+
...skills[0],
|
|
7153
|
+
name: packageSkillName(namespace, slug)
|
|
7154
|
+
}];
|
|
7155
|
+
}
|
|
7156
|
+
function checkNode(node) {
|
|
7157
|
+
if (node.type === "object") {
|
|
7158
|
+
const keys = (node.children ?? []).map((child) => String(child.children?.[0]?.value));
|
|
7159
|
+
if (keys.length !== new Set(keys).size || keys.some((key) => [
|
|
7160
|
+
"__proto__",
|
|
7161
|
+
"prototype",
|
|
7162
|
+
"constructor"
|
|
7163
|
+
].includes(key))) throw new Error("配置包含重复键或不安全键,拒绝修改。");
|
|
7164
|
+
}
|
|
7165
|
+
node.children?.forEach(checkNode);
|
|
7166
|
+
}
|
|
7167
|
+
function readDocument(text, format) {
|
|
7168
|
+
if (!text.trim()) return {};
|
|
7169
|
+
try {
|
|
7170
|
+
if (format === "toml") return object(parse$1(text));
|
|
7171
|
+
const errors = [];
|
|
7172
|
+
const tree = parseTree(text, errors, {
|
|
7173
|
+
allowTrailingComma: true,
|
|
7174
|
+
disallowComments: false
|
|
7175
|
+
});
|
|
7176
|
+
if (!tree || errors.length) throw new Error();
|
|
7177
|
+
checkNode(tree);
|
|
7178
|
+
return object(JSON.parse(JSON.stringify(getNodeValue(tree))));
|
|
7179
|
+
} catch {
|
|
7180
|
+
throw new Error("现有配置解析失败,未修改任何内容。请先修复配置格式或重复键。");
|
|
7181
|
+
}
|
|
7182
|
+
}
|
|
7183
|
+
function serversIn(document, path) {
|
|
7184
|
+
let current = document;
|
|
7185
|
+
for (const key of path) {
|
|
7186
|
+
if (current[key] === void 0) return {};
|
|
7187
|
+
current = object(current[key], "MCP 配置节点");
|
|
7188
|
+
}
|
|
7189
|
+
return current;
|
|
7190
|
+
}
|
|
7191
|
+
function tablePath(line) {
|
|
7192
|
+
if (!/^\s*\[[^[]/.test(line)) return;
|
|
7193
|
+
try {
|
|
7194
|
+
let value = parse$1(`${line}\n__bfi_table_marker__ = true\n`);
|
|
7195
|
+
const path = [];
|
|
7196
|
+
while (value && typeof value === "object") {
|
|
7197
|
+
const entries = Object.entries(value);
|
|
7198
|
+
if (entries.length !== 1) return;
|
|
7199
|
+
const [key, next] = entries[0];
|
|
7200
|
+
if (key === "__bfi_table_marker__") return path;
|
|
7201
|
+
path.push(key);
|
|
7202
|
+
value = next;
|
|
7203
|
+
}
|
|
7204
|
+
} catch {
|
|
7205
|
+
return;
|
|
7206
|
+
}
|
|
7207
|
+
}
|
|
7208
|
+
function editToml(text, replacements) {
|
|
7209
|
+
const lines = text.split(/(?<=\n)/);
|
|
7210
|
+
const starts = [];
|
|
7211
|
+
let offset = 0;
|
|
7212
|
+
for (const line of lines) {
|
|
7213
|
+
if (/^\s*\[/.test(line)) starts.push({
|
|
7214
|
+
offset,
|
|
7215
|
+
path: tablePath(line.trim())
|
|
7216
|
+
});
|
|
7217
|
+
offset += line.length;
|
|
7218
|
+
}
|
|
7219
|
+
const existing = serversIn(readDocument(text, "toml"), ["mcp_servers"]);
|
|
7220
|
+
const ranges = [];
|
|
7221
|
+
for (const name of Object.keys(replacements)) {
|
|
7222
|
+
const matches = starts.flatMap((start, index) => start.path?.[0] === "mcp_servers" && start.path[1] === name ? [[start.offset, starts[index + 1]?.offset ?? text.length]] : []);
|
|
7223
|
+
if (existing[name] !== void 0 && !matches.length) throw new Error("目标 MCP 使用内联或复杂 TOML 定义,无法安全增量更新;请先改为独立 [mcp_servers.\"name\"] 表。");
|
|
7224
|
+
ranges.push(...matches);
|
|
7225
|
+
}
|
|
7226
|
+
let result = text;
|
|
7227
|
+
for (const [start, end] of ranges.sort((left, right) => right[0] - left[0])) result = result.slice(0, start) + result.slice(end);
|
|
7228
|
+
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, "")}`;
|
|
7229
|
+
if (!readDocument(result, "toml").mcp_servers) result += "\n[mcp_servers]\n";
|
|
7230
|
+
return result;
|
|
7231
|
+
}
|
|
7232
|
+
function editDocument(text, format, path, replacements) {
|
|
7233
|
+
serversIn(readDocument(text, format), path);
|
|
7234
|
+
const expected = readDocument(text, format);
|
|
7235
|
+
let parent = expected;
|
|
7236
|
+
for (const key of path) parent = object(parent[key] ?? (parent[key] = {}));
|
|
7237
|
+
for (const [name, value] of Object.entries(replacements)) if (value === void 0) delete parent[name];
|
|
7238
|
+
else parent[name] = value;
|
|
7239
|
+
let result = text.trim() ? text : format === "jsonc" ? "{}\n" : "";
|
|
7240
|
+
if (format === "toml") result = editToml(result, replacements);
|
|
7241
|
+
else for (const [name, value] of Object.entries(replacements)) result = applyEdits(result, modify(result, [...path, name], value, { formattingOptions: {
|
|
7242
|
+
insertSpaces: true,
|
|
7243
|
+
tabSize: 2,
|
|
7244
|
+
eol: text.includes("\r\n") ? "\r\n" : "\n"
|
|
7245
|
+
} }));
|
|
7246
|
+
if (!isDeepStrictEqual(readDocument(result, format), expected)) throw new Error("配置保留校验失败,未写入;请使用客户端原生配置处理复杂结构。");
|
|
7247
|
+
return result;
|
|
7248
|
+
}
|
|
7249
|
+
const agentIds = [
|
|
7250
|
+
"claude-code",
|
|
7251
|
+
"codex",
|
|
7252
|
+
"cursor",
|
|
7253
|
+
"qwen-code",
|
|
7254
|
+
"gemini-cli",
|
|
7255
|
+
"zcode",
|
|
7256
|
+
"codebuddy",
|
|
7257
|
+
"kimi-code",
|
|
7258
|
+
"opencode"
|
|
7259
|
+
];
|
|
7260
|
+
const aliases = {
|
|
7261
|
+
claude: "claude-code",
|
|
7262
|
+
qwen: "qwen-code",
|
|
7263
|
+
gemini: "gemini-cli",
|
|
7264
|
+
kimi: "kimi-code",
|
|
7265
|
+
"kimi-cli": "kimi-code",
|
|
7266
|
+
"codebuddy-code": "codebuddy"
|
|
7267
|
+
};
|
|
7268
|
+
function preferred(paths) {
|
|
7269
|
+
return paths.find((filename) => existsSync$1(filename)) ?? paths[0];
|
|
7270
|
+
}
|
|
7271
|
+
function targetFor(name, scope = "user", options = {}) {
|
|
7272
|
+
const agent = aliases[name] ?? name;
|
|
7273
|
+
if (!agentIds.includes(agent)) throw new Error(`尚未支持该 Agent 的自动配置。支持:${agentIds.join(", ")}。WorkBuddy/IDE 专有配置请使用原生导入,不猜测路径。`);
|
|
7274
|
+
const home = options.home ?? homedir$1();
|
|
7275
|
+
const cwd = options.cwd ?? process.cwd();
|
|
7276
|
+
const env = options.env ?? process.env;
|
|
7277
|
+
const user = scope === "user";
|
|
7278
|
+
const paths = {
|
|
7279
|
+
"claude-code": [user ? join$1(home, ".claude.json") : join$1(cwd, ".mcp.json")],
|
|
7280
|
+
codex: [user ? join$1(env.CODEX_HOME ?? join$1(home, ".codex"), "config.toml") : join$1(cwd, ".codex/config.toml")],
|
|
7281
|
+
cursor: [join$1(user ? home : cwd, ".cursor/mcp.json")],
|
|
7282
|
+
"qwen-code": [join$1(user ? home : cwd, ".qwen/settings.json")],
|
|
7283
|
+
"gemini-cli": [join$1(user ? home : cwd, ".gemini/settings.json")],
|
|
7284
|
+
zcode: [join$1(user ? home : cwd, user ? ".zcode/cli/config.json" : ".zcode/config.json")],
|
|
7285
|
+
codebuddy: user ? [
|
|
7286
|
+
join$1(home, ".codebuddy/.mcp.json"),
|
|
7287
|
+
join$1(home, ".codebuddy/mcp.json"),
|
|
7288
|
+
join$1(home, ".codebuddy.json")
|
|
7289
|
+
] : [join$1(cwd, ".mcp.json"), join$1(cwd, "mcp.json")],
|
|
7290
|
+
"kimi-code": [join$1(user ? env.KIMI_CODE_HOME ?? join$1(home, ".kimi-code") : join$1(cwd, ".kimi-code"), "mcp.json")],
|
|
7291
|
+
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")]
|
|
7292
|
+
};
|
|
7293
|
+
if (agent === "claude-code" && env.CLAUDE_CONFIG_DIR && !options.config) throw new Error("设置了 CLAUDE_CONFIG_DIR;请用 --config 明确 MCP 文件,避免改错作用域。");
|
|
7294
|
+
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 布局。");
|
|
7295
|
+
return {
|
|
7296
|
+
agent,
|
|
7297
|
+
scope,
|
|
7298
|
+
path: resolve$1(options.config ?? preferred(paths[agent])),
|
|
7299
|
+
format: agent === "codex" ? "toml" : "jsonc",
|
|
7300
|
+
key: agent === "codex" ? ["mcp_servers"] : agent === "opencode" ? ["mcp"] : agent === "zcode" ? ["mcp", "servers"] : ["mcpServers"]
|
|
7301
|
+
};
|
|
7302
|
+
}
|
|
7303
|
+
function hasLiteralSecrets(config) {
|
|
7304
|
+
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());
|
|
7305
|
+
const sensitive = /token|secret|password|api.?key|authorization|credential|private.?key|x-pm-(ak|sk)/i;
|
|
7306
|
+
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;
|
|
7307
|
+
if (Object.entries(config.env ?? {}).some(([key, value]) => sensitive.test(key) && literal(value))) return true;
|
|
7308
|
+
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;
|
|
7309
|
+
if (config.url) {
|
|
7310
|
+
if ([...new URL(config.url.replace(variablePattern, "__BFI_VARIABLE__")).searchParams].some(([key, value]) => sensitive.test(key) && value.replaceAll("__BFI_VARIABLE__", ""))) return true;
|
|
7311
|
+
}
|
|
7312
|
+
return false;
|
|
7313
|
+
}
|
|
7314
|
+
function encodeServer(config, target, bindings = {}, materialize = false, environment = process.env) {
|
|
7315
|
+
if (config.cwd && ![
|
|
7316
|
+
"codex",
|
|
7317
|
+
"qwen-code",
|
|
7318
|
+
"gemini-cli",
|
|
7319
|
+
"kimi-code"
|
|
7320
|
+
].includes(target.agent)) throw new Error("此宿主的 MCP cwd 字段未支持或未确证;请提供不依赖 cwd 的启动配置。");
|
|
7321
|
+
if (Object.values(bindings).some((name) => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))) throw new Error("环境变量绑定名称无效。");
|
|
7322
|
+
if (materialize && target.scope !== "user") throw new Error("凭据物化仅允许用户级配置,项目级配置只能保存引用。");
|
|
7323
|
+
if (hasLiteralSecrets(config) && !materialize) throw new Error("源配置可能含明文凭据;请改为环境变量占位符,或显式选择用户级 --materialize-inputs。");
|
|
7324
|
+
let value = materialize ? resolveVariables(config, bindings, environment) : config;
|
|
7325
|
+
if (variables(value).length && ["zcode", "kimi-code"].includes(target.agent)) throw new Error("该宿主的通用变量插值未确证。请使用用户级 --materialize-inputs,或原生秘密配置;不会保存无效占位符。");
|
|
7326
|
+
if (!materialize && target.agent !== "codex") value = mapStrings(value, (text) => text.replace(variablePattern, (_whole, key, fallback) => {
|
|
7327
|
+
const name = bindings[key] ?? key;
|
|
7328
|
+
if (fallback !== void 0 && !["claude-code", "codebuddy"].includes(target.agent)) throw new Error("此宿主不支持带默认值的变量引用。");
|
|
7329
|
+
if (target.agent === "cursor") return `\${env:${name}}`;
|
|
7330
|
+
if (target.agent === "opencode") return `{env:${name}}`;
|
|
7331
|
+
return `\${${name}${fallback === void 0 ? "" : `:-${fallback}`}}`;
|
|
7332
|
+
}));
|
|
7333
|
+
const { type, ...rest } = value;
|
|
7334
|
+
if (target.agent === "codex") {
|
|
7335
|
+
if (type === "sse") throw new Error("Codex 不支持旧 SSE 配置;请提供 Streamable HTTP 服务。");
|
|
7336
|
+
const result = {};
|
|
7337
|
+
if (type === "stdio") {
|
|
7338
|
+
result.command = value.command;
|
|
7339
|
+
if (value.args) result.args = value.args;
|
|
7340
|
+
if (value.cwd) result.cwd = value.cwd;
|
|
7341
|
+
const env = {};
|
|
7342
|
+
const inherited = [];
|
|
7343
|
+
for (const [name, text] of Object.entries(value.env ?? {})) {
|
|
7344
|
+
const match = /^\$\{(?:user_config\.)?([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(text);
|
|
7345
|
+
if (match && (bindings[match[1]] ?? match[1]) === name) inherited.push(name);
|
|
7346
|
+
else if (text.includes("${")) throw new Error("Codex env_vars 不能重命名或拼接变量;请使用同名环境变量或用户级物化。");
|
|
7347
|
+
else env[name] = text;
|
|
7348
|
+
}
|
|
7349
|
+
if (Object.keys(env).length) result.env = env;
|
|
7350
|
+
if (inherited.length) result.env_vars = inherited;
|
|
7351
|
+
if ([
|
|
7352
|
+
value.command,
|
|
7353
|
+
value.cwd,
|
|
7354
|
+
...value.args ?? []
|
|
7355
|
+
].some((item) => item?.includes("${"))) throw new Error("Codex command/args/cwd 不支持变量插值。");
|
|
7356
|
+
} else {
|
|
7357
|
+
result.url = value.url;
|
|
7358
|
+
if (value.url?.includes("${")) throw new Error("Codex URL 不支持变量插值。");
|
|
7359
|
+
const literal = {};
|
|
7360
|
+
const inherited = {};
|
|
7361
|
+
for (const [header, text] of Object.entries(value.headers ?? {})) {
|
|
7362
|
+
const match = /^\$\{(?:user_config\.)?([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(text);
|
|
7363
|
+
const bearer = /^Bearer \$\{(?:user_config\.)?([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(text);
|
|
7364
|
+
if (match) inherited[header] = bindings[match[1]] ?? match[1];
|
|
7365
|
+
else if (header.toLowerCase() === "authorization" && bearer) result.bearer_token_env_var = bindings[bearer[1]] ?? bearer[1];
|
|
7366
|
+
else if (text.includes("${")) throw new Error("Codex header 只支持整值环境引用或 Bearer 引用,不支持拼接。");
|
|
7367
|
+
else literal[header] = text;
|
|
7368
|
+
}
|
|
7369
|
+
if (Object.keys(literal).length) result.http_headers = literal;
|
|
7370
|
+
if (Object.keys(inherited).length) result.env_http_headers = inherited;
|
|
7371
|
+
}
|
|
7372
|
+
return result;
|
|
7373
|
+
}
|
|
7374
|
+
if (target.agent === "opencode") {
|
|
7375
|
+
if (value.cwd) throw new Error("OpenCode MCP 无独立 cwd 字段;请提供绝对启动路径。");
|
|
7376
|
+
return type === "stdio" ? {
|
|
7377
|
+
type: "local",
|
|
7378
|
+
command: [value.command, ...value.args ?? []],
|
|
7379
|
+
...value.env ? { environment: value.env } : {}
|
|
7380
|
+
} : {
|
|
7381
|
+
type: "remote",
|
|
7382
|
+
url: value.url,
|
|
7383
|
+
...value.headers ? { headers: value.headers } : {}
|
|
7384
|
+
};
|
|
7385
|
+
}
|
|
7386
|
+
if (["qwen-code", "gemini-cli"].includes(target.agent)) {
|
|
7387
|
+
const { url, ...fields } = rest;
|
|
7388
|
+
return {
|
|
7389
|
+
...fields,
|
|
7390
|
+
...url ? { [type === "http" ? "httpUrl" : "url"]: url } : {}
|
|
7391
|
+
};
|
|
7392
|
+
}
|
|
7393
|
+
if (target.agent === "cursor") return rest;
|
|
7394
|
+
if (target.agent === "kimi-code") return {
|
|
7395
|
+
...rest,
|
|
7396
|
+
...type === "sse" ? { transport: "sse" } : {}
|
|
7397
|
+
};
|
|
7398
|
+
return {
|
|
7399
|
+
type,
|
|
7400
|
+
...rest
|
|
7401
|
+
};
|
|
7402
|
+
}
|
|
7403
|
+
const managedKeys = /* @__PURE__ */ new Set([
|
|
7404
|
+
"type",
|
|
7405
|
+
"transport",
|
|
7406
|
+
"command",
|
|
7407
|
+
"args",
|
|
7408
|
+
"env",
|
|
7409
|
+
"environment",
|
|
7410
|
+
"cwd",
|
|
7411
|
+
"url",
|
|
7412
|
+
"httpUrl",
|
|
7413
|
+
"headers",
|
|
7414
|
+
"http_headers",
|
|
7415
|
+
"env_http_headers",
|
|
7416
|
+
"env_vars",
|
|
7417
|
+
"bearer_token_env_var"
|
|
7418
|
+
]);
|
|
7419
|
+
function mergeServer(existing, desired) {
|
|
7420
|
+
if (existing === void 0) return desired;
|
|
7421
|
+
const previous = object(existing, "已有 MCP");
|
|
7422
|
+
const extras = Object.fromEntries(Object.entries(previous).filter(([key]) => !managedKeys.has(key)));
|
|
7423
|
+
if ([
|
|
7424
|
+
"oauth",
|
|
7425
|
+
"auth",
|
|
7426
|
+
"headersHelper",
|
|
7427
|
+
"http_headers_helper",
|
|
7428
|
+
"bearerTokenEnvVar",
|
|
7429
|
+
"bearer_token"
|
|
7430
|
+
].some((key) => extras[key] !== void 0) && [
|
|
7431
|
+
"url",
|
|
7432
|
+
"httpUrl",
|
|
7433
|
+
"command",
|
|
7434
|
+
"type",
|
|
7435
|
+
"transport"
|
|
7436
|
+
].some((key) => previous[key] !== desired[key])) throw new Error("已有服务器包含宿主专有认证设置,不能安全更换启动目标;请在客户端中处理。");
|
|
7437
|
+
return {
|
|
7438
|
+
...extras,
|
|
7439
|
+
...desired
|
|
7440
|
+
};
|
|
7441
|
+
}
|
|
7442
|
+
function decodeServer(raw, target) {
|
|
7443
|
+
const source = object(raw, "MCP");
|
|
7444
|
+
if (source.cwd && ![
|
|
7445
|
+
"codex",
|
|
7446
|
+
"qwen-code",
|
|
7447
|
+
"gemini-cli",
|
|
7448
|
+
"kimi-code"
|
|
7449
|
+
].includes(target.agent)) throw new Error("此宿主 cwd 语义未确证,请使用客户端原生测试。");
|
|
7450
|
+
if (source.enabled === false || source.disabled === true) throw new Error("此 MCP 已在客户端停用;测试不会绕过停用设置。");
|
|
7451
|
+
if ([
|
|
7452
|
+
"oauth",
|
|
7453
|
+
"auth",
|
|
7454
|
+
"headersHelper",
|
|
7455
|
+
"http_headers_helper",
|
|
7456
|
+
"bearer_token",
|
|
7457
|
+
"bearerTokenEnvVar"
|
|
7458
|
+
].some((key) => source[key] !== void 0)) throw new Error("此服务器使用宿主专有认证;请用客户端原生连接测试。");
|
|
7459
|
+
let result = Object.fromEntries(Object.entries(source).filter(([key]) => managedKeys.has(key)));
|
|
7460
|
+
if (target.agent === "codex") {
|
|
7461
|
+
const { env_vars, env_http_headers, http_headers, bearer_token_env_var, ...rest } = result;
|
|
7462
|
+
result = rest;
|
|
7463
|
+
if (source.command) {
|
|
7464
|
+
const env = { ...object(source.env ?? {}) };
|
|
7465
|
+
if (env_vars !== void 0 && (!Array.isArray(env_vars) || !env_vars.every((item) => typeof item === "string"))) throw new Error("不支持的 Codex env_vars 结构。");
|
|
7466
|
+
for (const name of env_vars ?? []) env[name] = `\${${name}}`;
|
|
7467
|
+
if (Object.keys(env).length) result.env = env;
|
|
7468
|
+
} else {
|
|
7469
|
+
const headers = { ...object(http_headers ?? {}) };
|
|
7470
|
+
for (const [name, variable] of Object.entries(object(env_http_headers ?? {}))) {
|
|
7471
|
+
if (typeof variable !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(variable)) throw new Error("无效环境变量引用。");
|
|
7472
|
+
headers[name] = `\${${variable}}`;
|
|
7473
|
+
}
|
|
7474
|
+
if (typeof bearer_token_env_var === "string") headers.Authorization = `Bearer \${${bearer_token_env_var}}`;
|
|
7475
|
+
if (Object.keys(headers).length) result.headers = headers;
|
|
7476
|
+
}
|
|
7477
|
+
} else if (target.agent === "opencode") {
|
|
7478
|
+
if (source.type === "local") {
|
|
7479
|
+
if (!Array.isArray(source.command) || !source.command.length) throw new Error("OpenCode command 必须为非空数组。");
|
|
7480
|
+
result = {
|
|
7481
|
+
type: "stdio",
|
|
7482
|
+
command: source.command[0],
|
|
7483
|
+
args: source.command.slice(1),
|
|
7484
|
+
...source.environment ? { env: source.environment } : {}
|
|
7485
|
+
};
|
|
7486
|
+
} else result = {
|
|
7487
|
+
type: "http",
|
|
7488
|
+
url: source.url,
|
|
7489
|
+
...source.headers ? { headers: source.headers } : {}
|
|
7490
|
+
};
|
|
7491
|
+
result = mapStrings(result, (text) => text.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, "${$1}"));
|
|
7492
|
+
} else if (["qwen-code", "gemini-cli"].includes(target.agent)) {
|
|
7493
|
+
const { httpUrl, ...rest } = result;
|
|
7494
|
+
result = {
|
|
7495
|
+
...rest,
|
|
7496
|
+
...httpUrl ? {
|
|
7497
|
+
type: "http",
|
|
7498
|
+
url: httpUrl
|
|
7499
|
+
} : source.url ? { type: "sse" } : {}
|
|
7500
|
+
};
|
|
7501
|
+
} else if (target.agent === "cursor") result = mapStrings(result, (text) => text.replace(/\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, "${$1}"));
|
|
7502
|
+
return normalizeConfig(result);
|
|
7503
|
+
}
|
|
7504
|
+
async function checkPath(filename) {
|
|
7505
|
+
let current = resolve$1(filename);
|
|
7506
|
+
while (current !== parse(current).root) {
|
|
7507
|
+
try {
|
|
7508
|
+
const stat = await lstat(current);
|
|
7509
|
+
if (stat.isSymbolicLink() || current === resolve$1(filename) && (!stat.isFile() || stat.nlink > 1)) throw new Error("配置路径含符号链接、特殊文件或硬链接,拒绝写入。");
|
|
7510
|
+
} catch (error) {
|
|
7511
|
+
if (error.code !== "ENOENT") throw error;
|
|
7512
|
+
}
|
|
7513
|
+
current = dirname$1(current);
|
|
7514
|
+
}
|
|
7515
|
+
}
|
|
7516
|
+
async function readConfig(filename) {
|
|
7517
|
+
await checkPath(filename);
|
|
7518
|
+
try {
|
|
7519
|
+
const handle = await open(filename, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
7520
|
+
try {
|
|
7521
|
+
const stat = await handle.stat();
|
|
7522
|
+
if (!stat.isFile() || stat.size > 2097152) throw new Error("配置文件不是普通文件或超过 2 MiB。");
|
|
7523
|
+
return await handle.readFile("utf8");
|
|
7524
|
+
} finally {
|
|
7525
|
+
await handle.close();
|
|
7526
|
+
}
|
|
7527
|
+
} catch (error) {
|
|
7528
|
+
if (error.code === "ENOENT") return;
|
|
7529
|
+
throw error;
|
|
7530
|
+
}
|
|
7531
|
+
}
|
|
7532
|
+
async function identity(filename) {
|
|
7533
|
+
try {
|
|
7534
|
+
const stat = await lstat(filename, { bigint: true });
|
|
7535
|
+
return `${stat.dev}:${stat.ino}:${stat.mode}:${stat.mtimeNs}:${stat.size}`;
|
|
7536
|
+
} catch (error) {
|
|
7537
|
+
if (error.code === "ENOENT") return;
|
|
7538
|
+
throw error;
|
|
7539
|
+
}
|
|
7540
|
+
}
|
|
7541
|
+
async function writeConfig(filename, previous, next, expectedIdentity) {
|
|
7542
|
+
if (previous === next) return;
|
|
7543
|
+
await checkPath(filename);
|
|
7544
|
+
const originalIdentity = expectedIdentity ?? await identity(filename);
|
|
7545
|
+
if (previous !== void 0 && ((await lstat(filename)).mode & 128) === 0) throw new Error("配置文件只读,不放宽其权限。");
|
|
7546
|
+
await mkdir(dirname$1(filename), {
|
|
7547
|
+
recursive: true,
|
|
7548
|
+
mode: 448
|
|
7549
|
+
});
|
|
7550
|
+
const lockPath = `${filename}.bfi-mcp.lock`;
|
|
7551
|
+
const temporary = `${filename}.bfi-mcp-${randomUUID()}.tmp`;
|
|
7552
|
+
const backup = previous === void 0 ? void 0 : `${filename}.bfi-mcp-${Date.now()}-${randomUUID()}.bak`;
|
|
7553
|
+
let lock;
|
|
7554
|
+
try {
|
|
7555
|
+
lock = await open(lockPath, "wx", 384);
|
|
7556
|
+
} catch {
|
|
7557
|
+
throw new Error("无法取得配置写入锁。可能有其它安装正在进行;请核对残留 .bfi-mcp.lock 后重试。");
|
|
7558
|
+
}
|
|
7559
|
+
try {
|
|
7560
|
+
await checkPath(filename);
|
|
7561
|
+
if (await readConfig(filename) !== previous || await identity(filename) !== originalIdentity) throw new Error("配置已被其它进程修改,请重新预览并重试。");
|
|
7562
|
+
if (backup) {
|
|
7563
|
+
const handle = await open(backup, "wx", 384);
|
|
7564
|
+
try {
|
|
7565
|
+
await handle.writeFile(previous, "utf8");
|
|
7566
|
+
await handle.sync();
|
|
7567
|
+
} finally {
|
|
7568
|
+
await handle.close();
|
|
7569
|
+
}
|
|
7570
|
+
}
|
|
7571
|
+
const handle = await open(temporary, "wx", 384);
|
|
7572
|
+
try {
|
|
7573
|
+
await handle.writeFile(next, "utf8");
|
|
7574
|
+
await handle.sync();
|
|
7575
|
+
} finally {
|
|
7576
|
+
await handle.close();
|
|
7577
|
+
}
|
|
7578
|
+
await checkPath(filename);
|
|
7579
|
+
if (await readConfig(filename) !== previous || await identity(filename) !== originalIdentity) throw new Error("写入前检测到并发修改,已取消。");
|
|
7580
|
+
await rename(temporary, filename);
|
|
7581
|
+
return backup;
|
|
7582
|
+
} finally {
|
|
7583
|
+
await unlink(temporary).catch(() => {});
|
|
7584
|
+
await lock.close().catch(() => {});
|
|
7585
|
+
await unlink(lockPath).catch(() => {});
|
|
7586
|
+
}
|
|
7587
|
+
}
|
|
7588
|
+
async function loadTarget(target) {
|
|
7589
|
+
const fileIdentity = await identity(target.path);
|
|
7590
|
+
const text = await readConfig(target.path);
|
|
7591
|
+
if (await identity(target.path) !== fileIdentity) throw new Error("读取时配置发生变化,请重试。");
|
|
7592
|
+
return {
|
|
7593
|
+
text,
|
|
7594
|
+
fileIdentity,
|
|
7595
|
+
servers: serversIn(readDocument(text ?? "", target.format), target.key)
|
|
7596
|
+
};
|
|
7597
|
+
}
|
|
7598
|
+
async function planInstall(target, entries, options = {}) {
|
|
7599
|
+
if (!entries.length || entries.length > 100) throw new Error("每次需要选择 1–100 个 MCP。");
|
|
7600
|
+
const names = entries.map((entry) => validName(entry.registrationName));
|
|
7601
|
+
if (new Set(names).size !== names.length) throw new Error("安装列表包含重复名称。");
|
|
7602
|
+
const knownVariables = new Set(entries.flatMap((entry) => variables(entry.config)));
|
|
7603
|
+
if (Object.keys(options.bindings ?? {}).some((key) => !knownVariables.has(key))) throw new Error("环境绑定包含未使用的变量名,请检查 --input-env。");
|
|
7604
|
+
for (const [server, bindings] of Object.entries(options.bindingsByServer ?? {})) {
|
|
7605
|
+
const entry = entries.find((candidate) => candidate.registrationName === server);
|
|
7606
|
+
if (!entry || Object.keys(bindings).some((key) => !variables(entry.config).includes(key))) throw new Error("服务器环境绑定包含未使用的名称。");
|
|
7607
|
+
}
|
|
7608
|
+
const { text, fileIdentity, servers } = await loadTarget(target);
|
|
7609
|
+
const replacements = {};
|
|
7610
|
+
const changes = [];
|
|
7611
|
+
for (const entry of entries) {
|
|
7612
|
+
if (entry.runtime?.source === "upload") throw new Error(`MCP ${entry.registrationName} 需要上传运行包。请先下载检查并准备本地启动路径,再用 --file 安装本地配置。`);
|
|
7613
|
+
const name = entry.registrationName;
|
|
7614
|
+
const bindings = {
|
|
7615
|
+
...options.bindings,
|
|
7616
|
+
...options.bindingsByServer?.[name]
|
|
7617
|
+
};
|
|
7618
|
+
const desired = encodeServer(entry.config, target, bindings, options.materialize, options.environment);
|
|
7619
|
+
const merged = mergeServer(servers[name], desired);
|
|
7620
|
+
const action = servers[name] === void 0 ? "add" : isDeepStrictEqual(servers[name], merged) ? "unchanged" : "replace";
|
|
7621
|
+
if (action === "replace" && !options.replace) throw new Error(`同名 MCP ${name} 配置不同;未修改任何服务器。确认要替换其连接配置时使用 --replace。`);
|
|
7622
|
+
if (action !== "unchanged") replacements[name] = merged;
|
|
7623
|
+
changes.push({
|
|
7624
|
+
name,
|
|
7625
|
+
action,
|
|
7626
|
+
transport: entry.config.type,
|
|
7627
|
+
variables: variables(entry.config).map((key) => bindings[key] ?? key)
|
|
7628
|
+
});
|
|
7629
|
+
}
|
|
7630
|
+
return {
|
|
7631
|
+
target,
|
|
7632
|
+
previous: text,
|
|
7633
|
+
next: Object.keys(replacements).length ? editDocument(text ?? "", target.format, target.key, replacements) : text,
|
|
7634
|
+
fileIdentity,
|
|
7635
|
+
changes
|
|
7636
|
+
};
|
|
7637
|
+
}
|
|
7638
|
+
async function planRemove(target, names) {
|
|
7639
|
+
if (!names.length || names.length > 100) throw new Error("请明确指定 1–100 个要移除的 MCP。");
|
|
7640
|
+
const { text, fileIdentity, servers } = await loadTarget(target);
|
|
7641
|
+
const replacements = {};
|
|
7642
|
+
const changes = [...new Set(names)].map((name) => {
|
|
7643
|
+
validName(name);
|
|
7644
|
+
const action = Object.hasOwn(servers, name) ? "remove" : "absent";
|
|
7645
|
+
if (action === "remove") replacements[name] = void 0;
|
|
7646
|
+
return {
|
|
7647
|
+
name,
|
|
7648
|
+
action
|
|
7649
|
+
};
|
|
7650
|
+
});
|
|
7651
|
+
return {
|
|
7652
|
+
target,
|
|
7653
|
+
previous: text,
|
|
7654
|
+
next: Object.keys(replacements).length ? editDocument(text ?? "", target.format, target.key, replacements) : text,
|
|
7655
|
+
fileIdentity,
|
|
7656
|
+
changes
|
|
7657
|
+
};
|
|
7658
|
+
}
|
|
7659
|
+
async function applyPlan(plan) {
|
|
7660
|
+
if (plan.previous === plan.next || plan.next === void 0) return { changed: false };
|
|
7661
|
+
if (await identity(plan.target.path) !== plan.fileIdentity) throw new Error("配置在预览后发生变化,请重新运行;未写入。");
|
|
7662
|
+
return {
|
|
7663
|
+
changed: true,
|
|
7664
|
+
backup: await writeConfig(plan.target.path, plan.previous, plan.next, plan.fileIdentity)
|
|
7665
|
+
};
|
|
7666
|
+
}
|
|
6969
7667
|
var package_exports = /* @__PURE__ */ __exportAll({ runPackage: () => runPackage });
|
|
6970
7668
|
const RESET$3 = "\x1B[0m";
|
|
6971
7669
|
const BOLD$1 = "\x1B[1m";
|
|
@@ -7007,6 +7705,12 @@ async function dispatchPackage(args, globalOpts) {
|
|
|
7007
7705
|
case "up":
|
|
7008
7706
|
await handleUpdate(args.slice(1), api, globalOpts);
|
|
7009
7707
|
break;
|
|
7708
|
+
case "marketplace":
|
|
7709
|
+
await handleMarketplace(args.slice(1), globalOpts);
|
|
7710
|
+
break;
|
|
7711
|
+
case "archive-headers":
|
|
7712
|
+
printArchiveHeaders(globalOpts);
|
|
7713
|
+
break;
|
|
7010
7714
|
case "search":
|
|
7011
7715
|
await handleSearch(args.slice(1), api, globalOpts);
|
|
7012
7716
|
break;
|
|
@@ -7136,7 +7840,7 @@ function buildTargetSummaryLines(targets, installMode) {
|
|
|
7136
7840
|
const subagentNames = targets.filter((t) => t.subagent).map(targetDisplayName);
|
|
7137
7841
|
const { universal, symlinked } = splitAgentsByType(rootAgents);
|
|
7138
7842
|
if (installMode === "symlink") {
|
|
7139
|
-
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("
|
|
7843
|
+
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("共享目录(共用 .agents/skills):")} ${formatList(universal, 3)}`);
|
|
7140
7844
|
if (symlinked.length > 0) lines.push(` ${import_picocolors.default.dim("符号链接 →")} ${formatList(symlinked)}`);
|
|
7141
7845
|
if (subagentNames.length > 0) lines.push(` ${import_picocolors.default.dim("copy →")} ${formatList(subagentNames)}`);
|
|
7142
7846
|
} else {
|
|
@@ -7193,8 +7897,47 @@ async function handleInstall(args, api, globalOpts) {
|
|
|
7193
7897
|
console.log(` ${DIM$3}• ${skill.displayName || skill.slug}${RESET$3} ${DIM$3}${ref}${RESET$3}`);
|
|
7194
7898
|
}
|
|
7195
7899
|
console.log();
|
|
7900
|
+
let mcpNames = [];
|
|
7901
|
+
let mcpEntries = [];
|
|
7902
|
+
try {
|
|
7903
|
+
mcpNames = ((await api.getPackageMcps(namespace, slug))?.mcps ?? []).filter((entry) => entry?.mcpEntryId).map((entry) => entry.lastKnownName).filter((name) => typeof name === "string" && name.length > 0);
|
|
7904
|
+
} catch {}
|
|
7905
|
+
if (mcpNames.length > 0) {
|
|
7906
|
+
try {
|
|
7907
|
+
const { catalogEntries } = await import("./_chunks/registry.mjs");
|
|
7908
|
+
const { registry } = resolveConfig(globalOpts);
|
|
7909
|
+
mcpEntries = await catalogEntries(registry, mcpNames);
|
|
7910
|
+
} catch {}
|
|
7911
|
+
console.log(`${BOLD$1}关联 MCP(${mcpNames.length}):${RESET$3}`);
|
|
7912
|
+
for (const name of mcpNames) {
|
|
7913
|
+
const entry = mcpEntries.find((item) => item.registrationName === name);
|
|
7914
|
+
const keys = entry ? variables(entry.config) : [];
|
|
7915
|
+
console.log(` ${DIM$3}• ${name}${keys.length > 0 ? ` 变量:${keys.join("、")}` : ""}${RESET$3}`);
|
|
7916
|
+
}
|
|
7917
|
+
if (mcpEntries.length === 0) console.log(` ${YELLOW$1}变量信息暂不可用,安装时会读取组合包内容。${RESET$3}`);
|
|
7918
|
+
console.log();
|
|
7919
|
+
}
|
|
7920
|
+
const bindingsByServer = {};
|
|
7921
|
+
const envNames = [];
|
|
7922
|
+
const interactive = !isYes && !!process.stdin.isTTY && !!process.stdout.isTTY;
|
|
7923
|
+
const promptBinding = async (entry, key) => {
|
|
7924
|
+
let value;
|
|
7925
|
+
do {
|
|
7926
|
+
value = await password({ message: `${entry.registrationName}.${key}(输入不回显)` });
|
|
7927
|
+
if (isCancel(value)) {
|
|
7928
|
+
cancel("安装已取消");
|
|
7929
|
+
process.exit(0);
|
|
7930
|
+
}
|
|
7931
|
+
} while (!String(value).trim());
|
|
7932
|
+
const envName = `${entry.registrationName}_${key}`.replace(/[^A-Za-z0-9_]/g, "_").toUpperCase().replace(/^(?=[0-9])/, "_");
|
|
7933
|
+
process.env[envName] = String(value);
|
|
7934
|
+
(bindingsByServer[entry.registrationName] ??= {})[key] = envName;
|
|
7935
|
+
if (!envNames.includes(envName)) envNames.push(envName);
|
|
7936
|
+
};
|
|
7937
|
+
if (interactive) for (const entry of mcpEntries) for (const key of variables(entry.config)) await promptBinding(entry, key);
|
|
7196
7938
|
let targetAgents;
|
|
7197
7939
|
let installGlobally;
|
|
7940
|
+
let mcpGloballyOverride;
|
|
7198
7941
|
let installMode;
|
|
7199
7942
|
let eveSubagentTargets = [void 0];
|
|
7200
7943
|
if (requestedAgents) {
|
|
@@ -7240,7 +7983,11 @@ async function handleInstall(args, api, globalOpts) {
|
|
|
7240
7983
|
targetAgents = ["eve"];
|
|
7241
7984
|
console.log(`${DIM$3}安装到:eve agent${RESET$3}`);
|
|
7242
7985
|
} else {
|
|
7243
|
-
const selected = await selectAgentsInteractive({
|
|
7986
|
+
const selected = await selectAgentsInteractive({
|
|
7987
|
+
global: false,
|
|
7988
|
+
useMemory: false,
|
|
7989
|
+
preselectDetected: false
|
|
7990
|
+
});
|
|
7244
7991
|
if (isCancel(selected)) {
|
|
7245
7992
|
cancel("安装已取消");
|
|
7246
7993
|
process.exit(0);
|
|
@@ -7262,7 +8009,11 @@ async function handleInstall(args, api, globalOpts) {
|
|
|
7262
8009
|
const firstAgent = installedAgents[0];
|
|
7263
8010
|
console.log(`${DIM$3}安装到:${agents[firstAgent].displayName}${RESET$3}`);
|
|
7264
8011
|
} else {
|
|
7265
|
-
const selected = await selectAgentsInteractive({
|
|
8012
|
+
const selected = await selectAgentsInteractive({
|
|
8013
|
+
global: false,
|
|
8014
|
+
useMemory: false,
|
|
8015
|
+
preselectDetected: false
|
|
8016
|
+
});
|
|
7266
8017
|
if (isCancel(selected)) {
|
|
7267
8018
|
cancel("安装已取消");
|
|
7268
8019
|
process.exit(0);
|
|
@@ -7339,7 +8090,7 @@ async function handleInstall(args, api, globalOpts) {
|
|
|
7339
8090
|
const overwriteChecks = await Promise.all(skills.flatMap((skill) => installTargets.map(async (target) => ({
|
|
7340
8091
|
skillName: skill.slug,
|
|
7341
8092
|
target,
|
|
7342
|
-
installed: await isSkillInstalled(
|
|
8093
|
+
installed: await isSkillInstalled(skill.slug, target.agent, {
|
|
7343
8094
|
global: installGlobally,
|
|
7344
8095
|
eveSubagent: target.subagent
|
|
7345
8096
|
})
|
|
@@ -7349,6 +8100,17 @@ async function handleInstall(args, api, globalOpts) {
|
|
|
7349
8100
|
if (!overwriteStatus.has(skillName)) overwriteStatus.set(skillName, /* @__PURE__ */ new Map());
|
|
7350
8101
|
overwriteStatus.get(skillName).set(targetKey(target), installed);
|
|
7351
8102
|
}
|
|
8103
|
+
if (envNames.length > 0 && !installGlobally) {
|
|
8104
|
+
const choice = await confirm({
|
|
8105
|
+
message: "项目级配置不能保存明文变量。是否将关联 MCP 写入用户级配置并保存输入值?",
|
|
8106
|
+
initialValue: true
|
|
8107
|
+
});
|
|
8108
|
+
if (isCancel(choice)) {
|
|
8109
|
+
cancel("安装已取消");
|
|
8110
|
+
process.exit(0);
|
|
8111
|
+
}
|
|
8112
|
+
mcpGloballyOverride = !!choice;
|
|
8113
|
+
}
|
|
7352
8114
|
const summaryLines = [];
|
|
7353
8115
|
for (const skill of skills) {
|
|
7354
8116
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
@@ -7366,41 +8128,128 @@ async function handleInstall(args, api, globalOpts) {
|
|
|
7366
8128
|
process.exit(0);
|
|
7367
8129
|
}
|
|
7368
8130
|
}
|
|
7369
|
-
const
|
|
8131
|
+
const mcpGlobally = mcpGloballyOverride ?? installGlobally;
|
|
8132
|
+
const { mkdtemp, readdir, realpath, rm } = await import("node:fs/promises");
|
|
8133
|
+
const { join } = await import("node:path");
|
|
8134
|
+
const { extractZip } = await Promise.resolve().then(() => zip_exports);
|
|
8135
|
+
const { discoverSkills } = await import("./_chunks/skills.mjs").then((n) => n.skills_exports);
|
|
8136
|
+
const { installSkillForAgent } = await Promise.resolve().then(() => installer_exports);
|
|
7370
8137
|
const installedSkills = [];
|
|
7371
8138
|
const nonEveAgents = targetAgents.filter((a) => a !== "eve");
|
|
7372
|
-
const
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
8139
|
+
const universalTarget = targetAgents.find((agent) => isUniversalAgent(agent));
|
|
8140
|
+
const installTargetsForInstall = [
|
|
8141
|
+
...universalTarget ? [{ agent: universalTarget }] : [],
|
|
8142
|
+
...targetAgents.filter((agent) => agent !== "eve" && !isUniversalAgent(agent)).map((agent) => ({ agent })),
|
|
8143
|
+
...targetAgents.includes("eve") ? eveSubagentTargets.map((subagent) => ({
|
|
8144
|
+
agent: "eve",
|
|
8145
|
+
subagent
|
|
8146
|
+
})) : []
|
|
8147
|
+
];
|
|
8148
|
+
const work = await mkdtemp(join(await realpath(os$1.tmpdir()), "bfi-package-"));
|
|
8149
|
+
let mcpFile;
|
|
8150
|
+
try {
|
|
8151
|
+
const archive = join(work, "package.zip");
|
|
8152
|
+
const unpack = join(work, "content");
|
|
8153
|
+
const spinner$2 = spinner();
|
|
8154
|
+
spinner$2.start("正在下载组合包...");
|
|
8155
|
+
await api.downloadPackage(namespace, slug, archive);
|
|
8156
|
+
spinner$2.message("正在解压组合包...");
|
|
8157
|
+
await extractZip(archive, unpack);
|
|
8158
|
+
spinner$2.stop("组合包已就绪,开始安装技能");
|
|
8159
|
+
const entries = await readdir(unpack, { withFileTypes: true });
|
|
8160
|
+
if (entries.some((entry) => entry.isFile() && entry.name === "mcp.json")) mcpFile = join(unpack, "mcp.json");
|
|
8161
|
+
const directories = entries.filter((entry) => entry.isDirectory());
|
|
8162
|
+
if (directories.length === 0) throw new Error("组合包中没有技能目录");
|
|
8163
|
+
const installSpinner = spinner();
|
|
8164
|
+
installSpinner.start("正在安装技能...");
|
|
8165
|
+
const failures = [];
|
|
8166
|
+
for (const directory of directories) {
|
|
8167
|
+
const skill = skills.find((entry) => entry.slug === directory.name);
|
|
8168
|
+
const skillRef = skill ? `@${skill.namespace || namespace}/${skill.slug}` : directory.name;
|
|
8169
|
+
installSpinner.message(`正在安装 ${directory.name}`);
|
|
8170
|
+
try {
|
|
8171
|
+
const found = (await discoverSkills(join(unpack, directory.name)))[0];
|
|
8172
|
+
if (!found) throw new Error("未找到有效的 SKILL.md");
|
|
8173
|
+
const results = [];
|
|
8174
|
+
for (const target of installTargetsForInstall) results.push(await installSkillForAgent(found, target.agent, {
|
|
8175
|
+
global: installGlobally,
|
|
8176
|
+
cwd: process.cwd(),
|
|
8177
|
+
mode: installMode,
|
|
8178
|
+
eveSubagent: target.subagent
|
|
8179
|
+
}));
|
|
8180
|
+
const failed = results.filter((result) => !result.success);
|
|
8181
|
+
if (failed.length > 0) throw new Error(failed.map((result) => result.error).filter(Boolean).join(";") || "安装失败");
|
|
8182
|
+
installedSkills.push({
|
|
8183
|
+
ref: skillRef,
|
|
8184
|
+
name: found.name || directory.name
|
|
8185
|
+
});
|
|
8186
|
+
} catch (error) {
|
|
8187
|
+
failures.push(`${directory.name}:${error.message}`);
|
|
8188
|
+
}
|
|
8189
|
+
}
|
|
8190
|
+
installSpinner.stop(`已安装 ${installedSkills.length}/${directories.length} 个技能`);
|
|
8191
|
+
for (const failure of failures) log.error(failure);
|
|
8192
|
+
} catch (error) {
|
|
8193
|
+
await rm(work, {
|
|
8194
|
+
recursive: true,
|
|
8195
|
+
force: true
|
|
8196
|
+
});
|
|
8197
|
+
throw error;
|
|
8198
|
+
}
|
|
8199
|
+
let mcpJsonText;
|
|
8200
|
+
if (mcpFile) try {
|
|
8201
|
+
mcpJsonText = await readConfig(mcpFile);
|
|
8202
|
+
} catch {
|
|
8203
|
+
mcpJsonText = void 0;
|
|
8204
|
+
}
|
|
8205
|
+
await rm(work, {
|
|
8206
|
+
recursive: true,
|
|
8207
|
+
force: true
|
|
8208
|
+
});
|
|
8209
|
+
if (installedSkills.length > 0) {
|
|
8210
|
+
let entries = [];
|
|
7385
8211
|
try {
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
subagent
|
|
7390
|
-
})) : [{
|
|
7391
|
-
agent,
|
|
7392
|
-
subagent: void 0
|
|
7393
|
-
}]);
|
|
7394
|
-
const verified = await Promise.all(targets.map((target) => isSkillInstalled(packageSkillName(skill.namespace || namespace, skill.slug), target.agent, {
|
|
7395
|
-
global: installGlobally,
|
|
7396
|
-
eveSubagent: target.subagent
|
|
7397
|
-
})));
|
|
7398
|
-
if (verified.length === 0 || verified.some((result) => !result)) throw new Error("Skill installation could not be verified");
|
|
7399
|
-
console.log(` ${GREEN$2}✓ ${skill.slug}${RESET$3}`);
|
|
7400
|
-
installedSkills.push(skillRef);
|
|
7401
|
-
} catch (error) {
|
|
7402
|
-
console.log(` ${RED$1}✗ ${skill.slug}:${error.message}${RESET$3}`);
|
|
8212
|
+
if (mcpJsonText) entries = entriesFromJson(readDocument(mcpJsonText, "jsonc"));
|
|
8213
|
+
} catch {
|
|
8214
|
+
entries = [];
|
|
7403
8215
|
}
|
|
8216
|
+
if (entries.length === 0) entries = mcpEntries;
|
|
8217
|
+
if (entries.length > 0) {
|
|
8218
|
+
const scope = mcpGlobally ? "user" : "project";
|
|
8219
|
+
if (interactive) {
|
|
8220
|
+
for (const entry of entries) for (const key of variables(entry.config)) if (!bindingsByServer[entry.registrationName]?.[key]) await promptBinding(entry, key);
|
|
8221
|
+
}
|
|
8222
|
+
const supported = nonEveAgents.filter((agent) => agentIds.includes(agent));
|
|
8223
|
+
const skipped = nonEveAgents.filter((agent) => !agentIds.includes(agent));
|
|
8224
|
+
const lines = [];
|
|
8225
|
+
let installedMcps = 0;
|
|
8226
|
+
for (const agent of supported) try {
|
|
8227
|
+
const plan = await planInstall(targetFor(agent, scope), entries, {
|
|
8228
|
+
bindingsByServer,
|
|
8229
|
+
materialize: scope === "user",
|
|
8230
|
+
environment: process.env
|
|
8231
|
+
});
|
|
8232
|
+
const result = await applyPlan(plan);
|
|
8233
|
+
const added = plan.changes.filter((change) => change.action === "add").length;
|
|
8234
|
+
const replaced = plan.changes.filter((change) => change.action === "replace").length;
|
|
8235
|
+
const unchanged = plan.changes.filter((change) => change.action === "unchanged").length;
|
|
8236
|
+
lines.push(`${agent.padEnd(14)} ${[
|
|
8237
|
+
added > 0 ? `新增 ${added}` : "",
|
|
8238
|
+
replaced > 0 ? `更新 ${replaced}` : "",
|
|
8239
|
+
unchanged > 0 ? `未变 ${unchanged}` : "",
|
|
8240
|
+
result.changed ? "" : "无变化"
|
|
8241
|
+
].filter(Boolean).join(" · ")}`);
|
|
8242
|
+
installedMcps++;
|
|
8243
|
+
} catch (error) {
|
|
8244
|
+
lines.push(`${agent.padEnd(14)} ✗ ${error.message}`);
|
|
8245
|
+
}
|
|
8246
|
+
note(lines.join("\n"), `关联 MCP(${installedMcps}/${supported.length} 个 Agent)`);
|
|
8247
|
+
if (skipped.length > 0) log.info(`未支持自动写入 MCP 的 Agent:${skipped.join(", ")}`);
|
|
8248
|
+
if (scope === "project") {
|
|
8249
|
+
const projectVariables = [...new Set(entries.flatMap((entry) => variables(entry.config).map((key) => bindingsByServer[entry.registrationName]?.[key] ?? key)))];
|
|
8250
|
+
if (projectVariables.length > 0) log.info(`项目级 MCP 配置只保存变量引用:${projectVariables.map((name) => "${" + name + "}").join("、")}。请在启动 Agent 前导出这些环境变量;需要保存输入值时可在提示中选择写入用户级配置。`);
|
|
8251
|
+
}
|
|
8252
|
+
} else if (mcpNames.length > 0) log.warn("组合声明的 MCP 未能加载,请稍后重试或使用 bfi mcp install 单独安装。");
|
|
7404
8253
|
}
|
|
7405
8254
|
if (installedSkills.length === 0) {
|
|
7406
8255
|
console.log(`${YELLOW$1}没有确认安装成功的技能,未创建包安装记录${RESET$3}`);
|
|
@@ -7417,14 +8266,14 @@ async function handleInstall(args, api, globalOpts) {
|
|
|
7417
8266
|
version: pkgInfo.version || "latest",
|
|
7418
8267
|
skillCount: installedSkills.length
|
|
7419
8268
|
};
|
|
7420
|
-
for (const skillRef of installedSkills) {
|
|
8269
|
+
for (const { ref: skillRef, name } of installedSkills) {
|
|
7421
8270
|
const identity = parseSkillRef(skillRef);
|
|
7422
8271
|
const existing = lock.skills[skillRef];
|
|
7423
8272
|
lock.skills[skillRef] = {
|
|
7424
8273
|
...existing,
|
|
7425
8274
|
namespace: identity.namespace,
|
|
7426
8275
|
slug: identity.slug,
|
|
7427
|
-
installedName:
|
|
8276
|
+
installedName: name,
|
|
7428
8277
|
version: existing?.version || "latest",
|
|
7429
8278
|
package: {
|
|
7430
8279
|
namespace,
|
|
@@ -7433,15 +8282,7 @@ async function handleInstall(args, api, globalOpts) {
|
|
|
7433
8282
|
};
|
|
7434
8283
|
}
|
|
7435
8284
|
saveLockfile(cwd, lock);
|
|
7436
|
-
|
|
7437
|
-
const mcpNames = ((await api.getPackageMcps(namespace, slug))?.mcps ?? []).filter((entry) => entry?.mcpEntryId).map((entry) => entry.lastKnownName).filter((name) => typeof name === "string" && name.length > 0);
|
|
7438
|
-
if (mcpNames.length > 0) {
|
|
7439
|
-
console.log();
|
|
7440
|
-
console.log(`${DIM$3}该组合关联 MCP:${mcpNames.join("、")}${RESET$3}`);
|
|
7441
|
-
console.log(`${DIM$3}安装 MCP:bfi mcp install ${mcpNames.join(" ")} --agent <agent>${RESET$3}`);
|
|
7442
|
-
}
|
|
7443
|
-
} catch {}
|
|
7444
|
-
for (const skillRef of installedSkills) await reportInstallEventForSkill(api, {
|
|
8285
|
+
for (const { ref: skillRef } of installedSkills) await reportInstallEventForSkill(api, {
|
|
7445
8286
|
skillId: skills.find((entry) => `@${entry.namespace || namespace}/${entry.slug}` === skillRef)?.skillId,
|
|
7446
8287
|
agentTarget: targetAgents[0]
|
|
7447
8288
|
});
|
|
@@ -7492,10 +8333,7 @@ async function handleUpdate(args, api, globalOpts) {
|
|
|
7492
8333
|
const pkgs = lock.packages || {};
|
|
7493
8334
|
if (pkgRef) {
|
|
7494
8335
|
const { namespace, slug } = parseSkillRef(pkgRef, globalOpts.namespace);
|
|
7495
|
-
if (!pkgs[`@${namespace}/${slug}`]) {
|
|
7496
|
-
console.log(`${YELLOW$1}包未安装:@${namespace}/${slug}${RESET$3}`);
|
|
7497
|
-
return;
|
|
7498
|
-
}
|
|
8336
|
+
if (!pkgs[`@${namespace}/${slug}`]) console.log(`${DIM$3}未找到安装记录,按首次安装重新执行:@${namespace}/${slug}${RESET$3}`);
|
|
7499
8337
|
packagesToUpdate.push({
|
|
7500
8338
|
namespace,
|
|
7501
8339
|
slug
|
|
@@ -7530,6 +8368,12 @@ async function handleUpdate(args, api, globalOpts) {
|
|
|
7530
8368
|
}
|
|
7531
8369
|
console.log(`${GREEN$2}✓ 包更新完成${RESET$3}`);
|
|
7532
8370
|
}
|
|
8371
|
+
async function handleMarketplace(args, globalOpts) {
|
|
8372
|
+
const [action, namespace] = args;
|
|
8373
|
+
if (action !== "add" || !namespace) throw new Error("用法:bfi skills package marketplace add <namespace>");
|
|
8374
|
+
const result = await addClaudeMarketplace(namespace, globalOpts);
|
|
8375
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8376
|
+
}
|
|
7533
8377
|
async function handleSearch(args, api, globalOpts) {
|
|
7534
8378
|
const query = args.filter((a) => !a.startsWith("--")).join(" ") || void 0;
|
|
7535
8379
|
const json = args.includes("--json");
|
|
@@ -9822,6 +10666,11 @@ ${BOLD}━━━ 技能包管理 ━━━${RESET}
|
|
|
9822
10666
|
--prepare-native <dir> 准备完整原生目录和市场索引,审阅后交给宿主安装;不注册、不转技能
|
|
9823
10667
|
--agent <a> 目标 agent(如 claude-code、codex、zcode)
|
|
9824
10668
|
--global 安装到主目录(跨项目可用)
|
|
10669
|
+
skills package download <ref> --as plugin [--output <file>] [--format zip|tgz|tar.gz] 下载并校验插件归档(不安装、不需要 --agent)
|
|
10670
|
+
skills package prepare <ref> --as plugin --agent <a> [--dir <dir>] 生成版本化原生目录并更新市场索引
|
|
10671
|
+
skills package update <ref> --as plugin [--agent <a>] 按默认安装版本重新下载/交接,不依赖安装记录
|
|
10672
|
+
skills package marketplace add <namespace> 将命名空间注册为 Claude Code 市场(settings.json)
|
|
10673
|
+
skills package archive-headers 输出 marketplace/归档所需的认证头(供 headersHelper 调用)
|
|
9825
10674
|
|
|
9826
10675
|
${BOLD}━━━ 文档 ━━━${RESET}
|
|
9827
10676
|
docs upload <file> 上传文档
|
|
@@ -10288,4 +11137,4 @@ main().catch((e) => {
|
|
|
10288
11137
|
console.error(`✗ ${e?.message || e}`);
|
|
10289
11138
|
process.exit(1);
|
|
10290
11139
|
});
|
|
10291
|
-
export { SkillHubAPI, getAgentBaseDir };
|
|
11140
|
+
export { InstallationClient, SkillHubAPI, agentIds, applyPlan, checkPath, credentialForRegistry, decodeServer, getAgentBaseDir, loadTarget, planInstall, planRemove, readConfig, readDocument, registryUrl, serversIn, targetFor, writeConfig };
|