@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.
@@ -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,iJAAiJ;CAC1J,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
+ {"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;AAgSrE,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqjB/E"}
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": "查看已连接的 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 skills = session.skills;
965
- if (skills.length === 0) {
966
- console.log(chalk.dim("\n 暂无技能。将 SKILL.md 文件放入 ~/.til/skills/ .til/skills/ 即可加载。\n"));
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
- else {
969
- const sourceLabel = { builtin: "内置", user: "用户", project: "项目", path: "路径" };
970
- console.log(chalk.bold(`\n已加载技能 (${skills.length}):\n`));
971
- for (const s of skills) {
972
- const tag = chalk.dim(`[${sourceLabel[s.source] || s.source}]`);
973
- console.log(` ${chalk.cyan(s.name.padEnd(24))} ${tag} ${chalk.dim(s.description.slice(0, 50))}`);
974
- }
975
- console.log(chalk.dim("\n 使用 /skill:<名称> 查看技能详情。"));
976
- console.log(chalk.dim(" 自定义: ~/.til/skills/ .til/skills/\n"));
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 cfg = await loadConfig();
982
- const mcpServers = cfg.mcpServers ?? {};
983
- const serverNames = Object.keys(mcpServers);
984
- if (serverNames.length === 0) {
985
- console.log(chalk.dim("\n 暂无 MCP 服务。\n"));
986
- console.log(chalk.dim(" 在 ~/.til/config.json 中配置 mcpServers:"));
987
- console.log(chalk.dim(' { "mcpServers": { "name": { "transport": "streamable_http", "url": "..." } } }\n'));
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
- else {
990
- const mcpExt = session.extensionRunner?.loadedExtensions.find((e) => e.name === "mcp");
991
- const mcpTools = mcpExt ? [...mcpExt.tools.keys()] : [];
992
- console.log(chalk.bold(`\nMCP 服务 (${serverNames.length}):\n`));
993
- for (const name of serverNames) {
994
- const server = mcpServers[name];
995
- const rawTransport = server.transport ?? server.type;
996
- const transport = (rawTransport === "http" || rawTransport === "streamable_http" || rawTransport === "sse")
997
- ? "http"
998
- : rawTransport === "stdio" ? "stdio"
999
- : server.url ? "http" : "stdio";
1000
- const serverTools = mcpTools.filter((t) => t.startsWith(`mcp_${name}_`));
1001
- const status = serverTools.length > 0 ? chalk.green("✓ 已连接") : chalk.red("✗ 未连接");
1002
- console.log(` ${chalk.cyan(name.padEnd(20))} ${status} ${chalk.dim(transport)}`);
1003
- if (server.url)
1004
- console.log(` ${chalk.dim("URL:")} ${server.url}`);
1005
- if (server.command)
1006
- console.log(` ${chalk.dim("CMD:")} ${server.command} ${(server.args ?? []).join(" ")}`);
1007
- if (serverTools.length > 0) {
1008
- console.log(` ${chalk.dim(`工具 (${serverTools.length}):`)}`);
1009
- for (const toolKey of serverTools) {
1010
- const shortName = toolKey.replace(`mcp_${name}_`, "");
1011
- const toolDef = mcpExt?.tools.get(toolKey);
1012
- const desc = toolDef ? chalk.dim(` — ${(toolDef.description ?? "").slice(0, 50)}`) : "";
1013
- console.log(` ${chalk.white(shortName)}${desc}`);
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
- console.log();
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": {