@24klynx/cli 0.1.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/dist/break-cache-B716oddK.mjs +71 -0
- package/dist/break-cache-B716oddK.mjs.map +1 -0
- package/dist/bughunter-DeAizlBM.mjs +32 -0
- package/dist/bughunter-DeAizlBM.mjs.map +1 -0
- package/dist/clear-C1dFE5aD.mjs +24 -0
- package/dist/clear-C1dFE5aD.mjs.map +1 -0
- package/dist/config-D-xVXTXi.mjs +2 -0
- package/dist/config-Des0z-k9.mjs +147 -0
- package/dist/config-Des0z-k9.mjs.map +1 -0
- package/dist/context-BmZ8VEan.mjs +128 -0
- package/dist/context-BmZ8VEan.mjs.map +1 -0
- package/dist/context-viz-2ZZaTL2C.mjs +61 -0
- package/dist/context-viz-2ZZaTL2C.mjs.map +1 -0
- package/dist/env-CeeZcoDI.mjs +55 -0
- package/dist/env-CeeZcoDI.mjs.map +1 -0
- package/dist/git-branch-Dn1CP6An.mjs +96 -0
- package/dist/git-branch-Dn1CP6An.mjs.map +1 -0
- package/dist/headless-launcher-I8NWyD6k.mjs +171 -0
- package/dist/headless-launcher-I8NWyD6k.mjs.map +1 -0
- package/dist/index.d.mts +970 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3243 -0
- package/dist/index.mjs.map +1 -0
- package/dist/memory-gnURjOnQ.mjs +199 -0
- package/dist/memory-gnURjOnQ.mjs.map +1 -0
- package/dist/privacy-B6Rm1Xck.mjs +114 -0
- package/dist/privacy-B6Rm1Xck.mjs.map +1 -0
- package/dist/process-lifecycle-Dg6n2QS-.mjs +784 -0
- package/dist/process-lifecycle-Dg6n2QS-.mjs.map +1 -0
- package/dist/sandbox-toggle-9akjTw3h.mjs +64 -0
- package/dist/sandbox-toggle-9akjTw3h.mjs.map +1 -0
- package/dist/stats-DjKezhTJ.mjs +73 -0
- package/dist/stats-DjKezhTJ.mjs.map +1 -0
- package/dist/status-B3Tw-Ef4.mjs +92 -0
- package/dist/status-B3Tw-Ef4.mjs.map +1 -0
- package/dist/upgrade-CREWRNeC.mjs +72 -0
- package/dist/upgrade-CREWRNeC.mjs.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
//#region src/commands/break-cache.ts
|
|
5
|
+
/**
|
|
6
|
+
* /break-cache 命令 — 清除指定缓存。
|
|
7
|
+
*
|
|
8
|
+
* 立即执行,不经过模型。支持的缓存类型:
|
|
9
|
+
* - prompt:提示词缓存
|
|
10
|
+
* - transcript:会话转录缓存
|
|
11
|
+
* - tool:工具 schema 缓存
|
|
12
|
+
* - all:清除所有缓存(默认)
|
|
13
|
+
*/
|
|
14
|
+
/** Lynx 缓存目录 */
|
|
15
|
+
const CACHE_DIR = join(homedir(), ".lynx", "cache");
|
|
16
|
+
/** 缓存类型到文件名的映射 */
|
|
17
|
+
const CACHE_FILES = {
|
|
18
|
+
prompt: ["prompt-cache.json"],
|
|
19
|
+
transcript: ["transcript-cache.json", "transcript-cache.jsonl"],
|
|
20
|
+
tool: ["tool-schema.json"]
|
|
21
|
+
};
|
|
22
|
+
/** 所有已知的缓存文件名(去重后的扁平列表) */
|
|
23
|
+
const ALL_CACHE_FILES = Object.values(CACHE_FILES).flat();
|
|
24
|
+
/**
|
|
25
|
+
* 处理 /break-cache 命令。
|
|
26
|
+
*
|
|
27
|
+
* 根据 args.cache 删除对应的缓存文件并返回中文结果。
|
|
28
|
+
* 这是 local 类型命令——不经过模型,直接在 CLI 进程中执行。
|
|
29
|
+
*/
|
|
30
|
+
async function handleBreakCacheCommand(args) {
|
|
31
|
+
ensureCacheDir();
|
|
32
|
+
const target = args.cache || "all";
|
|
33
|
+
if (target === "all") return clearAllCaches();
|
|
34
|
+
return clearSpecificCache(target);
|
|
35
|
+
}
|
|
36
|
+
/** 确保缓存目录存在(不存在则创建,避免后续操作报错)。 */
|
|
37
|
+
function ensureCacheDir() {
|
|
38
|
+
if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
/** 删除所有已知缓存文件。 */
|
|
41
|
+
function clearAllCaches() {
|
|
42
|
+
const deleted = [];
|
|
43
|
+
for (const file of ALL_CACHE_FILES) {
|
|
44
|
+
const filepath = join(CACHE_DIR, file);
|
|
45
|
+
if (existsSync(filepath)) {
|
|
46
|
+
unlinkSync(filepath);
|
|
47
|
+
deleted.push(file);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (deleted.length === 0) return { output: "没有缓存文件需要清除" };
|
|
51
|
+
return { output: `已清除 ${deleted.length} 个缓存文件:${deleted.join("、")}` };
|
|
52
|
+
}
|
|
53
|
+
/** 删除指定类型的缓存文件。 */
|
|
54
|
+
function clearSpecificCache(target) {
|
|
55
|
+
const files = CACHE_FILES[target];
|
|
56
|
+
if (!files) return { output: `未知缓存类型:${target}。可选:prompt、transcript、tool、all` };
|
|
57
|
+
const deleted = [];
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
const filepath = join(CACHE_DIR, file);
|
|
60
|
+
if (existsSync(filepath)) {
|
|
61
|
+
unlinkSync(filepath);
|
|
62
|
+
deleted.push(file);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (deleted.length === 0) return { output: `${target} 缓存不存在,无需清除` };
|
|
66
|
+
return { output: `已清除 ${target} 缓存:${deleted.join("、")}` };
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { handleBreakCacheCommand };
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=break-cache-B716oddK.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"break-cache-B716oddK.mjs","names":[],"sources":["../src/commands/break-cache.ts"],"sourcesContent":["/**\n * /break-cache 命令 — 清除指定缓存。\n *\n * 立即执行,不经过模型。支持的缓存类型:\n * - prompt:提示词缓存\n * - transcript:会话转录缓存\n * - tool:工具 schema 缓存\n * - all:清除所有缓存(默认)\n */\n\nimport { existsSync, unlinkSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\n// ── Constants ────────────────────────────────────────\n\n/** Lynx 缓存目录 */\nconst CACHE_DIR = join(homedir(), \".lynx\", \"cache\");\n\n/** 缓存类型到文件名的映射 */\nconst CACHE_FILES: Record<string, string[]> = {\n prompt: [\"prompt-cache.json\"],\n transcript: [\"transcript-cache.json\", \"transcript-cache.jsonl\"],\n tool: [\"tool-schema.json\"],\n};\n\n/** 所有已知的缓存文件名(去重后的扁平列表) */\nconst ALL_CACHE_FILES: string[] = Object.values(CACHE_FILES).flat();\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /break-cache 命令。\n *\n * 根据 args.cache 删除对应的缓存文件并返回中文结果。\n * 这是 local 类型命令——不经过模型,直接在 CLI 进程中执行。\n */\nexport async function handleBreakCacheCommand(args: {\n cache?: string;\n}): Promise<{ output: string }> {\n // 确保缓存目录存在(目录不存在时无需删除,直接返回)\n ensureCacheDir();\n\n const target = args.cache || \"all\";\n\n if (target === \"all\") {\n return clearAllCaches();\n }\n\n return clearSpecificCache(target);\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/** 确保缓存目录存在(不存在则创建,避免后续操作报错)。 */\nfunction ensureCacheDir(): void {\n if (!existsSync(CACHE_DIR)) {\n mkdirSync(CACHE_DIR, { recursive: true });\n }\n}\n\n/** 删除所有已知缓存文件。 */\nfunction clearAllCaches(): { output: string } {\n const deleted: string[] = [];\n\n for (const file of ALL_CACHE_FILES) {\n const filepath = join(CACHE_DIR, file);\n if (existsSync(filepath)) {\n unlinkSync(filepath);\n deleted.push(file);\n }\n }\n\n if (deleted.length === 0) {\n return { output: \"没有缓存文件需要清除\" };\n }\n\n return {\n output: `已清除 ${deleted.length} 个缓存文件:${deleted.join(\"、\")}`,\n };\n}\n\n/** 删除指定类型的缓存文件。 */\nfunction clearSpecificCache(target: string): { output: string } {\n const files = CACHE_FILES[target];\n\n if (!files) {\n return {\n output: `未知缓存类型:${target}。可选:prompt、transcript、tool、all`,\n };\n }\n\n const deleted: string[] = [];\n\n for (const file of files) {\n const filepath = join(CACHE_DIR, file);\n if (existsSync(filepath)) {\n unlinkSync(filepath);\n deleted.push(file);\n }\n }\n\n if (deleted.length === 0) {\n return { output: `${target} 缓存不存在,无需清除` };\n }\n\n return {\n output: `已清除 ${target} 缓存:${deleted.join(\"、\")}`,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAiBA,MAAM,YAAY,KAAK,QAAQ,GAAG,SAAS,OAAO;;AAGlD,MAAM,cAAwC;CAC5C,QAAQ,CAAC,mBAAmB;CAC5B,YAAY,CAAC,yBAAyB,wBAAwB;CAC9D,MAAM,CAAC,kBAAkB;AAC3B;;AAGA,MAAM,kBAA4B,OAAO,OAAO,WAAW,CAAC,CAAC,KAAK;;;;;;;AAUlE,eAAsB,wBAAwB,MAEd;CAE9B,eAAe;CAEf,MAAM,SAAS,KAAK,SAAS;CAE7B,IAAI,WAAW,OACb,OAAO,eAAe;CAGxB,OAAO,mBAAmB,MAAM;AAClC;;AAKA,SAAS,iBAAuB;CAC9B,IAAI,CAAC,WAAW,SAAS,GACvB,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAE5C;;AAGA,SAAS,iBAAqC;CAC5C,MAAM,UAAoB,CAAC;CAE3B,KAAK,MAAM,QAAQ,iBAAiB;EAClC,MAAM,WAAW,KAAK,WAAW,IAAI;EACrC,IAAI,WAAW,QAAQ,GAAG;GACxB,WAAW,QAAQ;GACnB,QAAQ,KAAK,IAAI;EACnB;CACF;CAEA,IAAI,QAAQ,WAAW,GACrB,OAAO,EAAE,QAAQ,aAAa;CAGhC,OAAO,EACL,QAAQ,OAAO,QAAQ,OAAO,SAAS,QAAQ,KAAK,GAAG,IACzD;AACF;;AAGA,SAAS,mBAAmB,QAAoC;CAC9D,MAAM,QAAQ,YAAY;CAE1B,IAAI,CAAC,OACH,OAAO,EACL,QAAQ,UAAU,OAAO,gCAC3B;CAGF,MAAM,UAAoB,CAAC;CAE3B,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,WAAW,KAAK,WAAW,IAAI;EACrC,IAAI,WAAW,QAAQ,GAAG;GACxB,WAAW,QAAQ;GACnB,QAAQ,KAAK,IAAI;EACnB;CACF;CAEA,IAAI,QAAQ,WAAW,GACrB,OAAO,EAAE,QAAQ,GAAG,OAAO,aAAa;CAG1C,OAAO,EACL,QAAQ,OAAO,OAAO,MAAM,QAAQ,KAAK,GAAG,IAC9C;AACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/commands/bughunter.ts
|
|
2
|
+
/**
|
|
3
|
+
* 处理 /bughunter 命令。
|
|
4
|
+
*
|
|
5
|
+
* 生成诊断指令,引导模型以只读模式排查问题。
|
|
6
|
+
* 模型可以读文件、搜索代码、查看日志,但不能写入或修改。
|
|
7
|
+
*/
|
|
8
|
+
function handleBughunterCommand(args) {
|
|
9
|
+
const lines = [
|
|
10
|
+
"# 诊断模式(只读)",
|
|
11
|
+
"",
|
|
12
|
+
"你现在处于只读诊断模式。请严格遵守以下规则:",
|
|
13
|
+
"",
|
|
14
|
+
"## 行为规则",
|
|
15
|
+
"",
|
|
16
|
+
"- **禁止修改任何文件**:不要使用 write_file、edit_file 或其他写入工具",
|
|
17
|
+
"- **禁止执行有副作用的命令**:不要 git commit、npm install、rm 等",
|
|
18
|
+
"- **允许的操作**:读文件、搜索代码、查看 Git 历史、查看日志、运行只读命令",
|
|
19
|
+
"- **如果发现问题**:报告问题位置和修复建议,但不要自己动手修改",
|
|
20
|
+
"",
|
|
21
|
+
"## 诊断流程",
|
|
22
|
+
""
|
|
23
|
+
];
|
|
24
|
+
if (args.issue && args.issue.trim().length > 0) lines.push(`用户报告的问题:${args.issue}`, "", "1. **理解问题**:仔细分析用户描述的问题,明确复现条件和影响范围", "2. **缩小范围**:通过搜索相关代码和日志来定位问题区域", "3. **检查最近变更**:运行 `git log --oneline -20` 查看最近的提交", "4. **分析相关代码**:读取可能有问题的文件,检查逻辑、边界条件、错误处理", "5. **检查测试**:找到相关的测试文件,检查是否有覆盖该场景的测试", "6. **检查日志**:查看 ~/.lynx/logs 目录下的日志文件寻找线索", "7. **写诊断报告**:");
|
|
25
|
+
else lines.push("未指定具体问题,请对代码库进行例行诊断扫描:", "", "1. **检查最近变更**:运行 `git log --oneline -20` 检查最近提交", "2. **检查类型错误**:如果项目有 typecheck 配置,检查输出", "3. **检查 lint 警告**:查找代码中的常见问题模式", "4. **检查日志**:查看 ~/.lynx/logs 目录,寻找错误或警告", "5. **检查磁盘使用**:确认 ~/.lynx 目录没有异常增长", "6. **写诊断报告**:");
|
|
26
|
+
lines.push("", "## 诊断报告格式", "", "请按以下格式输出诊断结果:", "", "```", "=== 诊断报告 ===", "", "问题概述", " [问题的简要描述]", "", "严重程度", " [严重 / 中等 / 轻微]", "", "根因分析", " [分析问题的根本原因]", "", "相关文件", " d:/path/to/file.ts:123 — [说明此行与问题的关系]", " d:/path/to/file2.ts:45 — [说明此行与问题的关系]", "", "修复建议", " [建议 1:具体步骤]", " [建议 2:具体步骤]", "", "风险提示", " [修复可能带来的副作用]", "", "测试建议", " [如何验证修复是否生效]", "```", "", "## 注意事项", "", "- 提供的文件路径和行号必须精确(通过实际读取文件确认)", "- 不要猜测 —— 如果不确定某处代码的行为,请实际读取并分析", "- 如果涉及多个问题,按严重程度排列", "- 如果一切正常,也请简短报告「未发现明显问题」");
|
|
27
|
+
return { instruction: lines.join("\n") };
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { handleBughunterCommand };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=bughunter-DeAizlBM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bughunter-DeAizlBM.mjs","names":[],"sources":["../src/commands/bughunter.ts"],"sourcesContent":["/**\n * /bughunter — 只读诊断模式。\n *\n * 返回一段中文指令,引导模型进入只读诊断模式:\n * 系统性地检查问题、分析日志和代码,但不修改任何文件。\n * 可用于调试错误、排查性能问题、审计代码等。\n */\n\n// ── Types ────────────────────────────────────────────\n\nexport interface BughunterCommandArgs {\n /** 可选,具体的 issue 描述或错误消息。 */\n issue?: string;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /bughunter 命令。\n *\n * 生成诊断指令,引导模型以只读模式排查问题。\n * 模型可以读文件、搜索代码、查看日志,但不能写入或修改。\n */\nexport function handleBughunterCommand(args: BughunterCommandArgs): { instruction: string } {\n const lines: string[] = [\n \"# 诊断模式(只读)\",\n \"\",\n \"你现在处于只读诊断模式。请严格遵守以下规则:\",\n \"\",\n \"## 行为规则\",\n \"\",\n \"- **禁止修改任何文件**:不要使用 write_file、edit_file 或其他写入工具\",\n \"- **禁止执行有副作用的命令**:不要 git commit、npm install、rm 等\",\n \"- **允许的操作**:读文件、搜索代码、查看 Git 历史、查看日志、运行只读命令\",\n \"- **如果发现问题**:报告问题位置和修复建议,但不要自己动手修改\",\n \"\",\n \"## 诊断流程\",\n \"\",\n ];\n\n if (args.issue && args.issue.trim().length > 0) {\n lines.push(\n `用户报告的问题:${args.issue}`,\n \"\",\n \"1. **理解问题**:仔细分析用户描述的问题,明确复现条件和影响范围\",\n \"2. **缩小范围**:通过搜索相关代码和日志来定位问题区域\",\n \"3. **检查最近变更**:运行 `git log --oneline -20` 查看最近的提交\",\n \"4. **分析相关代码**:读取可能有问题的文件,检查逻辑、边界条件、错误处理\",\n \"5. **检查测试**:找到相关的测试文件,检查是否有覆盖该场景的测试\",\n \"6. **检查日志**:查看 ~/.lynx/logs 目录下的日志文件寻找线索\",\n \"7. **写诊断报告**:\",\n );\n } else {\n lines.push(\n \"未指定具体问题,请对代码库进行例行诊断扫描:\",\n \"\",\n \"1. **检查最近变更**:运行 `git log --oneline -20` 检查最近提交\",\n \"2. **检查类型错误**:如果项目有 typecheck 配置,检查输出\",\n \"3. **检查 lint 警告**:查找代码中的常见问题模式\",\n \"4. **检查日志**:查看 ~/.lynx/logs 目录,寻找错误或警告\",\n \"5. **检查磁盘使用**:确认 ~/.lynx 目录没有异常增长\",\n \"6. **写诊断报告**:\",\n );\n }\n\n lines.push(\n \"\",\n \"## 诊断报告格式\",\n \"\",\n \"请按以下格式输出诊断结果:\",\n \"\",\n \"```\",\n \"=== 诊断报告 ===\",\n \"\",\n \"问题概述\",\n \" [问题的简要描述]\",\n \"\",\n \"严重程度\",\n \" [严重 / 中等 / 轻微]\",\n \"\",\n \"根因分析\",\n \" [分析问题的根本原因]\",\n \"\",\n \"相关文件\",\n \" d:/path/to/file.ts:123 — [说明此行与问题的关系]\",\n \" d:/path/to/file2.ts:45 — [说明此行与问题的关系]\",\n \"\",\n \"修复建议\",\n \" [建议 1:具体步骤]\",\n \" [建议 2:具体步骤]\",\n \"\",\n \"风险提示\",\n \" [修复可能带来的副作用]\",\n \"\",\n \"测试建议\",\n \" [如何验证修复是否生效]\",\n \"```\",\n \"\",\n \"## 注意事项\",\n \"\",\n \"- 提供的文件路径和行号必须精确(通过实际读取文件确认)\",\n \"- 不要猜测 —— 如果不确定某处代码的行为,请实际读取并分析\",\n \"- 如果涉及多个问题,按严重程度排列\",\n \"- 如果一切正常,也请简短报告「未发现明显问题」\",\n );\n\n return { instruction: lines.join(\"\\n\") };\n}\n"],"mappings":";;;;;;;AAuBA,SAAgB,uBAAuB,MAAqD;CAC1F,MAAM,QAAkB;EACtB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAEA,IAAI,KAAK,SAAS,KAAK,MAAM,KAAK,CAAC,CAAC,SAAS,GAC3C,MAAM,KACJ,WAAW,KAAK,SAChB,IACA,uCACA,kCACA,oDACA,2CACA,uCACA,4CACA,eACF;MAEA,MAAM,KACJ,0BACA,IACA,mDACA,yCACA,kCACA,0CACA,qCACA,eACF;CAGF,MAAM,KACJ,IACA,aACA,IACA,iBACA,IACA,OACA,gBACA,IACA,QACA,eACA,IACA,QACA,oBACA,IACA,QACA,iBACA,IACA,QACA,2CACA,2CACA,IACA,QACA,iBACA,iBACA,IACA,QACA,kBACA,IACA,QACA,kBACA,OACA,IACA,WACA,IACA,gCACA,mCACA,sBACA,0BACF;CAEA,OAAO,EAAE,aAAa,MAAM,KAAK,IAAI,EAAE;AACzC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/commands/clear.ts
|
|
2
|
+
/**
|
|
3
|
+
* 处理 /clear 命令。
|
|
4
|
+
*
|
|
5
|
+
* 返回一段指令文本,引导模型忽略历史并开始全新对话。
|
|
6
|
+
* 实际的上下文清理由引擎在收到指令后执行。
|
|
7
|
+
*/
|
|
8
|
+
function handleClearCommand(args) {
|
|
9
|
+
const lines = [
|
|
10
|
+
"# 清空会话",
|
|
11
|
+
"",
|
|
12
|
+
"用户请求清空当前会话。请按以下步骤处理:",
|
|
13
|
+
"",
|
|
14
|
+
"1. 忽略之前的所有对话历史,从现在开始作为全新对话",
|
|
15
|
+
"2. 告知用户:「会话已清空,有什么可以帮你的?」(或类似简短确认)",
|
|
16
|
+
"3. 等待用户的新问题,不要主动猜测或延续之前的话题"
|
|
17
|
+
];
|
|
18
|
+
if (args.sessionId) lines.push("", `目标会话:${args.sessionId}`);
|
|
19
|
+
return { instruction: lines.join("\n") };
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { handleClearCommand };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=clear-C1dFE5aD.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clear-C1dFE5aD.mjs","names":[],"sources":["../src/commands/clear.ts"],"sourcesContent":["/**\n * /clear — 清空当前会话消息,开始全新对话。\n *\n * 返回一条中文指令,引导模型忽略之前的对话历史,\n * 从当前上下文重新开始。不实际删除数据库中的消息,\n * 只影响当前对话的上下文窗口。\n */\n\n// ── Types ────────────────────────────────────────────\n\nexport interface ClearCommandArgs {\n /** 可选,指定要清空的会话 ID(默认当前会话)。 */\n sessionId?: string;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /clear 命令。\n *\n * 返回一段指令文本,引导模型忽略历史并开始全新对话。\n * 实际的上下文清理由引擎在收到指令后执行。\n */\nexport function handleClearCommand(args: ClearCommandArgs): { instruction: string } {\n const lines: string[] = [\n \"# 清空会话\",\n \"\",\n \"用户请求清空当前会话。请按以下步骤处理:\",\n \"\",\n \"1. 忽略之前的所有对话历史,从现在开始作为全新对话\",\n \"2. 告知用户:「会话已清空,有什么可以帮你的?」(或类似简短确认)\",\n \"3. 等待用户的新问题,不要主动猜测或延续之前的话题\",\n ];\n\n if (args.sessionId) {\n lines.push(\"\", `目标会话:${args.sessionId}`);\n }\n\n return { instruction: lines.join(\"\\n\") };\n}\n"],"mappings":";;;;;;;AAuBA,SAAgB,mBAAmB,MAAiD;CAClF,MAAM,QAAkB;EACtB;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAEA,IAAI,KAAK,WACP,MAAM,KAAK,IAAI,QAAQ,KAAK,WAAW;CAGzC,OAAO,EAAE,aAAa,MAAM,KAAK,IAAI,EAAE;AACzC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { resolvePaths } from "@lynx/core";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { createAnthropicProvider, createDeepSeekProvider, createOpenAiProvider } from "@lynx/llm";
|
|
5
|
+
//#region src/commands/config.ts
|
|
6
|
+
/**
|
|
7
|
+
* Config command — 统一配置文件管理与 Provider 工厂。
|
|
8
|
+
*
|
|
9
|
+
* 读取 ~/.lynx/config.json,提供类型安全的结构化配置。
|
|
10
|
+
* 同时输出 resolveConfigEnv(env 注入)和 resolveProvider(按模型名自动选型)。
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_CONFIG = {
|
|
13
|
+
model: "deepseek-v4-pro",
|
|
14
|
+
theme: "dark",
|
|
15
|
+
env: {},
|
|
16
|
+
permissions: {
|
|
17
|
+
allow: [],
|
|
18
|
+
deny: []
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function configPath() {
|
|
22
|
+
return resolvePaths().configFile;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 从磁盘加载配置,缺失字段填默认值。
|
|
26
|
+
* 文件损坏时自动备份为 .bak 并返回默认配置。
|
|
27
|
+
*/
|
|
28
|
+
function loadConfig() {
|
|
29
|
+
const path = configPath();
|
|
30
|
+
if (!existsSync(path)) return {
|
|
31
|
+
...DEFAULT_CONFIG,
|
|
32
|
+
env: {},
|
|
33
|
+
permissions: {
|
|
34
|
+
allow: [],
|
|
35
|
+
deny: []
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
try {
|
|
39
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
40
|
+
return {
|
|
41
|
+
model: raw.model ?? DEFAULT_CONFIG.model,
|
|
42
|
+
theme: raw.theme ?? DEFAULT_CONFIG.theme,
|
|
43
|
+
env: raw.env ?? {},
|
|
44
|
+
permissions: {
|
|
45
|
+
allow: raw.permissions?.allow ?? [],
|
|
46
|
+
deny: raw.permissions?.deny ?? []
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
} catch {
|
|
50
|
+
const backupPath = path + ".bak";
|
|
51
|
+
try {
|
|
52
|
+
renameSync(path, backupPath);
|
|
53
|
+
} catch {}
|
|
54
|
+
process.stderr.write(`[lynx] 警告:配置文件已损坏,已备份到 ${backupPath}。正在使用默认配置。\n`);
|
|
55
|
+
return {
|
|
56
|
+
...DEFAULT_CONFIG,
|
|
57
|
+
env: {},
|
|
58
|
+
permissions: {
|
|
59
|
+
allow: [],
|
|
60
|
+
deny: []
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 将 config.env 中的键注入 process.env(仅补未设的 key,不覆盖已有环境变量)。
|
|
67
|
+
*
|
|
68
|
+
* 优先级:已存在的 process.env > config.json env
|
|
69
|
+
*/
|
|
70
|
+
function resolveConfigEnv(config) {
|
|
71
|
+
for (const [key, value] of Object.entries(config.env)) if (process.env[key] === void 0 && value !== void 0 && value !== "") process.env[key] = value;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 按模型名自动匹配 LLM Provider。
|
|
75
|
+
*
|
|
76
|
+
* 匹配规则:
|
|
77
|
+
* - 含 `claude` → Anthropic(需 ANTHROPIC_AUTH_TOKEN 或 ANTHROPIC_API_KEY)
|
|
78
|
+
* - 含 `gpt` 或 `o1`/`o3` → OpenAI(需 OPENAI_API_KEY)
|
|
79
|
+
* - 其他 → DeepSeek(需 DEEPSEEK_API_KEY)
|
|
80
|
+
*
|
|
81
|
+
* 环境变量优先于 config.json env(resolveConfigEnv 已处理)。
|
|
82
|
+
*
|
|
83
|
+
* @throws 找不到对应 API key 时抛出中文错误。
|
|
84
|
+
*/
|
|
85
|
+
function resolveProvider(config) {
|
|
86
|
+
const model = process.env.LYNX_MODEL ?? config.model ?? "deepseek-v4-pro";
|
|
87
|
+
if (model.includes("claude")) {
|
|
88
|
+
const token = process.env.ANTHROPIC_AUTH_TOKEN ?? process.env.ANTHROPIC_API_KEY;
|
|
89
|
+
if (!token) throw new Error("未配置 Anthropic API 密钥。请在 config.json 的 env.ANTHROPIC_AUTH_TOKEN 中设置,或设置环境变量 ANTHROPIC_API_KEY。");
|
|
90
|
+
return createAnthropicProvider({
|
|
91
|
+
apiKey: token,
|
|
92
|
+
baseUrl: process.env.ANTHROPIC_BASE_URL
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (model.includes("gpt") || model.includes("o1") || model.includes("o3")) {
|
|
96
|
+
const token = process.env.OPENAI_API_KEY;
|
|
97
|
+
if (!token) throw new Error("未配置 OpenAI API 密钥。请在 config.json 的 env.OPENAI_API_KEY 中设置,或设置环境变量 OPENAI_API_KEY。");
|
|
98
|
+
return createOpenAiProvider({ apiKey: token });
|
|
99
|
+
}
|
|
100
|
+
const token = process.env.DEEPSEEK_API_KEY;
|
|
101
|
+
if (!token) throw new Error("未配置 DeepSeek API 密钥。请在 config.json 的 env.DEEPSEEK_API_KEY 中设置,或设置环境变量 DEEPSEEK_API_KEY。");
|
|
102
|
+
return createDeepSeekProvider({
|
|
103
|
+
apiKey: token,
|
|
104
|
+
baseUrl: process.env.DEEPSEEK_BASE_URL
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** 原子写入配置到磁盘(temp file + rename)。 */
|
|
108
|
+
function saveConfig(config) {
|
|
109
|
+
const path = configPath();
|
|
110
|
+
const dir = dirname(path);
|
|
111
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
112
|
+
const tmpPath = path + ".tmp";
|
|
113
|
+
const ordered = {
|
|
114
|
+
model: config.model,
|
|
115
|
+
theme: config.theme,
|
|
116
|
+
env: config.env,
|
|
117
|
+
permissions: config.permissions
|
|
118
|
+
};
|
|
119
|
+
writeFileSync(tmpPath, JSON.stringify(ordered, null, 2) + "\n", "utf-8");
|
|
120
|
+
renameSync(tmpPath, path);
|
|
121
|
+
}
|
|
122
|
+
/** 处理 `lynx config` 命令。 */
|
|
123
|
+
async function handleConfigCommand(opts) {
|
|
124
|
+
if (opts.path) {
|
|
125
|
+
process.stdout.write(`${configPath()}\n`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (opts.set) {
|
|
129
|
+
const config = loadConfig();
|
|
130
|
+
const key = opts.set;
|
|
131
|
+
if (key === "model" || key === "theme") config[key] = opts.value ?? "";
|
|
132
|
+
else if (key === "env" || key === "permissions") {
|
|
133
|
+
process.stderr.write(`[lynx] 警告:${opts.set} 是嵌套对象,请直接编辑 config.json。\n`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
saveConfig(config);
|
|
137
|
+
process.stdout.write(`${opts.set} = ${opts.value ?? ""}\n`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const config = loadConfig();
|
|
141
|
+
if (Object.keys(config).length === 0) process.stdout.write("No configuration values set.\n");
|
|
142
|
+
else for (const [key, val] of Object.entries(config)) process.stdout.write(`${key} = ${JSON.stringify(val)}\n`);
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
export { saveConfig as a, resolveProvider as i, loadConfig as n, resolveConfigEnv as r, handleConfigCommand as t };
|
|
146
|
+
|
|
147
|
+
//# sourceMappingURL=config-Des0z-k9.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-Des0z-k9.mjs","names":[],"sources":["../src/commands/config.ts"],"sourcesContent":["/**\n * Config command — 统一配置文件管理与 Provider 工厂。\n *\n * 读取 ~/.lynx/config.json,提供类型安全的结构化配置。\n * 同时输出 resolveConfigEnv(env 注入)和 resolveProvider(按模型名自动选型)。\n */\n\nimport { readFileSync, writeFileSync, existsSync, renameSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { resolvePaths } from \"@lynx/core\";\nimport type { LlmProvider } from \"@lynx/llm\";\nimport { createDeepSeekProvider, createAnthropicProvider, createOpenAiProvider } from \"@lynx/llm\";\n\n// ── Types ────────────────────────────────────────────\n\n/** Lynx 完整配置结构。对应 ~/.lynx/config.json。 */\nexport interface LynxConfig {\n /** 模型标识符(如 deepseek-v4-pro、claude-sonnet-4-6)。 */\n model: string;\n /** UI 主题(dark / light)。 */\n theme: string;\n /** 环境变量注入 — key 不存在于 process.env 时自动设置。 */\n env: Record<string, string>;\n /** 权限白/黑名单。格式 \"ToolName(args)\"。 */\n permissions: {\n allow: string[];\n deny: string[];\n };\n}\n\nexport interface ConfigCommandOptions {\n show?: boolean;\n set?: string;\n value?: string;\n path?: boolean;\n}\n\n// ── Defaults ─────────────────────────────────────────\n\nconst DEFAULT_CONFIG: LynxConfig = {\n model: \"deepseek-v4-pro\",\n theme: \"dark\",\n env: {},\n permissions: { allow: [], deny: [] },\n};\n\n// ── Helpers ──────────────────────────────────────────\n\nfunction configPath(): string {\n const paths = resolvePaths();\n return paths.configFile;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 从磁盘加载配置,缺失字段填默认值。\n * 文件损坏时自动备份为 .bak 并返回默认配置。\n */\nexport function loadConfig(): LynxConfig {\n const path = configPath();\n if (!existsSync(path))\n return { ...DEFAULT_CONFIG, env: {}, permissions: { allow: [], deny: [] } };\n try {\n const raw = JSON.parse(readFileSync(path, \"utf-8\")) as Partial<LynxConfig>;\n return {\n model: raw.model ?? DEFAULT_CONFIG.model,\n theme: raw.theme ?? DEFAULT_CONFIG.theme,\n env: raw.env ?? {},\n permissions: {\n allow: raw.permissions?.allow ?? [],\n deny: raw.permissions?.deny ?? [],\n },\n };\n } catch {\n const backupPath = path + \".bak\";\n try {\n renameSync(path, backupPath);\n } catch {\n // 备份失败不阻塞\n }\n process.stderr.write(\n `[lynx] 警告:配置文件已损坏,已备份到 ${backupPath}。正在使用默认配置。\\n`,\n );\n return { ...DEFAULT_CONFIG, env: {}, permissions: { allow: [], deny: [] } };\n }\n}\n\n/**\n * 将 config.env 中的键注入 process.env(仅补未设的 key,不覆盖已有环境变量)。\n *\n * 优先级:已存在的 process.env > config.json env\n */\nexport function resolveConfigEnv(config: LynxConfig): void {\n for (const [key, value] of Object.entries(config.env)) {\n if (process.env[key] === undefined && value !== undefined && value !== \"\") {\n process.env[key] = value;\n }\n }\n}\n\n/**\n * 按模型名自动匹配 LLM Provider。\n *\n * 匹配规则:\n * - 含 `claude` → Anthropic(需 ANTHROPIC_AUTH_TOKEN 或 ANTHROPIC_API_KEY)\n * - 含 `gpt` 或 `o1`/`o3` → OpenAI(需 OPENAI_API_KEY)\n * - 其他 → DeepSeek(需 DEEPSEEK_API_KEY)\n *\n * 环境变量优先于 config.json env(resolveConfigEnv 已处理)。\n *\n * @throws 找不到对应 API key 时抛出中文错误。\n */\nexport function resolveProvider(config: LynxConfig): LlmProvider {\n const model = process.env.LYNX_MODEL ?? config.model ?? \"deepseek-v4-pro\";\n\n if (model.includes(\"claude\")) {\n const token = process.env.ANTHROPIC_AUTH_TOKEN ?? process.env.ANTHROPIC_API_KEY;\n if (!token) {\n throw new Error(\n \"未配置 Anthropic API 密钥。请在 config.json 的 env.ANTHROPIC_AUTH_TOKEN 中设置,\" +\n \"或设置环境变量 ANTHROPIC_API_KEY。\",\n );\n }\n return createAnthropicProvider({\n apiKey: token,\n baseUrl: process.env.ANTHROPIC_BASE_URL,\n });\n }\n\n if (model.includes(\"gpt\") || model.includes(\"o1\") || model.includes(\"o3\")) {\n const token = process.env.OPENAI_API_KEY;\n if (!token) {\n throw new Error(\n \"未配置 OpenAI API 密钥。请在 config.json 的 env.OPENAI_API_KEY 中设置,\" +\n \"或设置环境变量 OPENAI_API_KEY。\",\n );\n }\n return createOpenAiProvider({ apiKey: token });\n }\n\n // 默认:DeepSeek\n const token = process.env.DEEPSEEK_API_KEY;\n if (!token) {\n throw new Error(\n \"未配置 DeepSeek API 密钥。请在 config.json 的 env.DEEPSEEK_API_KEY 中设置,\" +\n \"或设置环境变量 DEEPSEEK_API_KEY。\",\n );\n }\n return createDeepSeekProvider({\n apiKey: token,\n baseUrl: process.env.DEEPSEEK_BASE_URL,\n });\n}\n\n/** 原子写入配置到磁盘(temp file + rename)。 */\nexport function saveConfig(config: LynxConfig): void {\n const path = configPath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n const tmpPath = path + \".tmp\";\n // 排序键以保证 diff 友好\n const ordered = {\n model: config.model,\n theme: config.theme,\n env: config.env,\n permissions: config.permissions,\n };\n writeFileSync(tmpPath, JSON.stringify(ordered, null, 2) + \"\\n\", \"utf-8\");\n renameSync(tmpPath, path);\n}\n\n// ── CLI handler ──────────────────────────────────────\n\n/** 处理 `lynx config` 命令。 */\nexport async function handleConfigCommand(opts: ConfigCommandOptions): Promise<void> {\n if (opts.path) {\n process.stdout.write(`${configPath()}\\n`);\n return;\n }\n\n if (opts.set) {\n const config = loadConfig();\n // 简单顶层 key 直写;嵌套 key 暂不支持\n const key = opts.set as keyof LynxConfig;\n if (key === \"model\" || key === \"theme\") {\n config[key] = opts.value ?? \"\";\n } else if (key === \"env\" || key === \"permissions\") {\n process.stderr.write(`[lynx] 警告:${opts.set} 是嵌套对象,请直接编辑 config.json。\\n`);\n return;\n }\n saveConfig(config);\n process.stdout.write(`${opts.set} = ${opts.value ?? \"\"}\\n`);\n return;\n }\n\n // Default: --show\n const config = loadConfig();\n const keys = Object.keys(config);\n if (keys.length === 0) {\n process.stdout.write(\"No configuration values set.\\n\");\n } else {\n for (const [key, val] of Object.entries(config)) {\n process.stdout.write(`${key} = ${JSON.stringify(val)}\\n`);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
//#region src/commands/context.ts
|
|
5
|
+
/**
|
|
6
|
+
* /context — 显示 IDE 连接状态和活跃文件信息。
|
|
7
|
+
*
|
|
8
|
+
* 扫描 ~/.lynx/ide/*.lock 检测正在运行的 IDE,
|
|
9
|
+
* 读取 context.json 获取活跃文件、光标位置等上下文信息,
|
|
10
|
+
* 以表格形式输出。
|
|
11
|
+
*/
|
|
12
|
+
/** IDE 名称到显示名称的映射。 */
|
|
13
|
+
const IDE_DISPLAY_NAMES = {
|
|
14
|
+
vscode: "VS Code",
|
|
15
|
+
cursor: "Cursor",
|
|
16
|
+
windsurf: "Windsurf",
|
|
17
|
+
zed: "Zed"
|
|
18
|
+
};
|
|
19
|
+
/** 解析 lockfile 目录路径,优先 ~/.lynx/ide,回退 ~/.claude/ide。 */
|
|
20
|
+
function resolveIdeDir() {
|
|
21
|
+
const lynxIde = join(homedir(), ".lynx", "ide");
|
|
22
|
+
if (existsSync(lynxIde)) return lynxIde;
|
|
23
|
+
const claudeIde = join(homedir(), ".claude", "ide");
|
|
24
|
+
if (existsSync(claudeIde)) return claudeIde;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/** 读取并解析单个 lockfile,失败返回 null。 */
|
|
28
|
+
function readLockfile(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
31
|
+
const data = JSON.parse(raw);
|
|
32
|
+
const portMatch = filePath.match(/(\d+)\.lock$/);
|
|
33
|
+
if (!portMatch) return null;
|
|
34
|
+
const port = Number.parseInt(portMatch[1], 10);
|
|
35
|
+
if (Number.isNaN(port)) return null;
|
|
36
|
+
return {
|
|
37
|
+
port,
|
|
38
|
+
data
|
|
39
|
+
};
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** 读取 IDE 上下文状态文件。 */
|
|
45
|
+
function readIdeContext() {
|
|
46
|
+
const contextPath = join(homedir(), ".lynx", "ide", "context.json");
|
|
47
|
+
try {
|
|
48
|
+
if (!existsSync(contextPath)) return null;
|
|
49
|
+
const raw = readFileSync(contextPath, "utf-8");
|
|
50
|
+
return JSON.parse(raw);
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 处理 /context 命令。
|
|
57
|
+
*
|
|
58
|
+
* 扫描 IDE lockfile 和 context.json,格式化输出 IDE 连接状态。
|
|
59
|
+
* 不返回 instruction(这是 local 类型命令,直接在 CLI 进程中执行)。
|
|
60
|
+
*/
|
|
61
|
+
function handleContextCommand() {
|
|
62
|
+
const lines = [];
|
|
63
|
+
const separator = "━━━━━━━━━━━━━━━━━━━━━━━━";
|
|
64
|
+
lines.push("IDE 连接状态");
|
|
65
|
+
lines.push(separator);
|
|
66
|
+
const ideDir = resolveIdeDir();
|
|
67
|
+
if (!ideDir) {
|
|
68
|
+
lines.push("连接状态: 未连接");
|
|
69
|
+
lines.push("提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成");
|
|
70
|
+
lines.push(separator);
|
|
71
|
+
return { output: lines.join("\n") };
|
|
72
|
+
}
|
|
73
|
+
let entries;
|
|
74
|
+
try {
|
|
75
|
+
entries = readdirSync(ideDir);
|
|
76
|
+
} catch {
|
|
77
|
+
lines.push("连接状态: 未连接");
|
|
78
|
+
lines.push("提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成");
|
|
79
|
+
lines.push(separator);
|
|
80
|
+
return { output: lines.join("\n") };
|
|
81
|
+
}
|
|
82
|
+
const lockFiles = entries.filter((f) => f.endsWith(".lock"));
|
|
83
|
+
const ideInstances = [];
|
|
84
|
+
for (const file of lockFiles) {
|
|
85
|
+
const result = readLockfile(join(ideDir, file));
|
|
86
|
+
if (result) ideInstances.push(result);
|
|
87
|
+
}
|
|
88
|
+
if (ideInstances.length === 0) {
|
|
89
|
+
lines.push("连接状态: 未连接");
|
|
90
|
+
lines.push("提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成");
|
|
91
|
+
lines.push(separator);
|
|
92
|
+
return { output: lines.join("\n") };
|
|
93
|
+
}
|
|
94
|
+
const cwd = process.cwd();
|
|
95
|
+
const { data } = ideInstances.find((inst) => inst.data.workspace === cwd) ?? ideInstances[0];
|
|
96
|
+
const displayName = IDE_DISPLAY_NAMES[data.ideName] ?? data.ideName;
|
|
97
|
+
lines.push(`连接状态: 已连接`);
|
|
98
|
+
lines.push(`IDE 类型: ${displayName} (${data.ideName})`);
|
|
99
|
+
lines.push(`工作区: ${data.workspace}`);
|
|
100
|
+
const context = readIdeContext();
|
|
101
|
+
if (context?.activeFile) {
|
|
102
|
+
const pos = context.cursorPosition;
|
|
103
|
+
const posStr = pos ? `:${pos.line}` : "";
|
|
104
|
+
lines.push(`活跃文件: ${context.activeFile}${posStr}`);
|
|
105
|
+
}
|
|
106
|
+
if (context?.cursorPosition) {
|
|
107
|
+
const { line, column } = context.cursorPosition;
|
|
108
|
+
lines.push(`光标位置: 行 ${line}, 列 ${column}`);
|
|
109
|
+
}
|
|
110
|
+
if (context?.selectedText !== void 0) {
|
|
111
|
+
const len = context.selectedText.length;
|
|
112
|
+
lines.push(`选中文本: (${len} 字符)`);
|
|
113
|
+
}
|
|
114
|
+
if (context?.openFiles && context.openFiles.length > 0) {
|
|
115
|
+
const openCount = context.openFiles.length;
|
|
116
|
+
lines.push(`打开文件: ${openCount} 个`);
|
|
117
|
+
const maxDisplay = 20;
|
|
118
|
+
const displayFiles = context.openFiles.slice(0, maxDisplay);
|
|
119
|
+
for (const file of displayFiles) lines.push(` - ${file}`);
|
|
120
|
+
if (context.openFiles.length > maxDisplay) lines.push(` ... 还有 ${context.openFiles.length - maxDisplay} 个文件`);
|
|
121
|
+
}
|
|
122
|
+
lines.push(separator);
|
|
123
|
+
return { output: lines.join("\n") };
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { handleContextCommand };
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=context-BmZ8VEan.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-BmZ8VEan.mjs","names":[],"sources":["../src/commands/context.ts"],"sourcesContent":["/**\n * /context — 显示 IDE 连接状态和活跃文件信息。\n *\n * 扫描 ~/.lynx/ide/*.lock 检测正在运行的 IDE,\n * 读取 context.json 获取活跃文件、光标位置等上下文信息,\n * 以表格形式输出。\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { existsSync, readFileSync, readdirSync } from \"node:fs\";\n\n// ── Types ────────────────────────────────────────────\n\n/** IDE lockfile 内容格式(与 @lynx/ide adapter 一致)。 */\ninterface LockfileData {\n pid: number;\n workspace: string;\n transport: \"sse\" | \"ws\";\n ideName: string;\n authToken?: string;\n}\n\n/** context.json 中记录的 IDE 上下文状态。 */\ninterface IdeContext {\n activeFile?: string;\n cursorPosition?: { line: number; column: number };\n selectedText?: string;\n openFiles?: string[];\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/** IDE 名称到显示名称的映射。 */\nconst IDE_DISPLAY_NAMES: Record<string, string> = {\n vscode: \"VS Code\",\n cursor: \"Cursor\",\n windsurf: \"Windsurf\",\n zed: \"Zed\",\n};\n\n/** 解析 lockfile 目录路径,优先 ~/.lynx/ide,回退 ~/.claude/ide。 */\nfunction resolveIdeDir(): string | null {\n const lynxIde = join(homedir(), \".lynx\", \"ide\");\n if (existsSync(lynxIde)) return lynxIde;\n\n const claudeIde = join(homedir(), \".claude\", \"ide\");\n if (existsSync(claudeIde)) return claudeIde;\n\n return null;\n}\n\n/** 读取并解析单个 lockfile,失败返回 null。 */\nfunction readLockfile(filePath: string): { port: number; data: LockfileData } | null {\n try {\n const raw = readFileSync(filePath, \"utf-8\");\n const data = JSON.parse(raw) as LockfileData;\n const portMatch = filePath.match(/(\\d+)\\.lock$/);\n if (!portMatch) return null;\n const port = Number.parseInt(portMatch[1], 10);\n if (Number.isNaN(port)) return null;\n return { port, data };\n } catch {\n return null;\n }\n}\n\n/** 读取 IDE 上下文状态文件。 */\nfunction readIdeContext(): IdeContext | null {\n const contextPath = join(homedir(), \".lynx\", \"ide\", \"context.json\");\n try {\n if (!existsSync(contextPath)) return null;\n const raw = readFileSync(contextPath, \"utf-8\");\n return JSON.parse(raw) as IdeContext;\n } catch {\n return null;\n }\n}\n\n/** 格式化 Unix 时间戳为可读的时间字符串。 */\nfunction formatTime(timestamp: number): string {\n const date = new Date(timestamp);\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /context 命令。\n *\n * 扫描 IDE lockfile 和 context.json,格式化输出 IDE 连接状态。\n * 不返回 instruction(这是 local 类型命令,直接在 CLI 进程中执行)。\n */\nexport function handleContextCommand(): { output: string } {\n const lines: string[] = [];\n const separator = \"━━━━━━━━━━━━━━━━━━━━━━━━\";\n\n lines.push(\"IDE 连接状态\");\n lines.push(separator);\n\n const ideDir = resolveIdeDir();\n\n if (!ideDir) {\n // 无 IDE 目录 — 从未连接过\n lines.push(\"连接状态: 未连接\");\n lines.push(\"提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成\");\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n }\n\n // 扫描所有 lockfile\n let entries: string[];\n try {\n entries = readdirSync(ideDir);\n } catch {\n lines.push(\"连接状态: 未连接\");\n lines.push(\"提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成\");\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n }\n\n const lockFiles = entries.filter((f) => f.endsWith(\".lock\"));\n const ideInstances: Array<{ port: number; data: LockfileData }> = [];\n for (const file of lockFiles) {\n const result = readLockfile(join(ideDir, file));\n if (result) {\n ideInstances.push(result);\n }\n }\n\n if (ideInstances.length === 0) {\n lines.push(\"连接状态: 未连接\");\n lines.push(\"提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成\");\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n }\n\n // 取第一个连接的 IDE(或与当前工作目录匹配的)\n const cwd = process.cwd();\n const matched = ideInstances.find((inst) => inst.data.workspace === cwd) ?? ideInstances[0];\n const { data } = matched;\n\n const displayName = IDE_DISPLAY_NAMES[data.ideName] ?? data.ideName;\n\n lines.push(`连接状态: 已连接`);\n lines.push(`IDE 类型: ${displayName} (${data.ideName})`);\n lines.push(`工作区: ${data.workspace}`);\n\n // 尝试读取上下文状态\n const context = readIdeContext();\n\n if (context?.activeFile) {\n const pos = context.cursorPosition;\n const posStr = pos ? `:${pos.line}` : \"\";\n lines.push(`活跃文件: ${context.activeFile}${posStr}`);\n }\n\n if (context?.cursorPosition) {\n const { line, column } = context.cursorPosition;\n lines.push(`光标位置: 行 ${line}, 列 ${column}`);\n }\n\n if (context?.selectedText !== undefined) {\n const len = context.selectedText.length;\n lines.push(`选中文本: (${len} 字符)`);\n }\n\n if (context?.openFiles && context.openFiles.length > 0) {\n const openCount = context.openFiles.length;\n lines.push(`打开文件: ${openCount} 个`);\n const maxDisplay = 20;\n const displayFiles = context.openFiles.slice(0, maxDisplay);\n for (const file of displayFiles) {\n lines.push(` - ${file}`);\n }\n if (context.openFiles.length > maxDisplay) {\n lines.push(` ... 还有 ${context.openFiles.length - maxDisplay} 个文件`);\n }\n }\n\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region src/commands/context-viz.ts
|
|
2
|
+
/**
|
|
3
|
+
* /context-viz — 可视化当前上下文窗口的 token 使用情况。
|
|
4
|
+
*
|
|
5
|
+
* 返回一条中文指令,引导模型分析自身上下文窗口的组成:
|
|
6
|
+
* system prompt、项目上下文、技能列表、记忆注入、对话历史等各分区的
|
|
7
|
+
* token 占用,并用 ASCII 进度条和表格呈现。
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* 处理 /context-viz 命令。
|
|
11
|
+
*
|
|
12
|
+
* 返回一段中文指令,引导模型自分析上下文窗口并生成可视化报告。
|
|
13
|
+
* 模型根据自身消息历史和系统提示估算各分区的 token 使用量。
|
|
14
|
+
*/
|
|
15
|
+
function handleContextVizCommand() {
|
|
16
|
+
return { instruction: `请分析当前上下文窗口的使用情况并生成可视化报告:
|
|
17
|
+
|
|
18
|
+
1. **总体概览**
|
|
19
|
+
- 已用 token / 总 token 容量
|
|
20
|
+
- 使用百分比(带 ASCII 进度条)
|
|
21
|
+
- 示例格式:\`[████████░░░░] 78% (156,000 / 200,000 tokens)\`
|
|
22
|
+
|
|
23
|
+
2. **分区明细**
|
|
24
|
+
用表格列出各分区的 token 占用:
|
|
25
|
+
|
|
26
|
+
| 分区 | Token 占用 | 占比 | 说明 |
|
|
27
|
+
|------|-----------|------|------|
|
|
28
|
+
| System prompt | xxx | xx% | 固定前缀,包含工具目录和核心指令 |
|
|
29
|
+
| 项目上下文 | xxx | xx% | CLAUDE.md、IMPLEMENTATION.md、AGENTS.md 等 |
|
|
30
|
+
| 技能列表 | xxx | xx% | 渐进披露的 skill 目录 |
|
|
31
|
+
| 记忆注入 | xxx | xx% | 持久化规则和事实 |
|
|
32
|
+
| 对话历史 | xxx | xx% | 消息记录(区分 user/assistant/tool) |
|
|
33
|
+
| 预留余量 | xxx | xx% | 剩余可用空间 |
|
|
34
|
+
|
|
35
|
+
3. **对话历史细分**
|
|
36
|
+
- user 消息数及估算 token
|
|
37
|
+
- assistant 消息数及估算 token
|
|
38
|
+
- tool 调用结果数及估算 token
|
|
39
|
+
- 总轮次
|
|
40
|
+
|
|
41
|
+
4. **文件引用**
|
|
42
|
+
- 当前会话中已引用过的文件列表
|
|
43
|
+
- 每个文件在上下文中的近似 token 占用
|
|
44
|
+
- 标注是否仍在上下文中或已被截断
|
|
45
|
+
|
|
46
|
+
5. **诊断与建议**
|
|
47
|
+
- 使用率 < 60%:健康,无需操作
|
|
48
|
+
- 使用率 60%-80%:注意监控,可考虑精简不必要引用
|
|
49
|
+
- 使用率 80%-90%:建议执行 /compact 或移除冗余上下文
|
|
50
|
+
- 使用率 > 90%:紧急,上下文即将溢出,可能丢失关键信息
|
|
51
|
+
|
|
52
|
+
输出格式要求:
|
|
53
|
+
- 全部使用中文标签
|
|
54
|
+
- ASCII 进度条用 \`█\` 和 \`░\` 字符
|
|
55
|
+
- 表格对齐清晰
|
|
56
|
+
- 在报告末尾给出明确建议(是否需要压缩、是否可以移除冗余)` };
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { handleContextVizCommand };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=context-viz-2ZZaTL2C.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-viz-2ZZaTL2C.mjs","names":[],"sources":["../src/commands/context-viz.ts"],"sourcesContent":["/**\n * /context-viz — 可视化当前上下文窗口的 token 使用情况。\n *\n * 返回一条中文指令,引导模型分析自身上下文窗口的组成:\n * system prompt、项目上下文、技能列表、记忆注入、对话历史等各分区的\n * token 占用,并用 ASCII 进度条和表格呈现。\n */\n\n/**\n * 处理 /context-viz 命令。\n *\n * 返回一段中文指令,引导模型自分析上下文窗口并生成可视化报告。\n * 模型根据自身消息历史和系统提示估算各分区的 token 使用量。\n */\nexport function handleContextVizCommand(): { instruction: string } {\n return {\n instruction: `请分析当前上下文窗口的使用情况并生成可视化报告:\n\n1. **总体概览**\n - 已用 token / 总 token 容量\n - 使用百分比(带 ASCII 进度条)\n - 示例格式:\\`[████████░░░░] 78% (156,000 / 200,000 tokens)\\`\n\n2. **分区明细**\n 用表格列出各分区的 token 占用:\n\n | 分区 | Token 占用 | 占比 | 说明 |\n |------|-----------|------|------|\n | System prompt | xxx | xx% | 固定前缀,包含工具目录和核心指令 |\n | 项目上下文 | xxx | xx% | CLAUDE.md、IMPLEMENTATION.md、AGENTS.md 等 |\n | 技能列表 | xxx | xx% | 渐进披露的 skill 目录 |\n | 记忆注入 | xxx | xx% | 持久化规则和事实 |\n | 对话历史 | xxx | xx% | 消息记录(区分 user/assistant/tool) |\n | 预留余量 | xxx | xx% | 剩余可用空间 |\n\n3. **对话历史细分**\n - user 消息数及估算 token\n - assistant 消息数及估算 token\n - tool 调用结果数及估算 token\n - 总轮次\n\n4. **文件引用**\n - 当前会话中已引用过的文件列表\n - 每个文件在上下文中的近似 token 占用\n - 标注是否仍在上下文中或已被截断\n\n5. **诊断与建议**\n - 使用率 < 60%:健康,无需操作\n - 使用率 60%-80%:注意监控,可考虑精简不必要引用\n - 使用率 80%-90%:建议执行 /compact 或移除冗余上下文\n - 使用率 > 90%:紧急,上下文即将溢出,可能丢失关键信息\n\n输出格式要求:\n- 全部使用中文标签\n- ASCII 进度条用 \\`█\\` 和 \\`░\\` 字符\n- 表格对齐清晰\n- 在报告末尾给出明确建议(是否需要压缩、是否可以移除冗余)`,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,0BAAmD;CACjE,OAAO,EACL,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAyCf;AACF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { resolvePaths } from "@lynx/core";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
//#region src/commands/env.ts
|
|
5
|
+
/**
|
|
6
|
+
* /env — 显示 Lynx 环境变量和运行环境信息。
|
|
7
|
+
*
|
|
8
|
+
* 读取所有 LYNX_* 环境变量、Node.js 版本、操作系统平台
|
|
9
|
+
* 和 Lynx 主目录,以表格形式输出。
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* 处理 /env 命令。
|
|
13
|
+
*
|
|
14
|
+
* 收集并格式化输出所有 Lynx 相关的环境信息。
|
|
15
|
+
* 不返回 instruction(这是 local 类型命令,直接在 CLI 进程中执行)。
|
|
16
|
+
*/
|
|
17
|
+
function handleEnvCommand() {
|
|
18
|
+
const lines = [];
|
|
19
|
+
const lynxVars = Object.keys(process.env).filter((key) => key.startsWith("LYNX_")).sort();
|
|
20
|
+
lines.push("【Lynx 环境变量】");
|
|
21
|
+
lines.push("");
|
|
22
|
+
if (lynxVars.length === 0) lines.push(" (未设置任何 LYNX_* 环境变量)");
|
|
23
|
+
else for (const key of lynxVars) {
|
|
24
|
+
const value = process.env[key];
|
|
25
|
+
const display = value && value.length > 0 ? value : "(空)";
|
|
26
|
+
lines.push(` ${key.padEnd(30)} = ${display}`);
|
|
27
|
+
}
|
|
28
|
+
const missingVars = [
|
|
29
|
+
"LYNX_HOME",
|
|
30
|
+
"LYNX_CONFIG_PATH",
|
|
31
|
+
"LYNX_LOG_LEVEL",
|
|
32
|
+
"LYNX_MODEL"
|
|
33
|
+
].filter((v) => !lynxVars.includes(v));
|
|
34
|
+
if (missingVars.length > 0) {
|
|
35
|
+
lines.push("");
|
|
36
|
+
lines.push("【未设置的常见变量】");
|
|
37
|
+
lines.push("");
|
|
38
|
+
for (const key of missingVars) lines.push(` ${key.padEnd(30)} = (未设置,使用默认值)`);
|
|
39
|
+
}
|
|
40
|
+
const paths = resolvePaths();
|
|
41
|
+
lines.push("");
|
|
42
|
+
lines.push("【运行环境】");
|
|
43
|
+
lines.push("");
|
|
44
|
+
lines.push(` ${"Node.js 版本".padEnd(30)} = ${process.version}`);
|
|
45
|
+
lines.push(` ${"操作系统".padEnd(30)} = ${process.platform} (${process.arch})`);
|
|
46
|
+
lines.push(` ${"Lynx 主目录".padEnd(30)} = ${join(homedir(), ".lynx")}`);
|
|
47
|
+
lines.push(` ${"配置文件路径".padEnd(30)} = ${paths.configFile}`);
|
|
48
|
+
lines.push(` ${"数据库路径".padEnd(30)} = ${paths.stateDb}`);
|
|
49
|
+
lines.push(` ${"工作目录".padEnd(30)} = ${process.cwd()}`);
|
|
50
|
+
return { output: lines.join("\n") };
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { handleEnvCommand };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=env-CeeZcoDI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-CeeZcoDI.mjs","names":[],"sources":["../src/commands/env.ts"],"sourcesContent":["/**\n * /env — 显示 Lynx 环境变量和运行环境信息。\n *\n * 读取所有 LYNX_* 环境变量、Node.js 版本、操作系统平台\n * 和 Lynx 主目录,以表格形式输出。\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { resolvePaths } from \"@lynx/core\";\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /env 命令。\n *\n * 收集并格式化输出所有 Lynx 相关的环境信息。\n * 不返回 instruction(这是 local 类型命令,直接在 CLI 进程中执行)。\n */\nexport function handleEnvCommand(): { output: string } {\n const lines: string[] = [];\n\n // ── LYNX_* 环境变量 ──────────────────────────────\n const lynxVars = Object.keys(process.env)\n .filter((key) => key.startsWith(\"LYNX_\"))\n .sort();\n\n lines.push(\"【Lynx 环境变量】\");\n lines.push(\"\");\n\n if (lynxVars.length === 0) {\n lines.push(\" (未设置任何 LYNX_* 环境变量)\");\n } else {\n for (const key of lynxVars) {\n const value = process.env[key];\n const display = value && value.length > 0 ? value : \"(空)\";\n lines.push(` ${key.padEnd(30)} = ${display}`);\n }\n }\n\n // ── 已定义但未设置的常见变量 ──────────────────────\n const knownVars = [\"LYNX_HOME\", \"LYNX_CONFIG_PATH\", \"LYNX_LOG_LEVEL\", \"LYNX_MODEL\"];\n const missingVars = knownVars.filter((v) => !lynxVars.includes(v));\n if (missingVars.length > 0) {\n lines.push(\"\");\n lines.push(\"【未设置的常见变量】\");\n lines.push(\"\");\n for (const key of missingVars) {\n lines.push(` ${key.padEnd(30)} = (未设置,使用默认值)`);\n }\n }\n\n // ── 运行环境 ──────────────────────────────────────\n const paths = resolvePaths();\n\n lines.push(\"\");\n lines.push(\"【运行环境】\");\n lines.push(\"\");\n lines.push(` ${\"Node.js 版本\".padEnd(30)} = ${process.version}`);\n lines.push(` ${\"操作系统\".padEnd(30)} = ${process.platform} (${process.arch})`);\n lines.push(` ${\"Lynx 主目录\".padEnd(30)} = ${join(homedir(), \".lynx\")}`);\n lines.push(` ${\"配置文件路径\".padEnd(30)} = ${paths.configFile}`);\n lines.push(` ${\"数据库路径\".padEnd(30)} = ${paths.stateDb}`);\n lines.push(` ${\"工作目录\".padEnd(30)} = ${process.cwd()}`);\n\n return { output: lines.join(\"\\n\") };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,SAAgB,mBAAuC;CACrD,MAAM,QAAkB,CAAC;CAGzB,MAAM,WAAW,OAAO,KAAK,QAAQ,GAAG,CAAC,CACtC,QAAQ,QAAQ,IAAI,WAAW,OAAO,CAAC,CAAC,CACxC,KAAK;CAER,MAAM,KAAK,aAAa;CACxB,MAAM,KAAK,EAAE;CAEb,IAAI,SAAS,WAAW,GACtB,MAAM,KAAK,uBAAuB;MAElC,KAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,QAAQ,QAAQ,IAAI;EAC1B,MAAM,UAAU,SAAS,MAAM,SAAS,IAAI,QAAQ;EACpD,MAAM,KAAK,KAAK,IAAI,OAAO,EAAE,EAAE,KAAK,SAAS;CAC/C;CAKF,MAAM,cAAc;EADD;EAAa;EAAoB;EAAkB;CAC1C,CAAC,CAAC,QAAQ,MAAM,CAAC,SAAS,SAAS,CAAC,CAAC;CACjE,IAAI,YAAY,SAAS,GAAG;EAC1B,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,YAAY;EACvB,MAAM,KAAK,EAAE;EACb,KAAK,MAAM,OAAO,aAChB,MAAM,KAAK,KAAK,IAAI,OAAO,EAAE,EAAE,eAAe;CAElD;CAGA,MAAM,QAAQ,aAAa;CAE3B,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,QAAQ;CACnB,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,KAAK,aAAa,OAAO,EAAE,EAAE,KAAK,QAAQ,SAAS;CAC9D,MAAM,KAAK,KAAK,OAAO,OAAO,EAAE,EAAE,KAAK,QAAQ,SAAS,IAAI,QAAQ,KAAK,EAAE;CAC3E,MAAM,KAAK,KAAK,WAAW,OAAO,EAAE,EAAE,KAAK,KAAK,QAAQ,GAAG,OAAO,GAAG;CACrE,MAAM,KAAK,KAAK,SAAS,OAAO,EAAE,EAAE,KAAK,MAAM,YAAY;CAC3D,MAAM,KAAK,KAAK,QAAQ,OAAO,EAAE,EAAE,KAAK,MAAM,SAAS;CACvD,MAAM,KAAK,KAAK,OAAO,OAAO,EAAE,EAAE,KAAK,QAAQ,IAAI,GAAG;CAEtD,OAAO,EAAE,QAAQ,MAAM,KAAK,IAAI,EAAE;AACpC"}
|