@antaif3ng/til-work 0.3.0 → 0.4.0
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 +79 -30
- package/dist/core/config.d.ts +5 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +33 -3
- package/dist/core/config.js.map +1 -1
- package/dist/core/session.d.ts +10 -3
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +109 -8
- package/dist/core/session.js.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/modes/interactive.d.ts.map +1 -1
- package/dist/modes/interactive.js +431 -48
- package/dist/modes/interactive.js.map +1 -1
- package/dist/tools/browser.d.ts +5 -5
- package/dist/tools/browser.d.ts.map +1 -1
- package/dist/tools/browser.js +185 -135
- package/dist/tools/browser.js.map +1 -1
- package/package.json +2 -1
- package/skills/agent-browser/SKILL.md +89 -0
- package/skills/excel-xlsx/SKILL.md +103 -0
- package/skills/nano-pdf/SKILL.md +20 -0
- package/skills/word-docx/SKILL.md +104 -0
- package/skills/playwright-mcp/SKILL.md +0 -90
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAc,MAAM,aAAa,CAAC;AAChE,OAAO,EACN,UAAU,EACV,qBAAqB,EACrB,uBAAuB,EACvB,2BAA2B,GAG3B,MAAM,aAAa,CAAC;AAarB,MAAM,gBAAgB,GAA2B;IAChD,IAAI,EAAE,mDAAmD;IACzD,IAAI,EAAE,mJAAmJ;IACzJ,KAAK,EAAE,2BAA2B;IAClC,IAAI,EAAE,2DAA2D;IACjE,WAAW,EAAE,wDAAwD;IACrE,YAAY,EAAE,kEAAkE;IAChF,UAAU,EAAE,+EAA+E;IAC3F,SAAS,EAAE,kDAAkD;IAC7D,UAAU,EAAE,sFAAsF;IAClG,QAAQ,EAAE,4KAA4K;IACtL,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAc,MAAM,aAAa,CAAC;AAChE,OAAO,EACN,UAAU,EACV,qBAAqB,EACrB,uBAAuB,EACvB,2BAA2B,GAG3B,MAAM,aAAa,CAAC;AAarB,MAAM,gBAAgB,GAA2B;IAChD,IAAI,EAAE,mDAAmD;IACzD,IAAI,EAAE,mJAAmJ;IACzJ,KAAK,EAAE,2BAA2B;IAClC,IAAI,EAAE,2DAA2D;IACjE,WAAW,EAAE,wDAAwD;IACrE,YAAY,EAAE,kEAAkE;IAChF,UAAU,EAAE,+EAA+E;IAC3F,SAAS,EAAE,kDAAkD;IAC7D,UAAU,EAAE,sFAAsF;IAClG,QAAQ,EAAE,4KAA4K;IACtL,OAAO,EAAE,kUAAkU;CAC3U,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,OAA4B;IAC7D,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,kBAAkB,EAAE,GAAG,OAAO,CAAC;IAEnD,MAAM,SAAS,GAAG,KAAK;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;SACrD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,QAAQ,CAAC,IAAI,CAAC;;;;;;;;;0EAS2D,CAAC,CAAC;IAE3E,QAAQ,CAAC,IAAI,CAAC;;EAEb,SAAS,EAAE,CAAC,CAAC;IAEd,QAAQ,CAAC,IAAI,CAAC;;;;;;;;;;;;mSAYoR,CAAC,CAAC;IAEpS,IAAI,kBAAkB,EAAE,CAAC;QACxB,QAAQ,CAAC,IAAI,CAAC,6BAA6B,kBAAkB,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,6DAA6D;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IACjD,QAAQ,CAAC,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAElD,8BAA8B;IAC9B,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAC1E,MAAM,YAAY,GAAG,2BAA2B,CAAC,YAAY,CAAC,CAAC;IAC/D,IAAI,YAAY;QAAE,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAE9C,uEAAuE;IACvE,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5D,MAAM,WAAW,GAAG,qBAAqB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC1D,IAAI,WAAW;YAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC7C,CAAC;IAED,QAAQ,CAAC,IAAI,CAAC;;kBAEG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;kBACtC,IAAI,IAAI,EAAE,CAAC,kBAAkB,EAAE;uBAC1B,GAAG;QAClB,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,OAAO,EAAE;aACtC,OAAO,CAAC,OAAO;UAClB,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ;WACrB,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC,CAAC;IAE5C,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../../src/modes/interactive.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../../src/modes/interactive.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAiSrE,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqpB/E"}
|
|
@@ -5,7 +5,7 @@ import * as readline from "node:readline";
|
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { TilSession } from "../core/session.js";
|
|
8
|
-
import { getConfigPath, loadConfig, saveConfig } from "../core/config.js";
|
|
8
|
+
import { getConfigPath, loadConfig, saveConfig, saveProjectMcpServers, loadProjectMcpServers } from "../core/config.js";
|
|
9
9
|
import { VERSION } from "../version.js";
|
|
10
10
|
import { createToolCallRequestHandler } from "../core/tool-permissions.js";
|
|
11
11
|
import { formatTokenCount } from "../core/pricing.js";
|
|
@@ -16,8 +16,8 @@ const COMMANDS = {
|
|
|
16
16
|
"/help": "显示帮助信息",
|
|
17
17
|
"/clear": "清空对话历史",
|
|
18
18
|
"/model": "查看/切换/管理模型",
|
|
19
|
-
"/skills": "
|
|
20
|
-
"/mcp": "
|
|
19
|
+
"/skills": "查看/管理技能 (reload/install/enable/disable/rm)",
|
|
20
|
+
"/mcp": "查看/管理 MCP 服务 (add/rm/reload)",
|
|
21
21
|
"/extensions": "查看已加载的扩展和 MCP 服务",
|
|
22
22
|
"/memory": "查看当前记忆内容",
|
|
23
23
|
"/sessions": "查看历史会话列表",
|
|
@@ -471,6 +471,114 @@ export async function runInteractiveMode(options) {
|
|
|
471
471
|
resetMenu(menu);
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
|
+
else if (line.startsWith("/mcp ")) {
|
|
475
|
+
const mcpSub = line.slice("/mcp ".length);
|
|
476
|
+
if (!mcpSub.includes(" ")) {
|
|
477
|
+
const mcpSubcmds = [
|
|
478
|
+
{ label: "add", detail: "添加 MCP 服务", value: "/mcp add " },
|
|
479
|
+
{ label: "rm", detail: "删除 MCP 服务", value: "/mcp rm " },
|
|
480
|
+
{ label: "reload", detail: "重载所有 MCP 服务", value: "/mcp reload" },
|
|
481
|
+
];
|
|
482
|
+
const filtered = mcpSub ? mcpSubcmds.filter((e) => e.label.startsWith(mcpSub)) : mcpSubcmds;
|
|
483
|
+
if (filtered.length > 0) {
|
|
484
|
+
menu.visible = true;
|
|
485
|
+
menu.items = filtered;
|
|
486
|
+
menu.type = "command";
|
|
487
|
+
menu.selectedIndex = 0;
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
resetMenu(menu);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
else if (mcpSub.startsWith("rm ")) {
|
|
494
|
+
const nameFilter = mcpSub.slice("rm ".length);
|
|
495
|
+
const mcpExt = session.extensionRunner?.loadedExtensions.find((e) => e.name === "mcp");
|
|
496
|
+
const serverNames = new Set();
|
|
497
|
+
if (mcpExt) {
|
|
498
|
+
for (const key of mcpExt.tools.keys()) {
|
|
499
|
+
const parts = key.split("_");
|
|
500
|
+
if (parts.length >= 3 && parts[0] === "mcp")
|
|
501
|
+
serverNames.add(parts[1]);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (session.config.mcpServers) {
|
|
505
|
+
for (const name of Object.keys(session.config.mcpServers))
|
|
506
|
+
serverNames.add(name);
|
|
507
|
+
}
|
|
508
|
+
const entries = [...serverNames]
|
|
509
|
+
.filter((n) => !nameFilter || n.startsWith(nameFilter))
|
|
510
|
+
.map((n) => ({ label: n, detail: "MCP 服务", value: `/mcp rm ${n}` }));
|
|
511
|
+
if (entries.length > 0) {
|
|
512
|
+
menu.visible = true;
|
|
513
|
+
menu.items = entries;
|
|
514
|
+
menu.type = "command";
|
|
515
|
+
menu.selectedIndex = 0;
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
resetMenu(menu);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
resetMenu(menu);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
else if (line.startsWith("/skills ")) {
|
|
526
|
+
const skillsSub = line.slice("/skills ".length);
|
|
527
|
+
if (!skillsSub.includes(" ")) {
|
|
528
|
+
const skillSubcmds = [
|
|
529
|
+
{ label: "reload", detail: "重新扫描并加载技能", value: "/skills reload" },
|
|
530
|
+
{ label: "install", detail: "安装技能", value: "/skills install " },
|
|
531
|
+
{ label: "enable", detail: "启用技能", value: "/skills enable " },
|
|
532
|
+
{ label: "disable", detail: "禁用技能", value: "/skills disable " },
|
|
533
|
+
{ label: "rm", detail: "删除技能文件", value: "/skills rm " },
|
|
534
|
+
];
|
|
535
|
+
const filtered = skillsSub ? skillSubcmds.filter((e) => e.label.startsWith(skillsSub)) : skillSubcmds;
|
|
536
|
+
if (filtered.length > 0) {
|
|
537
|
+
menu.visible = true;
|
|
538
|
+
menu.items = filtered;
|
|
539
|
+
menu.type = "command";
|
|
540
|
+
menu.selectedIndex = 0;
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
resetMenu(menu);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
else if (skillsSub.startsWith("enable ")) {
|
|
547
|
+
const nameFilter = skillsSub.slice("enable ".length);
|
|
548
|
+
const disabled = session.config.disabledSkills ?? [];
|
|
549
|
+
const entries = disabled
|
|
550
|
+
.filter((n) => !nameFilter || n.startsWith(nameFilter))
|
|
551
|
+
.map((n) => ({ label: n, detail: "已禁用", value: `/skills enable ${n}` }));
|
|
552
|
+
if (entries.length > 0) {
|
|
553
|
+
menu.visible = true;
|
|
554
|
+
menu.items = entries;
|
|
555
|
+
menu.type = "command";
|
|
556
|
+
menu.selectedIndex = 0;
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
resetMenu(menu);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
else if (skillsSub.startsWith("disable ") || skillsSub.startsWith("rm ")) {
|
|
563
|
+
const cmd = skillsSub.startsWith("disable ") ? "disable" : "rm";
|
|
564
|
+
const nameFilter = skillsSub.slice(cmd.length + 1);
|
|
565
|
+
const entries = session.skills
|
|
566
|
+
.filter((s) => !nameFilter || s.name.startsWith(nameFilter))
|
|
567
|
+
.map((s) => ({ label: s.name, detail: s.source, value: `/skills ${cmd} ${s.name}` }));
|
|
568
|
+
if (entries.length > 0) {
|
|
569
|
+
menu.visible = true;
|
|
570
|
+
menu.items = entries;
|
|
571
|
+
menu.type = "command";
|
|
572
|
+
menu.selectedIndex = 0;
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
resetMenu(menu);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
resetMenu(menu);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
474
582
|
else if (line.startsWith("/") && !line.includes(" ")) {
|
|
475
583
|
const filtered = filterEntries(allEntries, line);
|
|
476
584
|
if (filtered.length > 0) {
|
|
@@ -736,6 +844,8 @@ export async function runInteractiveMode(options) {
|
|
|
736
844
|
}
|
|
737
845
|
});
|
|
738
846
|
rl.on("close", () => {
|
|
847
|
+
clearBelowArea(below, getCursorCol());
|
|
848
|
+
below.active = false;
|
|
739
849
|
printExitMessage(session).finally(() => process.exit(0));
|
|
740
850
|
});
|
|
741
851
|
// Main REPL loop
|
|
@@ -961,61 +1071,334 @@ async function handleCommand(input, session) {
|
|
|
961
1071
|
break;
|
|
962
1072
|
}
|
|
963
1073
|
case "/skills": {
|
|
964
|
-
const
|
|
965
|
-
if (
|
|
966
|
-
|
|
1074
|
+
const sub = args[0];
|
|
1075
|
+
if (!sub) {
|
|
1076
|
+
const skills = session.skills;
|
|
1077
|
+
const cfg = await loadConfig();
|
|
1078
|
+
const disabledSkills = cfg.disabledSkills ?? [];
|
|
1079
|
+
const totalCount = skills.length + disabledSkills.length;
|
|
1080
|
+
if (totalCount === 0) {
|
|
1081
|
+
console.log(chalk.dim("\n 暂无技能。将 SKILL.md 文件放入 ~/.til/skills/ 或 .til/skills/ 即可加载。\n"));
|
|
1082
|
+
}
|
|
1083
|
+
else {
|
|
1084
|
+
const sourceLabel = { builtin: "内置", user: "用户", project: "项目", path: "路径" };
|
|
1085
|
+
console.log(chalk.bold(`\n已加载技能 (${skills.length}):\n`));
|
|
1086
|
+
for (const s of skills) {
|
|
1087
|
+
const tag = chalk.dim(`[${sourceLabel[s.source] || s.source}]`);
|
|
1088
|
+
console.log(` ${chalk.green("●")} ${chalk.cyan(s.name.padEnd(24))} ${tag} ${chalk.dim(s.description.slice(0, 50))}`);
|
|
1089
|
+
}
|
|
1090
|
+
if (disabledSkills.length > 0) {
|
|
1091
|
+
console.log(chalk.bold(`\n已禁用技能 (${disabledSkills.length}):\n`));
|
|
1092
|
+
for (const name of disabledSkills) {
|
|
1093
|
+
console.log(` ${chalk.red("○")} ${chalk.dim(name)}`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
console.log(chalk.dim("\n /skill:<名称> 查看技能详情"));
|
|
1098
|
+
console.log(chalk.dim(" /skills reload 重新扫描并加载技能"));
|
|
1099
|
+
console.log(chalk.dim(" /skills install <source> 安装技能 (npx skills add)"));
|
|
1100
|
+
console.log(chalk.dim(" /skills enable <name> 启用已禁用的技能"));
|
|
1101
|
+
console.log(chalk.dim(" /skills disable <name> 禁用技能"));
|
|
1102
|
+
console.log(chalk.dim(" /skills rm <name> 删除技能文件\n"));
|
|
1103
|
+
break;
|
|
967
1104
|
}
|
|
968
|
-
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
|
|
1105
|
+
if (sub === "reload") {
|
|
1106
|
+
const reloadSpinner = new Spinner("正在重新加载技能...");
|
|
1107
|
+
reloadSpinner.start();
|
|
1108
|
+
try {
|
|
1109
|
+
await session.reloadSkills();
|
|
1110
|
+
reloadSpinner.stop(chalk.green(` ✓ 技能已重载 (${session.skills.length} 个)`));
|
|
1111
|
+
}
|
|
1112
|
+
catch (err) {
|
|
1113
|
+
reloadSpinner.stop(chalk.red(` ✗ 重载失败: ${err.message}`));
|
|
1114
|
+
}
|
|
1115
|
+
console.log();
|
|
1116
|
+
break;
|
|
977
1117
|
}
|
|
1118
|
+
if (sub === "install") {
|
|
1119
|
+
const source = args[1];
|
|
1120
|
+
if (!source) {
|
|
1121
|
+
console.log(chalk.red("\n 用法: /skills install <source>"));
|
|
1122
|
+
console.log(chalk.dim(" 示例: /skills install ivangdavila/excel-xlsx\n"));
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
console.log(chalk.dim(`\n 正在安装技能: ${source}...`));
|
|
1126
|
+
const { execSync } = await import("node:child_process");
|
|
1127
|
+
try {
|
|
1128
|
+
const output = execSync(`npx skills add ${source} -g -y`, {
|
|
1129
|
+
encoding: "utf-8",
|
|
1130
|
+
timeout: 60000,
|
|
1131
|
+
cwd: session.cwd,
|
|
1132
|
+
});
|
|
1133
|
+
console.log(chalk.green(` ✓ 安装完成`));
|
|
1134
|
+
if (output.trim())
|
|
1135
|
+
console.log(chalk.dim(` ${output.trim()}`));
|
|
1136
|
+
await session.reloadSkills();
|
|
1137
|
+
console.log(chalk.dim(` 已重载 (${session.skills.length} 个技能)\n`));
|
|
1138
|
+
}
|
|
1139
|
+
catch (err) {
|
|
1140
|
+
console.log(chalk.red(` ✗ 安装失败: ${err.message}\n`));
|
|
1141
|
+
}
|
|
1142
|
+
break;
|
|
1143
|
+
}
|
|
1144
|
+
if (sub === "enable") {
|
|
1145
|
+
const name = args[1];
|
|
1146
|
+
if (!name) {
|
|
1147
|
+
console.log(chalk.red("\n 用法: /skills enable <name>\n"));
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
const cfg = await loadConfig();
|
|
1151
|
+
const disabled = cfg.disabledSkills ?? [];
|
|
1152
|
+
if (!disabled.includes(name)) {
|
|
1153
|
+
console.log(chalk.yellow(`\n 技能 "${name}" 未被禁用\n`));
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
cfg.disabledSkills = disabled.filter((n) => n !== name);
|
|
1157
|
+
await saveConfig(cfg);
|
|
1158
|
+
await session.reloadSkills();
|
|
1159
|
+
console.log(chalk.green(`\n 已启用技能: ${name} (当前 ${session.skills.length} 个)\n`));
|
|
1160
|
+
break;
|
|
1161
|
+
}
|
|
1162
|
+
if (sub === "disable") {
|
|
1163
|
+
const name = args[1];
|
|
1164
|
+
if (!name) {
|
|
1165
|
+
console.log(chalk.red("\n 用法: /skills disable <name>\n"));
|
|
1166
|
+
break;
|
|
1167
|
+
}
|
|
1168
|
+
const skill = session.skills.find((s) => s.name === name);
|
|
1169
|
+
if (!skill) {
|
|
1170
|
+
console.log(chalk.yellow(`\n 技能 "${name}" 未找到或已禁用\n`));
|
|
1171
|
+
break;
|
|
1172
|
+
}
|
|
1173
|
+
const cfg = await loadConfig();
|
|
1174
|
+
if (!cfg.disabledSkills)
|
|
1175
|
+
cfg.disabledSkills = [];
|
|
1176
|
+
if (!cfg.disabledSkills.includes(name)) {
|
|
1177
|
+
cfg.disabledSkills.push(name);
|
|
1178
|
+
}
|
|
1179
|
+
await saveConfig(cfg);
|
|
1180
|
+
await session.reloadSkills();
|
|
1181
|
+
console.log(chalk.green(`\n 已禁用技能: ${name} (当前 ${session.skills.length} 个)\n`));
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1184
|
+
if (sub === "rm") {
|
|
1185
|
+
const name = args[1];
|
|
1186
|
+
if (!name) {
|
|
1187
|
+
console.log(chalk.red("\n 用法: /skills rm <name>\n"));
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
const { loadSkills: loadAllSkills } = await import("../core/skills.js");
|
|
1191
|
+
const allResult = loadAllSkills({ cwd: session.cwd, includeDefaults: true });
|
|
1192
|
+
const target = allResult.skills.find((s) => s.name === name);
|
|
1193
|
+
if (!target) {
|
|
1194
|
+
console.log(chalk.yellow(`\n 技能 "${name}" 未找到\n`));
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
if (target.source === "builtin") {
|
|
1198
|
+
console.log(chalk.red(`\n 内置技能不能删除,使用 /skills disable ${name} 禁用\n`));
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
const { unlink } = await import("node:fs/promises");
|
|
1202
|
+
try {
|
|
1203
|
+
await unlink(target.filePath);
|
|
1204
|
+
console.log(chalk.green(`\n 已删除: ${target.filePath}`));
|
|
1205
|
+
// Also remove from disabled list if present
|
|
1206
|
+
const cfg = await loadConfig();
|
|
1207
|
+
if (cfg.disabledSkills?.includes(name)) {
|
|
1208
|
+
cfg.disabledSkills = cfg.disabledSkills.filter((n) => n !== name);
|
|
1209
|
+
await saveConfig(cfg);
|
|
1210
|
+
}
|
|
1211
|
+
await session.reloadSkills();
|
|
1212
|
+
console.log(chalk.dim(` 已重载 (${session.skills.length} 个技能)\n`));
|
|
1213
|
+
}
|
|
1214
|
+
catch (err) {
|
|
1215
|
+
console.log(chalk.red(`\n 删除失败: ${err.message}\n`));
|
|
1216
|
+
}
|
|
1217
|
+
break;
|
|
1218
|
+
}
|
|
1219
|
+
console.log(chalk.yellow(`\n 未知子命令: ${sub}。使用 /skills 查看帮助。\n`));
|
|
978
1220
|
break;
|
|
979
1221
|
}
|
|
980
1222
|
case "/mcp": {
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1223
|
+
const sub = args[0];
|
|
1224
|
+
if (!sub) {
|
|
1225
|
+
// Display MCP status
|
|
1226
|
+
const cfg = await loadConfig();
|
|
1227
|
+
const globalMcp = cfg.mcpServers ?? {};
|
|
1228
|
+
const projectMcp = loadProjectMcpServers(session.cwd);
|
|
1229
|
+
const mergedMcp = { ...globalMcp, ...projectMcp };
|
|
1230
|
+
const serverNames = Object.keys(mergedMcp);
|
|
1231
|
+
if (serverNames.length === 0) {
|
|
1232
|
+
console.log(chalk.dim("\n 暂无 MCP 服务。\n"));
|
|
1233
|
+
console.log(chalk.dim(" 使用 /mcp add 添加服务,或在 ~/.til/config.json 中配置 mcpServers。\n"));
|
|
1234
|
+
}
|
|
1235
|
+
else {
|
|
1236
|
+
const mcpExt = session.extensionRunner?.loadedExtensions.find((e) => e.name === "mcp");
|
|
1237
|
+
const mcpTools = mcpExt ? [...mcpExt.tools.keys()] : [];
|
|
1238
|
+
console.log(chalk.bold(`\nMCP 服务 (${serverNames.length}):\n`));
|
|
1239
|
+
for (const name of serverNames) {
|
|
1240
|
+
const server = mergedMcp[name];
|
|
1241
|
+
const isProject = name in projectMcp;
|
|
1242
|
+
const rawTransport = server.transport ?? server.type;
|
|
1243
|
+
const transport = (rawTransport === "http" || rawTransport === "streamable_http" || rawTransport === "sse")
|
|
1244
|
+
? "http"
|
|
1245
|
+
: rawTransport === "stdio" ? "stdio"
|
|
1246
|
+
: server.url ? "http" : "stdio";
|
|
1247
|
+
const serverTools = mcpTools.filter((t) => t.startsWith(`mcp_${name}_`));
|
|
1248
|
+
const status = serverTools.length > 0 ? chalk.green("✓ 已连接") : chalk.red("✗ 未连接");
|
|
1249
|
+
const scope = isProject ? chalk.yellow("[项目]") : chalk.dim("[全局]");
|
|
1250
|
+
console.log(` ${chalk.cyan(name.padEnd(20))} ${status} ${chalk.dim(transport)} ${scope}`);
|
|
1251
|
+
if (server.url)
|
|
1252
|
+
console.log(` ${chalk.dim("URL:")} ${server.url}`);
|
|
1253
|
+
if (server.command)
|
|
1254
|
+
console.log(` ${chalk.dim("CMD:")} ${server.command} ${(server.args ?? []).join(" ")}`);
|
|
1255
|
+
if (serverTools.length > 0) {
|
|
1256
|
+
console.log(` ${chalk.dim(`工具 (${serverTools.length}):`)}`);
|
|
1257
|
+
for (const toolKey of serverTools) {
|
|
1258
|
+
const shortName = toolKey.replace(`mcp_${name}_`, "");
|
|
1259
|
+
const toolDef = mcpExt?.tools.get(toolKey);
|
|
1260
|
+
const desc = toolDef ? chalk.dim(` — ${(toolDef.description ?? "").slice(0, 50)}`) : "";
|
|
1261
|
+
console.log(` ${chalk.white(shortName)}${desc}`);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
console.log();
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
console.log(chalk.dim(` /mcp add <name> --stdio <cmd> [args...] 添加 stdio 服务`));
|
|
1268
|
+
console.log(chalk.dim(` /mcp add <name> --url <url> [-H key:value ...] 添加 HTTP 服务`));
|
|
1269
|
+
console.log(chalk.dim(` /mcp add <name> ... --project 添加到项目级配置`));
|
|
1270
|
+
console.log(chalk.dim(` /mcp rm <name> 删除服务`));
|
|
1271
|
+
console.log(chalk.dim(` /mcp reload 重载所有服务\n`));
|
|
1272
|
+
break;
|
|
988
1273
|
}
|
|
989
|
-
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
if (
|
|
1006
|
-
console.log(
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1274
|
+
if (sub === "add") {
|
|
1275
|
+
const name = args[1];
|
|
1276
|
+
if (!name) {
|
|
1277
|
+
console.log(chalk.red("\n 用法:"));
|
|
1278
|
+
console.log(chalk.dim(" /mcp add <name> --stdio <command> [args...]"));
|
|
1279
|
+
console.log(chalk.dim(" /mcp add <name> --url <url> [-H key:value ...]"));
|
|
1280
|
+
console.log(chalk.dim(" 添加 --project 存入项目级配置\n"));
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
const restArgs = args.slice(2);
|
|
1284
|
+
const isProject = restArgs.includes("--project");
|
|
1285
|
+
const filteredArgs = restArgs.filter((a) => a !== "--project");
|
|
1286
|
+
let serverConfig = null;
|
|
1287
|
+
if (filteredArgs.includes("--stdio")) {
|
|
1288
|
+
const stdioIdx = filteredArgs.indexOf("--stdio");
|
|
1289
|
+
const command = filteredArgs[stdioIdx + 1];
|
|
1290
|
+
if (!command) {
|
|
1291
|
+
console.log(chalk.red("\n --stdio 后需要指定命令\n"));
|
|
1292
|
+
break;
|
|
1293
|
+
}
|
|
1294
|
+
const cmdArgs = filteredArgs.slice(stdioIdx + 2).filter((a) => !a.startsWith("-H"));
|
|
1295
|
+
const env = {};
|
|
1296
|
+
// Parse -E key=value for env vars
|
|
1297
|
+
for (let i = 0; i < filteredArgs.length; i++) {
|
|
1298
|
+
if (filteredArgs[i] === "-E" && filteredArgs[i + 1]) {
|
|
1299
|
+
const [k, ...vParts] = filteredArgs[i + 1].split("=");
|
|
1300
|
+
if (k)
|
|
1301
|
+
env[k] = vParts.join("=");
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
serverConfig = {
|
|
1305
|
+
transport: "stdio",
|
|
1306
|
+
command,
|
|
1307
|
+
args: cmdArgs.length > 0 ? cmdArgs : undefined,
|
|
1308
|
+
env: Object.keys(env).length > 0 ? env : undefined,
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
else if (filteredArgs.includes("--url")) {
|
|
1312
|
+
const urlIdx = filteredArgs.indexOf("--url");
|
|
1313
|
+
const url = filteredArgs[urlIdx + 1];
|
|
1314
|
+
if (!url) {
|
|
1315
|
+
console.log(chalk.red("\n --url 后需要指定 URL\n"));
|
|
1316
|
+
break;
|
|
1317
|
+
}
|
|
1318
|
+
const headers = {};
|
|
1319
|
+
for (let i = 0; i < filteredArgs.length; i++) {
|
|
1320
|
+
if (filteredArgs[i] === "-H" && filteredArgs[i + 1]) {
|
|
1321
|
+
const colonIdx = filteredArgs[i + 1].indexOf(":");
|
|
1322
|
+
if (colonIdx > 0) {
|
|
1323
|
+
const key = filteredArgs[i + 1].slice(0, colonIdx).trim();
|
|
1324
|
+
const value = filteredArgs[i + 1].slice(colonIdx + 1).trim();
|
|
1325
|
+
headers[key] = value;
|
|
1326
|
+
}
|
|
1014
1327
|
}
|
|
1015
1328
|
}
|
|
1016
|
-
|
|
1329
|
+
serverConfig = {
|
|
1330
|
+
transport: "streamable_http",
|
|
1331
|
+
url,
|
|
1332
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
else {
|
|
1336
|
+
console.log(chalk.red("\n 需要指定 --stdio 或 --url\n"));
|
|
1337
|
+
break;
|
|
1338
|
+
}
|
|
1339
|
+
if (serverConfig) {
|
|
1340
|
+
if (isProject) {
|
|
1341
|
+
const projectServers = loadProjectMcpServers(session.cwd);
|
|
1342
|
+
projectServers[name] = serverConfig;
|
|
1343
|
+
await saveProjectMcpServers(session.cwd, projectServers);
|
|
1344
|
+
console.log(chalk.green(`\n 已添加 MCP 服务: ${name} (项目级)`));
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
const cfg = await loadConfig();
|
|
1348
|
+
if (!cfg.mcpServers)
|
|
1349
|
+
cfg.mcpServers = {};
|
|
1350
|
+
cfg.mcpServers[name] = serverConfig;
|
|
1351
|
+
await saveConfig(cfg);
|
|
1352
|
+
console.log(chalk.green(`\n 已添加 MCP 服务: ${name} (全局)`));
|
|
1353
|
+
}
|
|
1354
|
+
console.log(chalk.dim(` 使用 /mcp reload 激活\n`));
|
|
1355
|
+
}
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
if (sub === "rm") {
|
|
1359
|
+
const name = args[1];
|
|
1360
|
+
if (!name) {
|
|
1361
|
+
console.log(chalk.red("\n 用法: /mcp rm <name>\n"));
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
let removed = false;
|
|
1365
|
+
const cfg = await loadConfig();
|
|
1366
|
+
if (cfg.mcpServers?.[name]) {
|
|
1367
|
+
delete cfg.mcpServers[name];
|
|
1368
|
+
await saveConfig(cfg);
|
|
1369
|
+
removed = true;
|
|
1370
|
+
}
|
|
1371
|
+
const projectServers = loadProjectMcpServers(session.cwd);
|
|
1372
|
+
if (projectServers[name]) {
|
|
1373
|
+
delete projectServers[name];
|
|
1374
|
+
await saveProjectMcpServers(session.cwd, projectServers);
|
|
1375
|
+
removed = true;
|
|
1376
|
+
}
|
|
1377
|
+
if (removed) {
|
|
1378
|
+
console.log(chalk.green(`\n 已删除 MCP 服务: ${name}`));
|
|
1379
|
+
console.log(chalk.dim(` 使用 /mcp reload 使变更生效\n`));
|
|
1017
1380
|
}
|
|
1381
|
+
else {
|
|
1382
|
+
console.log(chalk.red(`\n MCP 服务 "${name}" 不存在\n`));
|
|
1383
|
+
}
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1386
|
+
if (sub === "reload") {
|
|
1387
|
+
const reloadSpinner = new Spinner("正在重载 MCP 服务...");
|
|
1388
|
+
reloadSpinner.start();
|
|
1389
|
+
try {
|
|
1390
|
+
await session.reloadMcpServers();
|
|
1391
|
+
const mcpExt = session.extensionRunner?.loadedExtensions.find((e) => e.name === "mcp");
|
|
1392
|
+
const toolCount = mcpExt ? mcpExt.tools.size : 0;
|
|
1393
|
+
reloadSpinner.stop(chalk.green(` ✓ MCP 服务已重载 (${toolCount} 个工具)`));
|
|
1394
|
+
}
|
|
1395
|
+
catch (err) {
|
|
1396
|
+
reloadSpinner.stop(chalk.red(` ✗ 重载失败: ${err.message}`));
|
|
1397
|
+
}
|
|
1398
|
+
console.log();
|
|
1399
|
+
break;
|
|
1018
1400
|
}
|
|
1401
|
+
console.log(chalk.yellow(`\n 未知子命令: ${sub}。使用 /mcp 查看帮助。\n`));
|
|
1019
1402
|
break;
|
|
1020
1403
|
}
|
|
1021
1404
|
case "/extensions": {
|