cc-vision-hook 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/CHANGELOG.md +47 -0
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/README.zh-CN.md +158 -0
- package/dist/cache.d.ts +70 -0
- package/dist/cache.js +164 -0
- package/dist/cache.js.map +1 -0
- package/dist/claudeSettings.d.ts +31 -0
- package/dist/claudeSettings.js +115 -0
- package/dist/claudeSettings.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +106 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/configCmd.d.ts +18 -0
- package/dist/commands/configCmd.js +104 -0
- package/dist/commands/configCmd.js.map +1 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.js +60 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/install.d.ts +16 -0
- package/dist/commands/install.js +48 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.js +35 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/testImage.d.ts +11 -0
- package/dist/commands/testImage.js +40 -0
- package/dist/commands/testImage.js.map +1 -0
- package/dist/commands/toggle.d.ts +10 -0
- package/dist/commands/toggle.js +21 -0
- package/dist/commands/toggle.js.map +1 -0
- package/dist/config.d.ts +72 -0
- package/dist/config.js +94 -0
- package/dist/config.js.map +1 -0
- package/dist/extract.d.ts +32 -0
- package/dist/extract.js +105 -0
- package/dist/extract.js.map +1 -0
- package/dist/hooks/postToolUse.d.ts +27 -0
- package/dist/hooks/postToolUse.js +53 -0
- package/dist/hooks/postToolUse.js.map +1 -0
- package/dist/hooks/userPromptSubmit.d.ts +29 -0
- package/dist/hooks/userPromptSubmit.js +64 -0
- package/dist/hooks/userPromptSubmit.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/pasteScanner.d.ts +37 -0
- package/dist/pasteScanner.js +68 -0
- package/dist/pasteScanner.js.map +1 -0
- package/dist/sdk/index.d.ts +12 -0
- package/dist/sdk/index.js +12 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/seenTracker.d.ts +23 -0
- package/dist/seenTracker.js +46 -0
- package/dist/seenTracker.js.map +1 -0
- package/dist/vision.d.ts +40 -0
- package/dist/vision.js +87 -0
- package/dist/vision.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 对 `~/.claude/settings.json` 里 Hook 注册条目的幂等读写。
|
|
3
|
+
*
|
|
4
|
+
* 幂等标识:用固定的 command 字符串("cvh hook user-prompt-submit"/"cvh hook post-tool-use")
|
|
5
|
+
* 作为唯一标识——install 前先检查是否已存在同样的条目,避免重复注册;uninstall 时精确按这个
|
|
6
|
+
* 字符串过滤移除,不误删用户自己配置的其他 Hook。
|
|
7
|
+
*/
|
|
8
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
9
|
+
import { join, dirname } from "node:path";
|
|
10
|
+
import { getClaudeHomeDir } from "./config.js";
|
|
11
|
+
export const USER_PROMPT_SUBMIT_COMMAND = "cvh hook user-prompt-submit";
|
|
12
|
+
export const POST_TOOL_USE_COMMAND = "cvh hook post-tool-use";
|
|
13
|
+
const getSettingsPath = () => join(getClaudeHomeDir(), "settings.json");
|
|
14
|
+
/**
|
|
15
|
+
* 读取 `~/.claude/settings.json`,文件不存在或解析失败时返回空对象(不覆盖用户已有配置)。
|
|
16
|
+
*
|
|
17
|
+
* @returns 解析后的设置对象
|
|
18
|
+
*/
|
|
19
|
+
const readSettings = async () => {
|
|
20
|
+
try {
|
|
21
|
+
const raw = await readFile(getSettingsPath(), "utf8");
|
|
22
|
+
return JSON.parse(raw);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* 写回 `~/.claude/settings.json`。
|
|
30
|
+
*
|
|
31
|
+
* @param settings - 完整的设置对象
|
|
32
|
+
*/
|
|
33
|
+
const writeSettings = async (settings) => {
|
|
34
|
+
const path = getSettingsPath();
|
|
35
|
+
await mkdir(dirname(path), { recursive: true });
|
|
36
|
+
await writeFile(path, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* 检查某个 Hook 分组数组里是否已经存在指定 command 的条目。
|
|
40
|
+
*
|
|
41
|
+
* @param groups - Hook 分组数组
|
|
42
|
+
* @param command - 要查找的命令字符串
|
|
43
|
+
* @returns 是否已存在
|
|
44
|
+
*/
|
|
45
|
+
const hasCommand = (groups, command) => (groups ?? []).some((g) => g.hooks.some((h) => h.command === command));
|
|
46
|
+
/**
|
|
47
|
+
* 幂等地在 `~/.claude/settings.json` 中注册 `UserPromptSubmit` 和 `PostToolUse` 两个 Hook。
|
|
48
|
+
* 已存在同样 command 的条目时跳过,不重复添加;不会动用户已有的其他 Hook 配置。
|
|
49
|
+
*
|
|
50
|
+
* @returns 本次实际新增的 Hook 数量(0 表示已经是安装好的状态,重复执行安全)
|
|
51
|
+
*/
|
|
52
|
+
export const installHooks = async () => {
|
|
53
|
+
const settings = await readSettings();
|
|
54
|
+
settings.hooks ??= {};
|
|
55
|
+
let added = 0;
|
|
56
|
+
if (!hasCommand(settings.hooks.UserPromptSubmit, USER_PROMPT_SUBMIT_COMMAND)) {
|
|
57
|
+
settings.hooks.UserPromptSubmit = [
|
|
58
|
+
...(settings.hooks.UserPromptSubmit ?? []),
|
|
59
|
+
{ hooks: [{ type: "command", command: USER_PROMPT_SUBMIT_COMMAND, timeout: 60 }] },
|
|
60
|
+
];
|
|
61
|
+
added += 1;
|
|
62
|
+
}
|
|
63
|
+
if (!hasCommand(settings.hooks.PostToolUse, POST_TOOL_USE_COMMAND)) {
|
|
64
|
+
settings.hooks.PostToolUse = [
|
|
65
|
+
...(settings.hooks.PostToolUse ?? []),
|
|
66
|
+
// matcher 显式设为 "*":图片检测逻辑完全在 hook 内部完成(不依赖工具名白名单),
|
|
67
|
+
// 挂 "*" 才能自动覆盖未来任何新工具(含 MCP/Bash 等),不需要用户手动维护 matcher 列表。
|
|
68
|
+
// 不显式设置时 Claude Code 的匹配行为未经验证,不能假设留空等价于匹配全部。
|
|
69
|
+
{ matcher: "*", hooks: [{ type: "command", command: POST_TOOL_USE_COMMAND, timeout: 60 }] },
|
|
70
|
+
];
|
|
71
|
+
added += 1;
|
|
72
|
+
}
|
|
73
|
+
await writeSettings(settings);
|
|
74
|
+
return added;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* 精确移除 cvh 注册的两个 Hook 条目,不影响用户配置的其他 Hook。
|
|
78
|
+
*
|
|
79
|
+
* @returns 本次实际移除的 Hook 数量
|
|
80
|
+
*/
|
|
81
|
+
export const uninstallHooks = async () => {
|
|
82
|
+
const settings = await readSettings();
|
|
83
|
+
if (!settings.hooks)
|
|
84
|
+
return 0;
|
|
85
|
+
let removed = 0;
|
|
86
|
+
if (settings.hooks.UserPromptSubmit) {
|
|
87
|
+
const before = settings.hooks.UserPromptSubmit.length;
|
|
88
|
+
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit
|
|
89
|
+
.map((g) => ({ ...g, hooks: g.hooks.filter((h) => h.command !== USER_PROMPT_SUBMIT_COMMAND) }))
|
|
90
|
+
.filter((g) => g.hooks.length > 0);
|
|
91
|
+
removed += before - settings.hooks.UserPromptSubmit.length;
|
|
92
|
+
}
|
|
93
|
+
if (settings.hooks.PostToolUse) {
|
|
94
|
+
const before = settings.hooks.PostToolUse.length;
|
|
95
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse
|
|
96
|
+
.map((g) => ({ ...g, hooks: g.hooks.filter((h) => h.command !== POST_TOOL_USE_COMMAND) }))
|
|
97
|
+
.filter((g) => g.hooks.length > 0);
|
|
98
|
+
removed += before - settings.hooks.PostToolUse.length;
|
|
99
|
+
}
|
|
100
|
+
await writeSettings(settings);
|
|
101
|
+
return removed;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* 检查两个 Hook 是否都已注册(用于 `cvh status`/`doctor`)。
|
|
105
|
+
*
|
|
106
|
+
* @returns 分别表示两个 Hook 是否已注册
|
|
107
|
+
*/
|
|
108
|
+
export const checkHooksInstalled = async () => {
|
|
109
|
+
const settings = await readSettings();
|
|
110
|
+
return {
|
|
111
|
+
userPromptSubmit: hasCommand(settings.hooks?.UserPromptSubmit, USER_PROMPT_SUBMIT_COMMAND),
|
|
112
|
+
postToolUse: hasCommand(settings.hooks?.PostToolUse, POST_TOOL_USE_COMMAND),
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=claudeSettings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeSettings.js","sourceRoot":"","sources":["../src/claudeSettings.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cvh CLI 入口(命令分发)。
|
|
4
|
+
* P0 范围:install/uninstall/enable/disable/status/doctor/config/test-image + 两个 hook 入口。
|
|
5
|
+
* P1(交互式 init/`--json` 已在部分命令支持)、P2(MCP)为后续阶段,见 README「路线图」。
|
|
6
|
+
*/
|
|
7
|
+
import { runInstall, runUninstall } from "./commands/install.js";
|
|
8
|
+
import { runToggle } from "./commands/toggle.js";
|
|
9
|
+
import { runStatus } from "./commands/status.js";
|
|
10
|
+
import { runDoctor } from "./commands/doctor.js";
|
|
11
|
+
import { runConfigGet, runConfigSet } from "./commands/configCmd.js";
|
|
12
|
+
import { runTestImage } from "./commands/testImage.js";
|
|
13
|
+
import { handleUserPromptSubmit } from "./hooks/userPromptSubmit.js";
|
|
14
|
+
import { handlePostToolUse } from "./hooks/postToolUse.js";
|
|
15
|
+
const printHelp = () => {
|
|
16
|
+
console.log(`cc-vision-hook (cvh)
|
|
17
|
+
|
|
18
|
+
让"静默忽略图片"型模型(收到图片不报错、只是看不懂)也能理解 Claude Code 里的图片内容。
|
|
19
|
+
⚠️ 对"协议层硬拒绝"型模型(收到图片直接导致请求失败)无效,详见 \`cvh doctor\` 提示。
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
cvh install 安装:创建配置 + 注册 Hook(幂等)
|
|
23
|
+
cvh uninstall [--purge] 卸载:移除 Hook 注册(--purge 连带删除配置和缓存)
|
|
24
|
+
cvh enable / cvh disable 开关(唯一决定是否处理图片的开关)
|
|
25
|
+
cvh status 查看当前状态
|
|
26
|
+
cvh doctor 自检(配置/Hook/视觉模型连通性 + 边界声明)
|
|
27
|
+
cvh config get 查看当前配置
|
|
28
|
+
cvh config set <key> <value> 设置配置项(provider/model/baseUrl/apiKey/timeoutMs/maxTokens)
|
|
29
|
+
cvh test-image <path> 手动测试:本地图片 -> 视觉解析
|
|
30
|
+
|
|
31
|
+
Global options:
|
|
32
|
+
--json 以 JSON 格式输出(供脚本消费)
|
|
33
|
+
`);
|
|
34
|
+
};
|
|
35
|
+
/** 读取 stdin 全部内容(用于 hook 子命令)。 */
|
|
36
|
+
const readStdin = async () => {
|
|
37
|
+
const chunks = [];
|
|
38
|
+
for await (const chunk of process.stdin)
|
|
39
|
+
chunks.push(chunk);
|
|
40
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
41
|
+
};
|
|
42
|
+
const main = async () => {
|
|
43
|
+
const argv = process.argv.slice(2);
|
|
44
|
+
const jsonOutput = argv.includes("--json");
|
|
45
|
+
const args = argv.filter((a) => a !== "--json");
|
|
46
|
+
const [command, ...rest] = args;
|
|
47
|
+
switch (command) {
|
|
48
|
+
case undefined:
|
|
49
|
+
case "help":
|
|
50
|
+
case "--help":
|
|
51
|
+
printHelp();
|
|
52
|
+
return;
|
|
53
|
+
case "install":
|
|
54
|
+
return runInstall(jsonOutput);
|
|
55
|
+
case "uninstall":
|
|
56
|
+
return runUninstall(rest.includes("--purge"), jsonOutput);
|
|
57
|
+
case "enable":
|
|
58
|
+
return runToggle(true, jsonOutput);
|
|
59
|
+
case "disable":
|
|
60
|
+
return runToggle(false, jsonOutput);
|
|
61
|
+
case "status":
|
|
62
|
+
return runStatus(jsonOutput);
|
|
63
|
+
case "doctor":
|
|
64
|
+
return runDoctor(jsonOutput);
|
|
65
|
+
case "config":
|
|
66
|
+
if (rest[0] === "get")
|
|
67
|
+
return runConfigGet(jsonOutput);
|
|
68
|
+
if (rest[0] === "set" && rest[1] && rest[2])
|
|
69
|
+
return runConfigSet(rest[1], rest[2], jsonOutput);
|
|
70
|
+
throw new Error("用法:cvh config get | cvh config set <key> <value>");
|
|
71
|
+
case "test-image":
|
|
72
|
+
if (!rest[0])
|
|
73
|
+
throw new Error("用法:cvh test-image <path>");
|
|
74
|
+
return runTestImage(rest[0], jsonOutput);
|
|
75
|
+
case "hook": {
|
|
76
|
+
const raw = await readStdin();
|
|
77
|
+
const payload = JSON.parse(raw || "{}");
|
|
78
|
+
if (rest[0] === "user-prompt-submit" || payload.hook_event_name === "UserPromptSubmit") {
|
|
79
|
+
const output = await handleUserPromptSubmit(payload);
|
|
80
|
+
console.log(JSON.stringify(output));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (rest[0] === "post-tool-use" || payload.hook_event_name === "PostToolUse") {
|
|
84
|
+
const output = await handlePostToolUse(payload);
|
|
85
|
+
console.log(JSON.stringify(output));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// 未知 hook 事件,安全兜底返回空对象,不阻塞 Claude Code 流程。
|
|
89
|
+
console.log("{}");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`未知命令:${command}。运行 \`cvh help\` 查看用法。`);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
main().catch((error) => {
|
|
97
|
+
// hook 子命令即使出错也不能让 Claude Code 卡住,统一输出空对象兜底 + 把错误打到 stderr 供排查。
|
|
98
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
+
console.error(`cvh error: ${message}`);
|
|
100
|
+
if (process.argv[2] === "hook") {
|
|
101
|
+
console.log("{}");
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
process.exit(1);
|
|
105
|
+
});
|
|
106
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cvh config get/set` 命令实现。
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 打印当前完整配置(脱敏 apiKey)。
|
|
6
|
+
*
|
|
7
|
+
* @param jsonOutput - 是否以 JSON 格式输出
|
|
8
|
+
*/
|
|
9
|
+
export declare const runConfigGet: (jsonOutput: boolean) => Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* 设置单个配置项并落盘。
|
|
12
|
+
*
|
|
13
|
+
* @param key - 配置项名(仅允许 EDITABLE_KEYS 中的字段)
|
|
14
|
+
* @param value - 新值(字符串形式,数字字段会自动转换)
|
|
15
|
+
* @param jsonOutput - 是否以 JSON 格式输出
|
|
16
|
+
* @throws 当 key 不在允许列表内时抛出错误
|
|
17
|
+
*/
|
|
18
|
+
export declare const runConfigSet: (key: string, value: string, jsonOutput: boolean) => Promise<void>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cvh config get/set` 命令实现。
|
|
3
|
+
*/
|
|
4
|
+
import { loadConfig, saveConfig } from "../config.js";
|
|
5
|
+
const EDITABLE_KEYS = ["provider", "model", "baseUrl", "apiKey", "timeoutMs", "maxTokens"];
|
|
6
|
+
/** provider 字段允许的合法值,与 vision.ts 的 resolveModel() 分支一一对应。 */
|
|
7
|
+
const VALID_PROVIDERS = ["oai", "responses", "anthropic", "gemini"];
|
|
8
|
+
const isEditableKey = (key) => EDITABLE_KEYS.includes(key);
|
|
9
|
+
/**
|
|
10
|
+
* 按字段名把字符串值写入配置对象的对应字段,避免使用 `as any` 绕过类型检查——
|
|
11
|
+
* 每个分支都显式声明目标字段的真实类型,并在写入前做基本校验,
|
|
12
|
+
* 避免非法值一直存活到 `describeImage()` 真正调用时才报错(体验差、排查成本高)。
|
|
13
|
+
*
|
|
14
|
+
* @param config - 待修改的配置对象(原地修改)
|
|
15
|
+
* @param key - 目标字段名
|
|
16
|
+
* @param value - 字符串形式的新值
|
|
17
|
+
* @throws 当值不满足对应字段的校验规则时抛出错误(provider 非法、数字字段非数字/非正数等)
|
|
18
|
+
*/
|
|
19
|
+
const applyConfigValue = (config, key, value) => {
|
|
20
|
+
switch (key) {
|
|
21
|
+
case "provider": {
|
|
22
|
+
if (!VALID_PROVIDERS.includes(value)) {
|
|
23
|
+
throw new Error(`不支持的 provider:${value}。可用值:${VALID_PROVIDERS.join(", ")}`);
|
|
24
|
+
}
|
|
25
|
+
config.provider = value;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
case "model": {
|
|
29
|
+
if (!value.trim())
|
|
30
|
+
throw new Error("model 不能为空");
|
|
31
|
+
config.model = value;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
case "baseUrl": {
|
|
35
|
+
try {
|
|
36
|
+
// eslint-disable-next-line no-new
|
|
37
|
+
new URL(value);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new Error(`baseUrl 不是合法的 URL:${value}`);
|
|
41
|
+
}
|
|
42
|
+
config.baseUrl = value;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
case "apiKey": {
|
|
46
|
+
if (!value.trim())
|
|
47
|
+
throw new Error("apiKey 不能为空");
|
|
48
|
+
config.apiKey = value;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
case "timeoutMs": {
|
|
52
|
+
const n = Number(value);
|
|
53
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
54
|
+
throw new Error(`timeoutMs 必须是正数:${value}`);
|
|
55
|
+
config.timeoutMs = n;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
case "maxTokens": {
|
|
59
|
+
const n = Number(value);
|
|
60
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
61
|
+
throw new Error(`maxTokens 必须是正数:${value}`);
|
|
62
|
+
config.maxTokens = n;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* 打印当前完整配置(脱敏 apiKey)。
|
|
69
|
+
*
|
|
70
|
+
* @param jsonOutput - 是否以 JSON 格式输出
|
|
71
|
+
*/
|
|
72
|
+
export const runConfigGet = async (jsonOutput) => {
|
|
73
|
+
const config = await loadConfig();
|
|
74
|
+
const masked = { ...config, apiKey: config.apiKey ? `${config.apiKey.slice(0, 6)}***` : undefined };
|
|
75
|
+
if (jsonOutput) {
|
|
76
|
+
console.log(JSON.stringify(masked));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
for (const [key, value] of Object.entries(masked)) {
|
|
80
|
+
console.log(`${key} = ${typeof value === "object" ? JSON.stringify(value) : value}`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* 设置单个配置项并落盘。
|
|
85
|
+
*
|
|
86
|
+
* @param key - 配置项名(仅允许 EDITABLE_KEYS 中的字段)
|
|
87
|
+
* @param value - 新值(字符串形式,数字字段会自动转换)
|
|
88
|
+
* @param jsonOutput - 是否以 JSON 格式输出
|
|
89
|
+
* @throws 当 key 不在允许列表内时抛出错误
|
|
90
|
+
*/
|
|
91
|
+
export const runConfigSet = async (key, value, jsonOutput) => {
|
|
92
|
+
if (!isEditableKey(key)) {
|
|
93
|
+
throw new Error(`不支持的配置项:${key}。可用项:${EDITABLE_KEYS.join(", ")}`);
|
|
94
|
+
}
|
|
95
|
+
const config = await loadConfig();
|
|
96
|
+
applyConfigValue(config, key, value);
|
|
97
|
+
await saveConfig(config);
|
|
98
|
+
if (jsonOutput) {
|
|
99
|
+
console.log(JSON.stringify({ ok: true, key, value }));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
console.log(`✅ ${key} = ${key === "apiKey" ? "***" : value}`);
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=configCmd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configCmd.js","sourceRoot":"","sources":["../../src/commands/configCmd.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cvh doctor` 命令实现:自检配置有效性、API key 可用性、Hook 注册完整性,
|
|
3
|
+
* 并显著提示 cvh 的适用边界(仅对"静默忽略图片"型模型有效)。
|
|
4
|
+
*/
|
|
5
|
+
import { stat } from "node:fs/promises";
|
|
6
|
+
import { loadConfig, getConfigPath } from "../config.js";
|
|
7
|
+
import { checkHooksInstalled } from "../claudeSettings.js";
|
|
8
|
+
import { describeImage } from "../vision.js";
|
|
9
|
+
// 1x1 透明 PNG,仅用于自检时验证视觉模型连通性,不产生真实业务含义。
|
|
10
|
+
const PROBE_IMAGE_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
|
|
11
|
+
/**
|
|
12
|
+
* 执行一整套自检并打印结果。
|
|
13
|
+
*
|
|
14
|
+
* @param jsonOutput - 是否以 JSON 格式输出结果
|
|
15
|
+
*/
|
|
16
|
+
export const runDoctor = async (jsonOutput) => {
|
|
17
|
+
const checks = [];
|
|
18
|
+
const config = await loadConfig();
|
|
19
|
+
checks.push({ name: "配置文件存在", ok: true, detail: getConfigPath() });
|
|
20
|
+
try {
|
|
21
|
+
const s = await stat(getConfigPath());
|
|
22
|
+
// eslint-disable-next-line no-bitwise
|
|
23
|
+
const mode = (s.mode & 0o777).toString(8);
|
|
24
|
+
checks.push({ name: "配置文件权限", ok: mode === "600", detail: `当前权限 ${mode}(建议 600,避免同机其他用户读取明文 API key)` });
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
checks.push({ name: "配置文件权限", ok: false, detail: "配置文件不存在,请先运行 cvh install" });
|
|
28
|
+
}
|
|
29
|
+
const hooks = await checkHooksInstalled();
|
|
30
|
+
checks.push({ name: "UserPromptSubmit Hook 已注册", ok: hooks.userPromptSubmit, detail: hooks.userPromptSubmit ? "" : "请运行 cvh install" });
|
|
31
|
+
checks.push({ name: "PostToolUse Hook 已注册", ok: hooks.postToolUse, detail: hooks.postToolUse ? "" : "请运行 cvh install" });
|
|
32
|
+
checks.push({ name: "API Key 已配置", ok: Boolean(config.apiKey), detail: config.apiKey ? "已设置" : "请运行 cvh config set apiKey <key>" });
|
|
33
|
+
if (config.apiKey) {
|
|
34
|
+
try {
|
|
35
|
+
const start = Date.now();
|
|
36
|
+
await describeImage(PROBE_IMAGE_BASE64, "image/png", config, "这是什么?一个字回答即可。");
|
|
37
|
+
checks.push({ name: "视觉模型连通性", ok: true, detail: `调用成功,耗时 ${Date.now() - start}ms` });
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
checks.push({ name: "视觉模型连通性", ok: false, detail: error instanceof Error ? error.message : String(error) });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
checks.push({ name: "视觉模型连通性", ok: false, detail: "跳过(未配置 API Key)" });
|
|
45
|
+
}
|
|
46
|
+
const allOk = checks.every((c) => c.ok);
|
|
47
|
+
if (jsonOutput) {
|
|
48
|
+
console.log(JSON.stringify({ ok: allOk, checks, boundaryWarning: BOUNDARY_WARNING }));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (const check of checks) {
|
|
52
|
+
console.log(`${check.ok ? "✅" : "❌"} ${check.name}${check.detail ? ` — ${check.detail}` : ""}`);
|
|
53
|
+
}
|
|
54
|
+
console.log("");
|
|
55
|
+
console.log(BOUNDARY_WARNING);
|
|
56
|
+
};
|
|
57
|
+
const BOUNDARY_WARNING = "⚠️ 重要边界声明:cvh 只对「静默忽略图片」型模型有效(模型收到图片后不报错,只是不理解)。\n" +
|
|
58
|
+
" 对「协议层硬拒绝」型模型(收到图片直接导致整个请求失败/报错)完全无效,装了 cvh 该场景依然会失败。\n" +
|
|
59
|
+
" 请自行判断当前使用的模型属于哪一种,再决定是否启用 cvh。";
|
|
60
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cvh install` / `cvh uninstall` 命令实现。
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 安装 cvh:创建配置文件(若不存在)+ 注册 Hook。幂等,可重复执行。
|
|
6
|
+
*
|
|
7
|
+
* @param jsonOutput - 是否以 JSON 格式输出结果
|
|
8
|
+
*/
|
|
9
|
+
export declare const runInstall: (jsonOutput: boolean) => Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* 卸载 cvh:移除 Hook 注册,可选连带删除配置和缓存。
|
|
12
|
+
*
|
|
13
|
+
* @param purge - 是否连带删除配置文件和缓存目录
|
|
14
|
+
* @param jsonOutput - 是否以 JSON 格式输出结果
|
|
15
|
+
*/
|
|
16
|
+
export declare const runUninstall: (purge: boolean, jsonOutput: boolean) => Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cvh install` / `cvh uninstall` 命令实现。
|
|
3
|
+
*/
|
|
4
|
+
import { rm } from "node:fs/promises";
|
|
5
|
+
import { DEFAULT_CONFIG, getConfigPath, getCvhHomeDir, loadConfig, saveConfig } from "../config.js";
|
|
6
|
+
import { installHooks, uninstallHooks } from "../claudeSettings.js";
|
|
7
|
+
/**
|
|
8
|
+
* 安装 cvh:创建配置文件(若不存在)+ 注册 Hook。幂等,可重复执行。
|
|
9
|
+
*
|
|
10
|
+
* @param jsonOutput - 是否以 JSON 格式输出结果
|
|
11
|
+
*/
|
|
12
|
+
export const runInstall = async (jsonOutput) => {
|
|
13
|
+
const existing = await loadConfig().catch(() => null);
|
|
14
|
+
if (!existing)
|
|
15
|
+
await saveConfig(DEFAULT_CONFIG);
|
|
16
|
+
const added = await installHooks();
|
|
17
|
+
if (jsonOutput) {
|
|
18
|
+
console.log(JSON.stringify({ ok: true, hooksAdded: added, configPath: getConfigPath() }));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.log("✅ cvh 安装完成");
|
|
22
|
+
console.log(` 配置文件:${getConfigPath()}`);
|
|
23
|
+
console.log(` 新增 Hook 注册数:${added}(0 表示之前已安装过,本次是幂等重跑)`);
|
|
24
|
+
console.log(` ⚠️ 当前默认 enabled=false,运行 \`cvh enable\` 后才会生效`);
|
|
25
|
+
console.log(` ⚠️ cvh 仅对"静默忽略图片"型模型有效,对协议层硬拒绝型模型(如某些不支持视觉的模型)无效,详见 README`);
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* 卸载 cvh:移除 Hook 注册,可选连带删除配置和缓存。
|
|
29
|
+
*
|
|
30
|
+
* @param purge - 是否连带删除配置文件和缓存目录
|
|
31
|
+
* @param jsonOutput - 是否以 JSON 格式输出结果
|
|
32
|
+
*/
|
|
33
|
+
export const runUninstall = async (purge, jsonOutput) => {
|
|
34
|
+
const removed = await uninstallHooks();
|
|
35
|
+
if (purge) {
|
|
36
|
+
await rm(getConfigPath(), { force: true });
|
|
37
|
+
await rm(getCvhHomeDir(), { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
if (jsonOutput) {
|
|
40
|
+
console.log(JSON.stringify({ ok: true, hooksRemoved: removed, purged: purge }));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
console.log("✅ cvh 卸载完成");
|
|
44
|
+
console.log(` 移除 Hook 注册数:${removed}`);
|
|
45
|
+
if (purge)
|
|
46
|
+
console.log(" 已删除配置文件与缓存目录(--purge)");
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACpG,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEpE;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,UAAmB,EAAiB,EAAE;IACrE,MAAM,QAAQ,GAAG,MAAM,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,CAAC,QAAQ;QAAE,MAAM,UAAU,CAAC,cAAc,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,MAAM,YAAY,EAAE,CAAC;IAEnC,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC;QAC1F,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC1B,OAAO,CAAC,GAAG,CAAC,WAAW,aAAa,EAAE,EAAE,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,sBAAsB,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC;AAChF,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,KAAc,EAAE,UAAmB,EAAiB,EAAE;IACvF,MAAM,OAAO,GAAG,MAAM,cAAc,EAAE,CAAC;IACvC,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,EAAE,CAAC,aAAa,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,MAAM,EAAE,CAAC,aAAa,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAChF,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC1B,OAAO,CAAC,GAAG,CAAC,kBAAkB,OAAO,EAAE,CAAC,CAAC;IACzC,IAAI,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;AACrD,CAAC,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cvh status` 命令实现:展示开关状态、Hook 注册情况、缓存统计。
|
|
3
|
+
*/
|
|
4
|
+
import { loadConfig, getConfigPath } from "../config.js";
|
|
5
|
+
import { checkHooksInstalled } from "../claudeSettings.js";
|
|
6
|
+
import { countCacheEntries, getCacheDiskUsage } from "../cache.js";
|
|
7
|
+
/**
|
|
8
|
+
* 打印当前 cvh 的运行状态。
|
|
9
|
+
*
|
|
10
|
+
* @param jsonOutput - 是否以 JSON 格式输出结果
|
|
11
|
+
*/
|
|
12
|
+
export const runStatus = async (jsonOutput) => {
|
|
13
|
+
const config = await loadConfig();
|
|
14
|
+
const hooks = await checkHooksInstalled();
|
|
15
|
+
const cacheCount = await countCacheEntries();
|
|
16
|
+
const cacheBytes = await getCacheDiskUsage();
|
|
17
|
+
const result = {
|
|
18
|
+
enabled: config.enabled,
|
|
19
|
+
provider: config.provider,
|
|
20
|
+
model: config.model,
|
|
21
|
+
configPath: getConfigPath(),
|
|
22
|
+
hooksInstalled: hooks,
|
|
23
|
+
cache: { entries: cacheCount, bytes: cacheBytes, ttlDays: config.cache.ttlDays },
|
|
24
|
+
};
|
|
25
|
+
if (jsonOutput) {
|
|
26
|
+
console.log(JSON.stringify(result));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(`开关状态:${config.enabled ? "✅ 已启用" : "⚠️ 已停用"}`);
|
|
30
|
+
console.log(`视觉 provider:${config.provider} / 模型:${config.model}`);
|
|
31
|
+
console.log(`配置文件:${config.enabled ? "" : ""}${getConfigPath()}`);
|
|
32
|
+
console.log(`Hook 注册:UserPromptSubmit=${hooks.userPromptSubmit ? "✅" : "❌"} PostToolUse=${hooks.postToolUse ? "✅" : "❌"}`);
|
|
33
|
+
console.log(`本地缓存:${cacheCount} 条,约 ${(cacheBytes / 1024).toFixed(1)} KB,TTL ${config.cache.ttlDays} 天`);
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,UAAmB,EAAiB,EAAE;IACpE,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,MAAM,mBAAmB,EAAE,CAAC;IAC1C,MAAM,UAAU,GAAG,MAAM,iBAAiB,EAAE,CAAC;IAC7C,MAAM,UAAU,GAAG,MAAM,iBAAiB,EAAE,CAAC;IAE7C,MAAM,MAAM,GAAG;QACb,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,UAAU,EAAE,aAAa,EAAE;QAC3B,cAAc,EAAE,KAAK;QACrB,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE;KACjF,CAAC;IAEF,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QACpC,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,QAAQ,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,eAAe,MAAM,CAAC,QAAQ,SAAS,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,QAAQ,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,aAAa,EAAE,EAAE,CAAC,CAAC;IAClE,OAAO,CAAC,GAAG,CAAC,4BAA4B,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,gBAAgB,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3H,OAAO,CAAC,GAAG,CAAC,QAAQ,UAAU,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAC3G,CAAC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cvh test-image <path>` 命令实现:本地图片 -> 视觉解析 -> 打印描述,验证链路通畅。
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 读取本地图片文件,调用当前配置的视觉模型解析并打印结果。
|
|
6
|
+
*
|
|
7
|
+
* @param path - 本地图片文件路径
|
|
8
|
+
* @param jsonOutput - 是否以 JSON 格式输出
|
|
9
|
+
* @throws 当文件不存在、格式不支持或视觉模型调用失败时抛出错误
|
|
10
|
+
*/
|
|
11
|
+
export declare const runTestImage: (path: string, jsonOutput: boolean) => Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cvh test-image <path>` 命令实现:本地图片 -> 视觉解析 -> 打印描述,验证链路通畅。
|
|
3
|
+
*/
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { extname } from "node:path";
|
|
6
|
+
import { loadConfig } from "../config.js";
|
|
7
|
+
import { describeImage } from "../vision.js";
|
|
8
|
+
const EXT_TO_MIME = {
|
|
9
|
+
".png": "image/png",
|
|
10
|
+
".jpg": "image/jpeg",
|
|
11
|
+
".jpeg": "image/jpeg",
|
|
12
|
+
".webp": "image/webp",
|
|
13
|
+
".gif": "image/gif",
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* 读取本地图片文件,调用当前配置的视觉模型解析并打印结果。
|
|
17
|
+
*
|
|
18
|
+
* @param path - 本地图片文件路径
|
|
19
|
+
* @param jsonOutput - 是否以 JSON 格式输出
|
|
20
|
+
* @throws 当文件不存在、格式不支持或视觉模型调用失败时抛出错误
|
|
21
|
+
*/
|
|
22
|
+
export const runTestImage = async (path, jsonOutput) => {
|
|
23
|
+
const mimeType = EXT_TO_MIME[extname(path).toLowerCase()];
|
|
24
|
+
if (!mimeType)
|
|
25
|
+
throw new Error(`不支持的图片格式:${path}(支持 png/jpg/jpeg/webp/gif)`);
|
|
26
|
+
const bytes = await readFile(path);
|
|
27
|
+
const config = await loadConfig();
|
|
28
|
+
if (!config.apiKey)
|
|
29
|
+
throw new Error("尚未配置 API Key,请先运行 cvh config set apiKey <key>");
|
|
30
|
+
const start = Date.now();
|
|
31
|
+
const description = await describeImage(bytes.toString("base64"), mimeType, config);
|
|
32
|
+
const elapsedMs = Date.now() - start;
|
|
33
|
+
if (jsonOutput) {
|
|
34
|
+
console.log(JSON.stringify({ ok: true, description, elapsedMs }));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log(`✅ 解析成功(耗时 ${elapsedMs}ms):`);
|
|
38
|
+
console.log(description);
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=testImage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testImage.js","sourceRoot":"","sources":["../../src/commands/testImage.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,GAA2B;IAC1C,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;CACpB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,IAAY,EAAE,UAAmB,EAAiB,EAAE;IACrF,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,4BAA4B,CAAC,CAAC;IAE7E,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAErF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,WAAW,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACpF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;IAErC,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QAClE,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,aAAa,SAAS,MAAM,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AAC3B,CAAC,CAAC"}
|