@mantra-hq/privacy-hook 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli.js +22 -14
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +25 -10
- package/dist/index.js +22 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,9 +75,9 @@ Removes the hook from Claude Code settings.
|
|
|
75
75
|
|
|
76
76
|
The hook reads the Mantra client's port configuration from the settings file:
|
|
77
77
|
|
|
78
|
-
- **macOS**: `~/Library/Application Support/com.mantra
|
|
79
|
-
- **Linux**: `~/.local/share/com.mantra
|
|
80
|
-
- **Windows**: `%APPDATA%\com.mantra
|
|
78
|
+
- **macOS**: `~/Library/Application Support/com.gonewx.mantra/settings.yaml`
|
|
79
|
+
- **Linux**: `~/.local/share/com.gonewx.mantra/settings.yaml`
|
|
80
|
+
- **Windows**: `%APPDATA%\com.gonewx.mantra\settings.yaml`
|
|
81
81
|
|
|
82
82
|
Default port: `19836`
|
|
83
83
|
|
package/dist/cli.js
CHANGED
|
@@ -7,9 +7,16 @@ import { Command } from "commander";
|
|
|
7
7
|
import * as fs from "fs";
|
|
8
8
|
import * as path from "path";
|
|
9
9
|
import * as os from "os";
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
var HOOK_COMMAND = "mantra-privacy-hook check";
|
|
11
|
+
var HOOK_ENTRY = {
|
|
12
|
+
matcher: {},
|
|
13
|
+
// 空 matcher 表示匹配所有
|
|
14
|
+
hooks: [
|
|
15
|
+
{
|
|
16
|
+
type: "command",
|
|
17
|
+
command: HOOK_COMMAND
|
|
18
|
+
}
|
|
19
|
+
]
|
|
13
20
|
};
|
|
14
21
|
function getClaudeSettingsPath() {
|
|
15
22
|
const homeDir = os.homedir();
|
|
@@ -36,6 +43,11 @@ function saveClaudeSettings(settings) {
|
|
|
36
43
|
}
|
|
37
44
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
38
45
|
}
|
|
46
|
+
function hasOurHook(entry) {
|
|
47
|
+
return entry.hooks?.some(
|
|
48
|
+
(hook) => hook.type === "command" && hook.command === HOOK_COMMAND
|
|
49
|
+
) ?? false;
|
|
50
|
+
}
|
|
39
51
|
function registerHook() {
|
|
40
52
|
try {
|
|
41
53
|
const settings = loadClaudeSettings();
|
|
@@ -45,13 +57,11 @@ function registerHook() {
|
|
|
45
57
|
if (!settings.hooks.UserPromptSubmit) {
|
|
46
58
|
settings.hooks.UserPromptSubmit = [];
|
|
47
59
|
}
|
|
48
|
-
const alreadyRegistered = settings.hooks.UserPromptSubmit.some(
|
|
49
|
-
(hook) => hook.command === HOOK_CONFIG.command
|
|
50
|
-
);
|
|
60
|
+
const alreadyRegistered = settings.hooks.UserPromptSubmit.some(hasOurHook);
|
|
51
61
|
if (alreadyRegistered) {
|
|
52
62
|
return true;
|
|
53
63
|
}
|
|
54
|
-
settings.hooks.UserPromptSubmit.push(
|
|
64
|
+
settings.hooks.UserPromptSubmit.push(HOOK_ENTRY);
|
|
55
65
|
saveClaudeSettings(settings);
|
|
56
66
|
return true;
|
|
57
67
|
} catch (error2) {
|
|
@@ -67,7 +77,7 @@ function unregisterHook() {
|
|
|
67
77
|
}
|
|
68
78
|
const originalLength = settings.hooks.UserPromptSubmit.length;
|
|
69
79
|
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
|
|
70
|
-
(
|
|
80
|
+
(entry) => !hasOurHook(entry)
|
|
71
81
|
);
|
|
72
82
|
if (settings.hooks.UserPromptSubmit.length === originalLength) {
|
|
73
83
|
return true;
|
|
@@ -91,9 +101,7 @@ function isHookRegistered() {
|
|
|
91
101
|
if (!settings.hooks?.UserPromptSubmit) {
|
|
92
102
|
return false;
|
|
93
103
|
}
|
|
94
|
-
return settings.hooks.UserPromptSubmit.some(
|
|
95
|
-
(hook) => hook.command === HOOK_CONFIG.command
|
|
96
|
-
);
|
|
104
|
+
return settings.hooks.UserPromptSubmit.some(hasOurHook);
|
|
97
105
|
} catch {
|
|
98
106
|
return false;
|
|
99
107
|
}
|
|
@@ -113,11 +121,11 @@ function getMantraConfigDir() {
|
|
|
113
121
|
const homeDir = os2.homedir();
|
|
114
122
|
switch (platform2) {
|
|
115
123
|
case "darwin":
|
|
116
|
-
return path2.join(homeDir, "Library", "Application Support", "com.mantra
|
|
124
|
+
return path2.join(homeDir, "Library", "Application Support", "com.gonewx.mantra");
|
|
117
125
|
case "win32":
|
|
118
|
-
return path2.join(process.env.APPDATA || path2.join(homeDir, "AppData", "Roaming"), "com.mantra
|
|
126
|
+
return path2.join(process.env.APPDATA || path2.join(homeDir, "AppData", "Roaming"), "com.gonewx.mantra");
|
|
119
127
|
default:
|
|
120
|
-
return path2.join(homeDir, ".local", "share", "com.mantra
|
|
128
|
+
return path2.join(homeDir, ".local", "share", "com.gonewx.mantra");
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
131
|
function loadMantraConfig() {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/claude-settings.ts","../src/config.ts","../src/client-api.ts","../src/commands/install.ts","../src/commands/uninstall.ts","../src/commands/status.ts","../src/hook-handler.ts","../src/commands/check.ts"],"sourcesContent":["/**\n * Mantra Privacy Hook CLI\n * Story 3.11: Task 6 - AC #1, #2, #5, #6\n *\n * CLI 命令:\n * - install - 安装 Hook 到 Claude Code\n * - uninstall - 从 Claude Code 移除 Hook\n * - status - 显示状态\n * - check - 执行隐私检查(由 Claude Code Hook 调用)\n */\n\nimport { Command } from \"commander\";\nimport { installCommand } from \"./commands/install.js\";\nimport { uninstallCommand } from \"./commands/uninstall.js\";\nimport { statusCommand } from \"./commands/status.js\";\nimport { checkCommand } from \"./commands/check.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"mantra-privacy-hook\")\n .description(\"Privacy protection hook for AI coding tools\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"install\")\n .description(\"Install the privacy hook to Claude Code\")\n .action(async () => {\n await installCommand();\n });\n\nprogram\n .command(\"uninstall\")\n .description(\"Remove the privacy hook from Claude Code\")\n .action(async () => {\n await uninstallCommand();\n });\n\nprogram\n .command(\"status\")\n .description(\"Show hook installation and client connection status\")\n .action(async () => {\n await statusCommand();\n });\n\nprogram\n .command(\"check\")\n .description(\"Check prompt for sensitive information (called by Claude Code)\")\n .action(async () => {\n await checkCommand();\n });\n\nprogram.parse();\n","/**\n * Claude Code Settings 管理模块\n * Story 3.11: Task 7 - AC #2, #5\n *\n * 管理 Claude Code 的 settings.json 中的 hook 配置\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { ClaudeSettings, ClaudeHookConfig } from \"./types.js\";\n\n/** Hook 配置 */\nconst HOOK_CONFIG: ClaudeHookConfig = {\n command: \"mantra-privacy-hook check\",\n description: \"Mantra Privacy Check\",\n};\n\n/**\n * 获取 Claude Code settings.json 路径(跨平台)\n *\n * - macOS: ~/.claude/settings.json\n * - Linux: ~/.claude/settings.json\n * - Windows: %USERPROFILE%\\.claude\\settings.json\n */\nexport function getClaudeSettingsPath(): string {\n const homeDir = os.homedir();\n return path.join(homeDir, \".claude\", \"settings.json\");\n}\n\n/**\n * 读取 Claude Code settings\n *\n * @returns settings 对象,如果文件不存在则返回空对象\n */\nexport function loadClaudeSettings(): ClaudeSettings {\n const settingsPath = getClaudeSettingsPath();\n\n try {\n if (!fs.existsSync(settingsPath)) {\n return {};\n }\n\n const content = fs.readFileSync(settingsPath, \"utf-8\");\n return JSON.parse(content) as ClaudeSettings;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to read Claude settings: ${error}`);\n return {};\n }\n}\n\n/**\n * 保存 Claude Code settings\n *\n * @param settings - settings 对象\n */\nexport function saveClaudeSettings(settings: ClaudeSettings): void {\n const settingsPath = getClaudeSettingsPath();\n const settingsDir = path.dirname(settingsPath);\n\n // 确保目录存在\n if (!fs.existsSync(settingsDir)) {\n fs.mkdirSync(settingsDir, { recursive: true });\n }\n\n // 写入文件(保持格式化)\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), \"utf-8\");\n}\n\n/**\n * 注册 Hook 到 Claude Code\n *\n * @returns true 如果成功注册\n */\nexport function registerHook(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n // 确保 hooks 对象存在\n if (!settings.hooks) {\n settings.hooks = {};\n }\n\n // 确保 UserPromptSubmit 数组存在\n if (!settings.hooks.UserPromptSubmit) {\n settings.hooks.UserPromptSubmit = [];\n }\n\n // 检查是否已经注册\n const alreadyRegistered = settings.hooks.UserPromptSubmit.some(\n (hook) => hook.command === HOOK_CONFIG.command\n );\n\n if (alreadyRegistered) {\n return true; // 已经注册\n }\n\n // 添加 hook\n settings.hooks.UserPromptSubmit.push(HOOK_CONFIG);\n\n // 保存\n saveClaudeSettings(settings);\n return true;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to register hook: ${error}`);\n return false;\n }\n}\n\n/**\n * 从 Claude Code 移除 Hook\n *\n * @returns true 如果成功移除\n */\nexport function unregisterHook(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n // 如果没有 hooks 配置,无需处理\n if (!settings.hooks?.UserPromptSubmit) {\n return true;\n }\n\n // 过滤掉我们的 hook\n const originalLength = settings.hooks.UserPromptSubmit.length;\n settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(\n (hook) => hook.command !== HOOK_CONFIG.command\n );\n\n // 如果没有变化,说明本来就没注册\n if (settings.hooks.UserPromptSubmit.length === originalLength) {\n return true;\n }\n\n // 如果 UserPromptSubmit 数组为空,可以删除它\n if (settings.hooks.UserPromptSubmit.length === 0) {\n delete settings.hooks.UserPromptSubmit;\n }\n\n // 如果 hooks 对象为空,可以删除它\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n\n // 保存\n saveClaudeSettings(settings);\n return true;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to unregister hook: ${error}`);\n return false;\n }\n}\n\n/**\n * 检查 Hook 是否已注册\n *\n * @returns true 如果已注册\n */\nexport function isHookRegistered(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n if (!settings.hooks?.UserPromptSubmit) {\n return false;\n }\n\n return settings.hooks.UserPromptSubmit.some(\n (hook) => hook.command === HOOK_CONFIG.command\n );\n } catch {\n return false;\n }\n}\n\n/**\n * 检查 Claude Code settings 文件是否存在\n */\nexport function claudeSettingsExists(): boolean {\n return fs.existsSync(getClaudeSettingsPath());\n}\n","/**\n * Configuration utilities\n * Story 3.11: Task 8, 9 - AC #7\n *\n * 读取 Mantra 配置文件获取端口等设置\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { MantraConfig } from \"./types.js\";\n\n/** 默认端口 */\nexport const DEFAULT_PORT = 19836;\n\n/** 配置文件名 */\nconst CONFIG_FILENAME = \"settings.yaml\";\n\n/**\n * 获取 Mantra 配置目录路径(跨平台)\n *\n * - macOS: ~/Library/Application Support/com.mantra.app/\n * - Linux: ~/.local/share/com.mantra.app/\n * - Windows: %APPDATA%\\com.mantra.app\\\n */\nexport function getMantraConfigDir(): string {\n const platform = os.platform();\n const homeDir = os.homedir();\n\n switch (platform) {\n case \"darwin\": // macOS\n return path.join(homeDir, \"Library\", \"Application Support\", \"com.mantra.app\");\n case \"win32\": // Windows\n return path.join(process.env.APPDATA || path.join(homeDir, \"AppData\", \"Roaming\"), \"com.mantra.app\");\n default: // Linux and others\n return path.join(homeDir, \".local\", \"share\", \"com.mantra.app\");\n }\n}\n\n/**\n * 读取 Mantra 配置\n *\n * @returns 配置对象,如果文件不存在则返回默认配置\n */\nexport function loadMantraConfig(): MantraConfig {\n const configDir = getMantraConfigDir();\n const configPath = path.join(configDir, CONFIG_FILENAME);\n\n try {\n if (!fs.existsSync(configPath)) {\n return { local_api_port: DEFAULT_PORT };\n }\n\n const content = fs.readFileSync(configPath, \"utf-8\");\n // 简单的 YAML 解析(只需要读取 local_api_port)\n const portMatch = content.match(/local_api_port:\\s*(\\d+)/);\n if (portMatch) {\n return { local_api_port: parseInt(portMatch[1], 10) };\n }\n return { local_api_port: DEFAULT_PORT };\n } catch {\n return { local_api_port: DEFAULT_PORT };\n }\n}\n\n/**\n * 获取当前配置的端口\n */\nexport function getPort(): number {\n const config = loadMantraConfig();\n return config.local_api_port ?? DEFAULT_PORT;\n}\n\n/**\n * 获取 Mantra API 基础 URL\n */\nexport function getApiBaseUrl(): string {\n const port = getPort();\n return `http://127.0.0.1:${port}`;\n}\n","/**\n * Client API - Mantra 客户端通信模块\n * Story 3.11: Task 9 - AC #6, #7\n *\n * 提供与 Mantra 客户端 HTTP API 的通信功能\n */\n\nimport type { PrivacyCheckRequest, PrivacyCheckResponse } from \"./types.js\";\nimport { getApiBaseUrl, getPort } from \"./config.js\";\n\n/** 请求超时时间(毫秒) */\nconst REQUEST_TIMEOUT = 3000;\n\n/**\n * 检查 Mantra 客户端是否在运行\n *\n * @returns true 如果客户端正在运行\n */\nexport async function checkClientRunning(): Promise<boolean> {\n const baseUrl = getApiBaseUrl();\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);\n\n const response = await fetch(`${baseUrl}/api/health`, {\n method: \"GET\",\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * 发送隐私检查请求到 Mantra 客户端\n *\n * @param prompt - 待检查的内容\n * @returns 检查响应或 null(如果连接失败)\n */\nexport async function checkPrivacy(\n prompt: string\n): Promise<PrivacyCheckResponse | null> {\n const baseUrl = getApiBaseUrl();\n\n const request: PrivacyCheckRequest = {\n prompt,\n context: {\n tool: \"claude-code\",\n timestamp: new Date().toISOString(),\n },\n };\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);\n\n const response = await fetch(`${baseUrl}/api/privacy/check`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.error(\n `[mantra-privacy-hook] API error: ${response.status} ${response.statusText}`\n );\n return null;\n }\n\n return (await response.json()) as PrivacyCheckResponse;\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n console.error(\"[mantra-privacy-hook] Request timeout\");\n } else {\n console.error(`[mantra-privacy-hook] Connection error: ${error.message}`);\n }\n }\n return null;\n }\n}\n\n/**\n * 获取客户端状态信息\n *\n * @returns 状态信息对象\n */\nexport async function getClientStatus(): Promise<{\n running: boolean;\n port: number;\n url: string;\n}> {\n const port = getPort();\n const url = getApiBaseUrl();\n const running = await checkClientRunning();\n\n return {\n running,\n port,\n url,\n };\n}\n","/**\n * Install 命令\n * Story 3.11: Task 6 - AC #2\n *\n * 安装 Hook 到 Claude Code settings\n */\n\nimport { registerHook, isHookRegistered, claudeSettingsExists, getClaudeSettingsPath } from \"../claude-settings.js\";\nimport { checkClientRunning, getClientStatus } from \"../client-api.js\";\n\nexport async function installCommand(): Promise<void> {\n console.log(\"🔧 Installing Mantra Privacy Hook for Claude Code...\\n\");\n\n // 检查是否已安装\n if (isHookRegistered()) {\n console.log(\"✅ Hook is already installed.\\n\");\n await showStatus();\n return;\n }\n\n // 检查 Claude Code settings 是否存在\n if (!claudeSettingsExists()) {\n console.log(`📁 Creating Claude Code settings at: ${getClaudeSettingsPath()}\\n`);\n }\n\n // 注册 Hook\n const success = registerHook();\n\n if (success) {\n console.log(\"✅ Hook installed successfully!\\n\");\n console.log(\" The hook will check your prompts for sensitive information\");\n console.log(\" before sending them to Claude Code.\\n\");\n await showStatus();\n } else {\n console.error(\"❌ Failed to install hook.\\n\");\n console.error(\" Please check that you have write permission to:\");\n console.error(` ${getClaudeSettingsPath()}\\n`);\n process.exit(1);\n }\n}\n\nasync function showStatus(): Promise<void> {\n const status = await getClientStatus();\n \n console.log(\"📊 Status:\");\n console.log(` • Hook installed: ✓`);\n console.log(` • Mantra client: ${status.running ? \"✓ Running\" : \"✗ Not running\"}`);\n console.log(` • API endpoint: ${status.url}`);\n\n if (!status.running) {\n console.log(\"\\n💡 Note: Start Mantra client to enable privacy protection.\");\n }\n console.log(\"\");\n}\n","/**\n * Uninstall 命令\n * Story 3.11: Task 6 - AC #5\n *\n * 从 Claude Code settings 移除 Hook\n */\n\nimport { unregisterHook, isHookRegistered } from \"../claude-settings.js\";\n\nexport async function uninstallCommand(): Promise<void> {\n console.log(\"🔧 Uninstalling Mantra Privacy Hook from Claude Code...\\n\");\n\n // 检查是否已安装\n if (!isHookRegistered()) {\n console.log(\"ℹ️ Hook is not installed.\\n\");\n return;\n }\n\n // 移除 Hook\n const success = unregisterHook();\n\n if (success) {\n console.log(\"✅ Hook uninstalled successfully!\\n\");\n console.log(\" Privacy protection is now disabled for Claude Code.\");\n console.log(\" Run 'mantra-privacy-hook install' to re-enable.\\n\");\n } else {\n console.error(\"❌ Failed to uninstall hook.\\n\");\n process.exit(1);\n }\n}\n","/**\n * Status 命令\n * Story 3.11: Task 6 - AC #6\n *\n * 显示 Hook 安装状态和客户端连接状态\n */\n\nimport { isHookRegistered, getClaudeSettingsPath, claudeSettingsExists } from \"../claude-settings.js\";\nimport { getClientStatus } from \"../client-api.js\";\nimport { getMantraConfigDir } from \"../config.js\";\n\nexport async function statusCommand(): Promise<void> {\n console.log(\"📊 Mantra Privacy Hook Status\\n\");\n\n // Hook 安装状态\n const hookInstalled = isHookRegistered();\n console.log(`Hook Installation:`);\n console.log(` • Installed: ${hookInstalled ? \"✓ Yes\" : \"✗ No\"}`);\n console.log(` • Claude settings: ${getClaudeSettingsPath()}`);\n console.log(` • Settings exists: ${claudeSettingsExists() ? \"✓ Yes\" : \"✗ No\"}`);\n console.log(\"\");\n\n // 客户端状态\n const status = await getClientStatus();\n console.log(`Mantra Client:`);\n console.log(` • Running: ${status.running ? \"✓ Yes\" : \"✗ No\"}`);\n console.log(` • API Port: ${status.port}`);\n console.log(` • API URL: ${status.url}`);\n console.log(` • Config dir: ${getMantraConfigDir()}`);\n console.log(\"\");\n\n // 整体状态\n if (hookInstalled && status.running) {\n console.log(\"🟢 Privacy protection is ACTIVE\\n\");\n } else if (hookInstalled && !status.running) {\n console.log(\"🟡 Hook installed but client not running\");\n console.log(\" Start Mantra client to enable protection.\\n\");\n } else if (!hookInstalled && status.running) {\n console.log(\"🟡 Client running but hook not installed\");\n console.log(\" Run 'mantra-privacy-hook install' to enable.\\n\");\n } else {\n console.log(\"🔴 Privacy protection is INACTIVE\");\n console.log(\" Run 'mantra-privacy-hook install' and start Mantra client.\\n\");\n }\n}\n","/**\n * Hook 处理器\n * Story 3.11: Task 8 - AC #3\n *\n * 处理 Claude Code Hook 调用:\n * - 从 stdin 读取数据\n * - 调用 Mantra 客户端检查\n * - 返回适当的 exit code\n */\n\nimport type { ClaudeHookInput } from \"./types.js\";\nimport { checkPrivacy, checkClientRunning } from \"./client-api.js\";\n\n/**\n * 从 stdin 读取 JSON 数据\n */\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = \"\";\n\n process.stdin.setEncoding(\"utf-8\");\n process.stdin.on(\"data\", (chunk) => {\n data += chunk;\n });\n process.stdin.on(\"end\", () => {\n resolve(data);\n });\n process.stdin.on(\"error\", (err) => {\n reject(err);\n });\n });\n}\n\n/**\n * 输出警告信息到 stderr\n */\nfunction warn(message: string): void {\n console.error(`[Mantra Privacy Hook] ${message}`);\n}\n\n/**\n * 输出错误信息到 stderr\n */\nfunction error(message: string): void {\n console.error(`[Mantra Privacy Hook] ⚠️ ${message}`);\n}\n\n/**\n * 处理 Hook 调用\n *\n * Exit codes:\n * - 0: 允许继续(无敏感信息或客户端未运行)\n * - 2: 阻止提交(检测到敏感信息)\n */\nexport async function handleHook(): Promise<void> {\n try {\n // 读取 stdin\n const input = await readStdin();\n\n if (!input.trim()) {\n // 无输入,放行\n process.exit(0);\n }\n\n // 解析输入\n let hookInput: ClaudeHookInput;\n try {\n hookInput = JSON.parse(input) as ClaudeHookInput;\n } catch {\n error(\"Invalid JSON input\");\n process.exit(0); // 解析失败时放行\n }\n\n // 提取 prompt 内容\n const promptContent = hookInput.prompt?.content;\n if (!promptContent) {\n // 无 prompt 内容,放行\n process.exit(0);\n }\n\n // 检查 Mantra 客户端是否运行\n const clientRunning = await checkClientRunning();\n if (!clientRunning) {\n warn(\"Mantra 客户端未运行,隐私保护未启用\");\n process.exit(0); // 客户端未运行时放行 + 警告\n }\n\n // 调用隐私检查 API\n const result = await checkPrivacy(promptContent);\n\n if (!result) {\n warn(\"无法连接到 Mantra 客户端\");\n process.exit(0); // 连接失败时放行 + 警告\n }\n\n if (result.action === \"block\") {\n // 检测到敏感信息,阻止提交\n error(\"检测到敏感信息!\");\n \n if (result.message) {\n console.error(` ${result.message}`);\n }\n\n if (result.matches && result.matches.length > 0) {\n console.error(\" 详情:\");\n for (const match of result.matches) {\n console.error(` - [${match.severity}] ${match.rule_id}: ${match.preview}`);\n }\n }\n\n console.error(\"\");\n console.error(\" 请在 Mantra 客户端中处理敏感信息后重试。\");\n console.error(\"\");\n\n process.exit(2); // Exit code 2 阻止提交\n }\n\n // 允许继续\n process.exit(0);\n } catch (err) {\n error(`处理错误: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(0); // 错误时放行\n }\n}\n","/**\n * Check 命令\n * Story 3.11: Task 8 - AC #3\n *\n * 被 Claude Code Hook 调用,检查 prompt 中的敏感信息\n */\n\nimport { handleHook } from \"../hook-handler.js\";\n\nexport async function checkCommand(): Promise<void> {\n // 直接调用 hook 处理器\n // 它会从 stdin 读取数据并返回适当的 exit code\n await handleHook();\n}\n"],"mappings":"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","names":["error","fs","path","os","platform","error"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/claude-settings.ts","../src/config.ts","../src/client-api.ts","../src/commands/install.ts","../src/commands/uninstall.ts","../src/commands/status.ts","../src/hook-handler.ts","../src/commands/check.ts"],"sourcesContent":["/**\n * Mantra Privacy Hook CLI\n * Story 3.11: Task 6 - AC #1, #2, #5, #6\n *\n * CLI 命令:\n * - install - 安装 Hook 到 Claude Code\n * - uninstall - 从 Claude Code 移除 Hook\n * - status - 显示状态\n * - check - 执行隐私检查(由 Claude Code Hook 调用)\n */\n\nimport { Command } from \"commander\";\nimport { installCommand } from \"./commands/install.js\";\nimport { uninstallCommand } from \"./commands/uninstall.js\";\nimport { statusCommand } from \"./commands/status.js\";\nimport { checkCommand } from \"./commands/check.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"mantra-privacy-hook\")\n .description(\"Privacy protection hook for AI coding tools\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"install\")\n .description(\"Install the privacy hook to Claude Code\")\n .action(async () => {\n await installCommand();\n });\n\nprogram\n .command(\"uninstall\")\n .description(\"Remove the privacy hook from Claude Code\")\n .action(async () => {\n await uninstallCommand();\n });\n\nprogram\n .command(\"status\")\n .description(\"Show hook installation and client connection status\")\n .action(async () => {\n await statusCommand();\n });\n\nprogram\n .command(\"check\")\n .description(\"Check prompt for sensitive information (called by Claude Code)\")\n .action(async () => {\n await checkCommand();\n });\n\nprogram.parse();\n","/**\n * Claude Code Settings 管理模块\n * Story 3.11: Task 7 - AC #2, #5\n *\n * 管理 Claude Code 的 settings.json 中的 hook 配置\n * 使用新的 hooks 格式 (matcher + hooks 数组)\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { ClaudeSettings, ClaudeHookEntry, ClaudeHookCommand } from \"./types.js\";\n\n/** Hook 命令 */\nconst HOOK_COMMAND = \"mantra-privacy-hook check\";\n\n/** Hook 配置 (新格式) */\nconst HOOK_ENTRY: ClaudeHookEntry = {\n matcher: {}, // 空 matcher 表示匹配所有\n hooks: [\n {\n type: \"command\",\n command: HOOK_COMMAND,\n },\n ],\n};\n\n/**\n * 获取 Claude Code settings.json 路径(跨平台)\n *\n * - macOS: ~/.claude/settings.json\n * - Linux: ~/.claude/settings.json\n * - Windows: %USERPROFILE%\\.claude\\settings.json\n */\nexport function getClaudeSettingsPath(): string {\n const homeDir = os.homedir();\n return path.join(homeDir, \".claude\", \"settings.json\");\n}\n\n/**\n * 读取 Claude Code settings\n *\n * @returns settings 对象,如果文件不存在则返回空对象\n */\nexport function loadClaudeSettings(): ClaudeSettings {\n const settingsPath = getClaudeSettingsPath();\n\n try {\n if (!fs.existsSync(settingsPath)) {\n return {};\n }\n\n const content = fs.readFileSync(settingsPath, \"utf-8\");\n return JSON.parse(content) as ClaudeSettings;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to read Claude settings: ${error}`);\n return {};\n }\n}\n\n/**\n * 保存 Claude Code settings\n *\n * @param settings - settings 对象\n */\nexport function saveClaudeSettings(settings: ClaudeSettings): void {\n const settingsPath = getClaudeSettingsPath();\n const settingsDir = path.dirname(settingsPath);\n\n // 确保目录存在\n if (!fs.existsSync(settingsDir)) {\n fs.mkdirSync(settingsDir, { recursive: true });\n }\n\n // 写入文件(保持格式化)\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), \"utf-8\");\n}\n\n/**\n * 检查 hook entry 是否包含我们的命令\n */\nfunction hasOurHook(entry: ClaudeHookEntry): boolean {\n return entry.hooks?.some(\n (hook: ClaudeHookCommand) => hook.type === \"command\" && hook.command === HOOK_COMMAND\n ) ?? false;\n}\n\n/**\n * 注册 Hook 到 Claude Code\n *\n * @returns true 如果成功注册\n */\nexport function registerHook(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n // 确保 hooks 对象存在\n if (!settings.hooks) {\n settings.hooks = {};\n }\n\n // 确保 UserPromptSubmit 数组存在\n if (!settings.hooks.UserPromptSubmit) {\n settings.hooks.UserPromptSubmit = [];\n }\n\n // 检查是否已经注册\n const alreadyRegistered = settings.hooks.UserPromptSubmit.some(hasOurHook);\n\n if (alreadyRegistered) {\n return true; // 已经注册\n }\n\n // 添加 hook entry\n settings.hooks.UserPromptSubmit.push(HOOK_ENTRY);\n\n // 保存\n saveClaudeSettings(settings);\n return true;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to register hook: ${error}`);\n return false;\n }\n}\n\n/**\n * 从 Claude Code 移除 Hook\n *\n * @returns true 如果成功移除\n */\nexport function unregisterHook(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n // 如果没有 hooks 配置,无需处理\n if (!settings.hooks?.UserPromptSubmit) {\n return true;\n }\n\n // 过滤掉包含我们 hook 的 entry\n const originalLength = settings.hooks.UserPromptSubmit.length;\n settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(\n (entry) => !hasOurHook(entry)\n );\n\n // 如果没有变化,说明本来就没注册\n if (settings.hooks.UserPromptSubmit.length === originalLength) {\n return true;\n }\n\n // 如果 UserPromptSubmit 数组为空,可以删除它\n if (settings.hooks.UserPromptSubmit.length === 0) {\n delete settings.hooks.UserPromptSubmit;\n }\n\n // 如果 hooks 对象为空,可以删除它\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n\n // 保存\n saveClaudeSettings(settings);\n return true;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to unregister hook: ${error}`);\n return false;\n }\n}\n\n/**\n * 检查 Hook 是否已注册\n *\n * @returns true 如果已注册\n */\nexport function isHookRegistered(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n if (!settings.hooks?.UserPromptSubmit) {\n return false;\n }\n\n return settings.hooks.UserPromptSubmit.some(hasOurHook);\n } catch {\n return false;\n }\n}\n\n/**\n * 检查 Claude Code settings 文件是否存在\n */\nexport function claudeSettingsExists(): boolean {\n return fs.existsSync(getClaudeSettingsPath());\n}\n","/**\n * Configuration utilities\n * Story 3.11: Task 8, 9 - AC #7\n *\n * 读取 Mantra 配置文件获取端口等设置\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { MantraConfig } from \"./types.js\";\n\n/** 默认端口 */\nexport const DEFAULT_PORT = 19836;\n\n/** 配置文件名 */\nconst CONFIG_FILENAME = \"settings.yaml\";\n\n/**\n * 获取 Mantra 配置目录路径(跨平台)\n *\n * - macOS: ~/Library/Application Support/com.gonewx.mantra/\n * - Linux: ~/.local/share/com.gonewx.mantra/\n * - Windows: %APPDATA%\\com.gonewx.mantra\\\n */\nexport function getMantraConfigDir(): string {\n const platform = os.platform();\n const homeDir = os.homedir();\n\n switch (platform) {\n case \"darwin\": // macOS\n return path.join(homeDir, \"Library\", \"Application Support\", \"com.gonewx.mantra\");\n case \"win32\": // Windows\n return path.join(process.env.APPDATA || path.join(homeDir, \"AppData\", \"Roaming\"), \"com.gonewx.mantra\");\n default: // Linux and others\n return path.join(homeDir, \".local\", \"share\", \"com.gonewx.mantra\");\n }\n}\n\n/**\n * 读取 Mantra 配置\n *\n * @returns 配置对象,如果文件不存在则返回默认配置\n */\nexport function loadMantraConfig(): MantraConfig {\n const configDir = getMantraConfigDir();\n const configPath = path.join(configDir, CONFIG_FILENAME);\n\n try {\n if (!fs.existsSync(configPath)) {\n return { local_api_port: DEFAULT_PORT };\n }\n\n const content = fs.readFileSync(configPath, \"utf-8\");\n // 简单的 YAML 解析(只需要读取 local_api_port)\n const portMatch = content.match(/local_api_port:\\s*(\\d+)/);\n if (portMatch) {\n return { local_api_port: parseInt(portMatch[1], 10) };\n }\n return { local_api_port: DEFAULT_PORT };\n } catch {\n return { local_api_port: DEFAULT_PORT };\n }\n}\n\n/**\n * 获取当前配置的端口\n */\nexport function getPort(): number {\n const config = loadMantraConfig();\n return config.local_api_port ?? DEFAULT_PORT;\n}\n\n/**\n * 获取 Mantra API 基础 URL\n */\nexport function getApiBaseUrl(): string {\n const port = getPort();\n return `http://127.0.0.1:${port}`;\n}\n","/**\n * Client API - Mantra 客户端通信模块\n * Story 3.11: Task 9 - AC #6, #7\n *\n * 提供与 Mantra 客户端 HTTP API 的通信功能\n */\n\nimport type { PrivacyCheckRequest, PrivacyCheckResponse } from \"./types.js\";\nimport { getApiBaseUrl, getPort } from \"./config.js\";\n\n/** 请求超时时间(毫秒) */\nconst REQUEST_TIMEOUT = 3000;\n\n/**\n * 检查 Mantra 客户端是否在运行\n *\n * @returns true 如果客户端正在运行\n */\nexport async function checkClientRunning(): Promise<boolean> {\n const baseUrl = getApiBaseUrl();\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);\n\n const response = await fetch(`${baseUrl}/api/health`, {\n method: \"GET\",\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * 发送隐私检查请求到 Mantra 客户端\n *\n * @param prompt - 待检查的内容\n * @returns 检查响应或 null(如果连接失败)\n */\nexport async function checkPrivacy(\n prompt: string\n): Promise<PrivacyCheckResponse | null> {\n const baseUrl = getApiBaseUrl();\n\n const request: PrivacyCheckRequest = {\n prompt,\n context: {\n tool: \"claude-code\",\n timestamp: new Date().toISOString(),\n },\n };\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);\n\n const response = await fetch(`${baseUrl}/api/privacy/check`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.error(\n `[mantra-privacy-hook] API error: ${response.status} ${response.statusText}`\n );\n return null;\n }\n\n return (await response.json()) as PrivacyCheckResponse;\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n console.error(\"[mantra-privacy-hook] Request timeout\");\n } else {\n console.error(`[mantra-privacy-hook] Connection error: ${error.message}`);\n }\n }\n return null;\n }\n}\n\n/**\n * 获取客户端状态信息\n *\n * @returns 状态信息对象\n */\nexport async function getClientStatus(): Promise<{\n running: boolean;\n port: number;\n url: string;\n}> {\n const port = getPort();\n const url = getApiBaseUrl();\n const running = await checkClientRunning();\n\n return {\n running,\n port,\n url,\n };\n}\n","/**\n * Install 命令\n * Story 3.11: Task 6 - AC #2\n *\n * 安装 Hook 到 Claude Code settings\n */\n\nimport { registerHook, isHookRegistered, claudeSettingsExists, getClaudeSettingsPath } from \"../claude-settings.js\";\nimport { checkClientRunning, getClientStatus } from \"../client-api.js\";\n\nexport async function installCommand(): Promise<void> {\n console.log(\"🔧 Installing Mantra Privacy Hook for Claude Code...\\n\");\n\n // 检查是否已安装\n if (isHookRegistered()) {\n console.log(\"✅ Hook is already installed.\\n\");\n await showStatus();\n return;\n }\n\n // 检查 Claude Code settings 是否存在\n if (!claudeSettingsExists()) {\n console.log(`📁 Creating Claude Code settings at: ${getClaudeSettingsPath()}\\n`);\n }\n\n // 注册 Hook\n const success = registerHook();\n\n if (success) {\n console.log(\"✅ Hook installed successfully!\\n\");\n console.log(\" The hook will check your prompts for sensitive information\");\n console.log(\" before sending them to Claude Code.\\n\");\n await showStatus();\n } else {\n console.error(\"❌ Failed to install hook.\\n\");\n console.error(\" Please check that you have write permission to:\");\n console.error(` ${getClaudeSettingsPath()}\\n`);\n process.exit(1);\n }\n}\n\nasync function showStatus(): Promise<void> {\n const status = await getClientStatus();\n \n console.log(\"📊 Status:\");\n console.log(` • Hook installed: ✓`);\n console.log(` • Mantra client: ${status.running ? \"✓ Running\" : \"✗ Not running\"}`);\n console.log(` • API endpoint: ${status.url}`);\n\n if (!status.running) {\n console.log(\"\\n💡 Note: Start Mantra client to enable privacy protection.\");\n }\n console.log(\"\");\n}\n","/**\n * Uninstall 命令\n * Story 3.11: Task 6 - AC #5\n *\n * 从 Claude Code settings 移除 Hook\n */\n\nimport { unregisterHook, isHookRegistered } from \"../claude-settings.js\";\n\nexport async function uninstallCommand(): Promise<void> {\n console.log(\"🔧 Uninstalling Mantra Privacy Hook from Claude Code...\\n\");\n\n // 检查是否已安装\n if (!isHookRegistered()) {\n console.log(\"ℹ️ Hook is not installed.\\n\");\n return;\n }\n\n // 移除 Hook\n const success = unregisterHook();\n\n if (success) {\n console.log(\"✅ Hook uninstalled successfully!\\n\");\n console.log(\" Privacy protection is now disabled for Claude Code.\");\n console.log(\" Run 'mantra-privacy-hook install' to re-enable.\\n\");\n } else {\n console.error(\"❌ Failed to uninstall hook.\\n\");\n process.exit(1);\n }\n}\n","/**\n * Status 命令\n * Story 3.11: Task 6 - AC #6\n *\n * 显示 Hook 安装状态和客户端连接状态\n */\n\nimport { isHookRegistered, getClaudeSettingsPath, claudeSettingsExists } from \"../claude-settings.js\";\nimport { getClientStatus } from \"../client-api.js\";\nimport { getMantraConfigDir } from \"../config.js\";\n\nexport async function statusCommand(): Promise<void> {\n console.log(\"📊 Mantra Privacy Hook Status\\n\");\n\n // Hook 安装状态\n const hookInstalled = isHookRegistered();\n console.log(`Hook Installation:`);\n console.log(` • Installed: ${hookInstalled ? \"✓ Yes\" : \"✗ No\"}`);\n console.log(` • Claude settings: ${getClaudeSettingsPath()}`);\n console.log(` • Settings exists: ${claudeSettingsExists() ? \"✓ Yes\" : \"✗ No\"}`);\n console.log(\"\");\n\n // 客户端状态\n const status = await getClientStatus();\n console.log(`Mantra Client:`);\n console.log(` • Running: ${status.running ? \"✓ Yes\" : \"✗ No\"}`);\n console.log(` • API Port: ${status.port}`);\n console.log(` • API URL: ${status.url}`);\n console.log(` • Config dir: ${getMantraConfigDir()}`);\n console.log(\"\");\n\n // 整体状态\n if (hookInstalled && status.running) {\n console.log(\"🟢 Privacy protection is ACTIVE\\n\");\n } else if (hookInstalled && !status.running) {\n console.log(\"🟡 Hook installed but client not running\");\n console.log(\" Start Mantra client to enable protection.\\n\");\n } else if (!hookInstalled && status.running) {\n console.log(\"🟡 Client running but hook not installed\");\n console.log(\" Run 'mantra-privacy-hook install' to enable.\\n\");\n } else {\n console.log(\"🔴 Privacy protection is INACTIVE\");\n console.log(\" Run 'mantra-privacy-hook install' and start Mantra client.\\n\");\n }\n}\n","/**\n * Hook 处理器\n * Story 3.11: Task 8 - AC #3\n *\n * 处理 Claude Code Hook 调用:\n * - 从 stdin 读取数据\n * - 调用 Mantra 客户端检查\n * - 返回适当的 exit code\n */\n\nimport type { ClaudeHookInput } from \"./types.js\";\nimport { checkPrivacy, checkClientRunning } from \"./client-api.js\";\n\n/**\n * 从 stdin 读取 JSON 数据\n */\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = \"\";\n\n process.stdin.setEncoding(\"utf-8\");\n process.stdin.on(\"data\", (chunk) => {\n data += chunk;\n });\n process.stdin.on(\"end\", () => {\n resolve(data);\n });\n process.stdin.on(\"error\", (err) => {\n reject(err);\n });\n });\n}\n\n/**\n * 输出警告信息到 stderr\n */\nfunction warn(message: string): void {\n console.error(`[Mantra Privacy Hook] ${message}`);\n}\n\n/**\n * 输出错误信息到 stderr\n */\nfunction error(message: string): void {\n console.error(`[Mantra Privacy Hook] ⚠️ ${message}`);\n}\n\n/**\n * 处理 Hook 调用\n *\n * Exit codes:\n * - 0: 允许继续(无敏感信息或客户端未运行)\n * - 2: 阻止提交(检测到敏感信息)\n */\nexport async function handleHook(): Promise<void> {\n try {\n // 读取 stdin\n const input = await readStdin();\n\n if (!input.trim()) {\n // 无输入,放行\n process.exit(0);\n }\n\n // 解析输入\n let hookInput: ClaudeHookInput;\n try {\n hookInput = JSON.parse(input) as ClaudeHookInput;\n } catch {\n error(\"Invalid JSON input\");\n process.exit(0); // 解析失败时放行\n }\n\n // 提取 prompt 内容\n const promptContent = hookInput.prompt?.content;\n if (!promptContent) {\n // 无 prompt 内容,放行\n process.exit(0);\n }\n\n // 检查 Mantra 客户端是否运行\n const clientRunning = await checkClientRunning();\n if (!clientRunning) {\n warn(\"Mantra 客户端未运行,隐私保护未启用\");\n process.exit(0); // 客户端未运行时放行 + 警告\n }\n\n // 调用隐私检查 API\n const result = await checkPrivacy(promptContent);\n\n if (!result) {\n warn(\"无法连接到 Mantra 客户端\");\n process.exit(0); // 连接失败时放行 + 警告\n }\n\n if (result.action === \"block\") {\n // 检测到敏感信息,阻止提交\n error(\"检测到敏感信息!\");\n \n if (result.message) {\n console.error(` ${result.message}`);\n }\n\n if (result.matches && result.matches.length > 0) {\n console.error(\" 详情:\");\n for (const match of result.matches) {\n console.error(` - [${match.severity}] ${match.rule_id}: ${match.preview}`);\n }\n }\n\n console.error(\"\");\n console.error(\" 请在 Mantra 客户端中处理敏感信息后重试。\");\n console.error(\"\");\n\n process.exit(2); // Exit code 2 阻止提交\n }\n\n // 允许继续\n process.exit(0);\n } catch (err) {\n error(`处理错误: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(0); // 错误时放行\n }\n}\n","/**\n * Check 命令\n * Story 3.11: Task 8 - AC #3\n *\n * 被 Claude Code Hook 调用,检查 prompt 中的敏感信息\n */\n\nimport { handleHook } from \"../hook-handler.js\";\n\nexport async function checkCommand(): Promise<void> {\n // 直接调用 hook 处理器\n // 它会从 stdin 读取数据并返回适当的 exit code\n await handleHook();\n}\n"],"mappings":"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","names":["error","fs","path","os","platform","error"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -67,19 +67,33 @@ interface HookStatus {
|
|
|
67
67
|
port: number;
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
* Claude Code
|
|
70
|
+
* Claude Code hook 命令配置 (新格式)
|
|
71
71
|
*/
|
|
72
|
-
interface
|
|
72
|
+
interface ClaudeHookCommand {
|
|
73
|
+
type: "command";
|
|
73
74
|
command: string;
|
|
74
|
-
description?: string;
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
|
-
* Claude Code
|
|
77
|
+
* Claude Code hook matcher 配置
|
|
78
|
+
*/
|
|
79
|
+
interface ClaudeHookMatcher {
|
|
80
|
+
tools?: string[];
|
|
81
|
+
[key: string]: unknown;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Claude Code hook 条目 (新格式)
|
|
85
|
+
*/
|
|
86
|
+
interface ClaudeHookEntry {
|
|
87
|
+
matcher: ClaudeHookMatcher;
|
|
88
|
+
hooks: ClaudeHookCommand[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Claude Code settings.json 结构 (新格式)
|
|
78
92
|
*/
|
|
79
93
|
interface ClaudeSettings {
|
|
80
94
|
hooks?: {
|
|
81
|
-
UserPromptSubmit?:
|
|
82
|
-
[key: string]:
|
|
95
|
+
UserPromptSubmit?: ClaudeHookEntry[];
|
|
96
|
+
[key: string]: ClaudeHookEntry[] | undefined;
|
|
83
97
|
};
|
|
84
98
|
[key: string]: unknown;
|
|
85
99
|
}
|
|
@@ -103,9 +117,9 @@ declare const DEFAULT_PORT = 19836;
|
|
|
103
117
|
/**
|
|
104
118
|
* 获取 Mantra 配置目录路径(跨平台)
|
|
105
119
|
*
|
|
106
|
-
* - macOS: ~/Library/Application Support/com.mantra
|
|
107
|
-
* - Linux: ~/.local/share/com.mantra
|
|
108
|
-
* - Windows: %APPDATA%\com.mantra
|
|
120
|
+
* - macOS: ~/Library/Application Support/com.gonewx.mantra/
|
|
121
|
+
* - Linux: ~/.local/share/com.gonewx.mantra/
|
|
122
|
+
* - Windows: %APPDATA%\com.gonewx.mantra\
|
|
109
123
|
*/
|
|
110
124
|
declare function getMantraConfigDir(): string;
|
|
111
125
|
/**
|
|
@@ -159,6 +173,7 @@ declare function getClientStatus(): Promise<{
|
|
|
159
173
|
* Story 3.11: Task 7 - AC #2, #5
|
|
160
174
|
*
|
|
161
175
|
* 管理 Claude Code 的 settings.json 中的 hook 配置
|
|
176
|
+
* 使用新的 hooks 格式 (matcher + hooks 数组)
|
|
162
177
|
*/
|
|
163
178
|
|
|
164
179
|
/**
|
|
@@ -222,4 +237,4 @@ declare function claudeSettingsExists(): boolean;
|
|
|
222
237
|
*/
|
|
223
238
|
declare function handleHook(): Promise<void>;
|
|
224
239
|
|
|
225
|
-
export { type
|
|
240
|
+
export { type ClaudeHookCommand, type ClaudeHookEntry, type ClaudeHookInput, type ClaudeHookMatcher, type ClaudeSettings, DEFAULT_PORT, type HookStatus, type MantraConfig, type MatchInfo, type PrivacyCheckContext, type PrivacyCheckRequest, type PrivacyCheckResponse, checkClientRunning, checkPrivacy, claudeSettingsExists, getApiBaseUrl, getClaudeSettingsPath, getClientStatus, getMantraConfigDir, getPort, handleHook, isHookRegistered, loadClaudeSettings, loadMantraConfig, registerHook, saveClaudeSettings, unregisterHook };
|
package/dist/index.js
CHANGED
|
@@ -9,11 +9,11 @@ function getMantraConfigDir() {
|
|
|
9
9
|
const homeDir = os.homedir();
|
|
10
10
|
switch (platform2) {
|
|
11
11
|
case "darwin":
|
|
12
|
-
return path.join(homeDir, "Library", "Application Support", "com.mantra
|
|
12
|
+
return path.join(homeDir, "Library", "Application Support", "com.gonewx.mantra");
|
|
13
13
|
case "win32":
|
|
14
|
-
return path.join(process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"), "com.mantra
|
|
14
|
+
return path.join(process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"), "com.gonewx.mantra");
|
|
15
15
|
default:
|
|
16
|
-
return path.join(homeDir, ".local", "share", "com.mantra
|
|
16
|
+
return path.join(homeDir, ".local", "share", "com.gonewx.mantra");
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
function loadMantraConfig() {
|
|
@@ -113,9 +113,16 @@ async function getClientStatus() {
|
|
|
113
113
|
import * as fs2 from "fs";
|
|
114
114
|
import * as path2 from "path";
|
|
115
115
|
import * as os2 from "os";
|
|
116
|
-
var
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
var HOOK_COMMAND = "mantra-privacy-hook check";
|
|
117
|
+
var HOOK_ENTRY = {
|
|
118
|
+
matcher: {},
|
|
119
|
+
// 空 matcher 表示匹配所有
|
|
120
|
+
hooks: [
|
|
121
|
+
{
|
|
122
|
+
type: "command",
|
|
123
|
+
command: HOOK_COMMAND
|
|
124
|
+
}
|
|
125
|
+
]
|
|
119
126
|
};
|
|
120
127
|
function getClaudeSettingsPath() {
|
|
121
128
|
const homeDir = os2.homedir();
|
|
@@ -142,6 +149,11 @@ function saveClaudeSettings(settings) {
|
|
|
142
149
|
}
|
|
143
150
|
fs2.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
144
151
|
}
|
|
152
|
+
function hasOurHook(entry) {
|
|
153
|
+
return entry.hooks?.some(
|
|
154
|
+
(hook) => hook.type === "command" && hook.command === HOOK_COMMAND
|
|
155
|
+
) ?? false;
|
|
156
|
+
}
|
|
145
157
|
function registerHook() {
|
|
146
158
|
try {
|
|
147
159
|
const settings = loadClaudeSettings();
|
|
@@ -151,13 +163,11 @@ function registerHook() {
|
|
|
151
163
|
if (!settings.hooks.UserPromptSubmit) {
|
|
152
164
|
settings.hooks.UserPromptSubmit = [];
|
|
153
165
|
}
|
|
154
|
-
const alreadyRegistered = settings.hooks.UserPromptSubmit.some(
|
|
155
|
-
(hook) => hook.command === HOOK_CONFIG.command
|
|
156
|
-
);
|
|
166
|
+
const alreadyRegistered = settings.hooks.UserPromptSubmit.some(hasOurHook);
|
|
157
167
|
if (alreadyRegistered) {
|
|
158
168
|
return true;
|
|
159
169
|
}
|
|
160
|
-
settings.hooks.UserPromptSubmit.push(
|
|
170
|
+
settings.hooks.UserPromptSubmit.push(HOOK_ENTRY);
|
|
161
171
|
saveClaudeSettings(settings);
|
|
162
172
|
return true;
|
|
163
173
|
} catch (error2) {
|
|
@@ -173,7 +183,7 @@ function unregisterHook() {
|
|
|
173
183
|
}
|
|
174
184
|
const originalLength = settings.hooks.UserPromptSubmit.length;
|
|
175
185
|
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
|
|
176
|
-
(
|
|
186
|
+
(entry) => !hasOurHook(entry)
|
|
177
187
|
);
|
|
178
188
|
if (settings.hooks.UserPromptSubmit.length === originalLength) {
|
|
179
189
|
return true;
|
|
@@ -197,9 +207,7 @@ function isHookRegistered() {
|
|
|
197
207
|
if (!settings.hooks?.UserPromptSubmit) {
|
|
198
208
|
return false;
|
|
199
209
|
}
|
|
200
|
-
return settings.hooks.UserPromptSubmit.some(
|
|
201
|
-
(hook) => hook.command === HOOK_CONFIG.command
|
|
202
|
-
);
|
|
210
|
+
return settings.hooks.UserPromptSubmit.some(hasOurHook);
|
|
203
211
|
} catch {
|
|
204
212
|
return false;
|
|
205
213
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/client-api.ts","../src/claude-settings.ts","../src/hook-handler.ts"],"sourcesContent":["/**\n * Configuration utilities\n * Story 3.11: Task 8, 9 - AC #7\n *\n * 读取 Mantra 配置文件获取端口等设置\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { MantraConfig } from \"./types.js\";\n\n/** 默认端口 */\nexport const DEFAULT_PORT = 19836;\n\n/** 配置文件名 */\nconst CONFIG_FILENAME = \"settings.yaml\";\n\n/**\n * 获取 Mantra 配置目录路径(跨平台)\n *\n * - macOS: ~/Library/Application Support/com.mantra.app/\n * - Linux: ~/.local/share/com.mantra.app/\n * - Windows: %APPDATA%\\com.mantra.app\\\n */\nexport function getMantraConfigDir(): string {\n const platform = os.platform();\n const homeDir = os.homedir();\n\n switch (platform) {\n case \"darwin\": // macOS\n return path.join(homeDir, \"Library\", \"Application Support\", \"com.mantra.app\");\n case \"win32\": // Windows\n return path.join(process.env.APPDATA || path.join(homeDir, \"AppData\", \"Roaming\"), \"com.mantra.app\");\n default: // Linux and others\n return path.join(homeDir, \".local\", \"share\", \"com.mantra.app\");\n }\n}\n\n/**\n * 读取 Mantra 配置\n *\n * @returns 配置对象,如果文件不存在则返回默认配置\n */\nexport function loadMantraConfig(): MantraConfig {\n const configDir = getMantraConfigDir();\n const configPath = path.join(configDir, CONFIG_FILENAME);\n\n try {\n if (!fs.existsSync(configPath)) {\n return { local_api_port: DEFAULT_PORT };\n }\n\n const content = fs.readFileSync(configPath, \"utf-8\");\n // 简单的 YAML 解析(只需要读取 local_api_port)\n const portMatch = content.match(/local_api_port:\\s*(\\d+)/);\n if (portMatch) {\n return { local_api_port: parseInt(portMatch[1], 10) };\n }\n return { local_api_port: DEFAULT_PORT };\n } catch {\n return { local_api_port: DEFAULT_PORT };\n }\n}\n\n/**\n * 获取当前配置的端口\n */\nexport function getPort(): number {\n const config = loadMantraConfig();\n return config.local_api_port ?? DEFAULT_PORT;\n}\n\n/**\n * 获取 Mantra API 基础 URL\n */\nexport function getApiBaseUrl(): string {\n const port = getPort();\n return `http://127.0.0.1:${port}`;\n}\n","/**\n * Client API - Mantra 客户端通信模块\n * Story 3.11: Task 9 - AC #6, #7\n *\n * 提供与 Mantra 客户端 HTTP API 的通信功能\n */\n\nimport type { PrivacyCheckRequest, PrivacyCheckResponse } from \"./types.js\";\nimport { getApiBaseUrl, getPort } from \"./config.js\";\n\n/** 请求超时时间(毫秒) */\nconst REQUEST_TIMEOUT = 3000;\n\n/**\n * 检查 Mantra 客户端是否在运行\n *\n * @returns true 如果客户端正在运行\n */\nexport async function checkClientRunning(): Promise<boolean> {\n const baseUrl = getApiBaseUrl();\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);\n\n const response = await fetch(`${baseUrl}/api/health`, {\n method: \"GET\",\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * 发送隐私检查请求到 Mantra 客户端\n *\n * @param prompt - 待检查的内容\n * @returns 检查响应或 null(如果连接失败)\n */\nexport async function checkPrivacy(\n prompt: string\n): Promise<PrivacyCheckResponse | null> {\n const baseUrl = getApiBaseUrl();\n\n const request: PrivacyCheckRequest = {\n prompt,\n context: {\n tool: \"claude-code\",\n timestamp: new Date().toISOString(),\n },\n };\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);\n\n const response = await fetch(`${baseUrl}/api/privacy/check`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.error(\n `[mantra-privacy-hook] API error: ${response.status} ${response.statusText}`\n );\n return null;\n }\n\n return (await response.json()) as PrivacyCheckResponse;\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n console.error(\"[mantra-privacy-hook] Request timeout\");\n } else {\n console.error(`[mantra-privacy-hook] Connection error: ${error.message}`);\n }\n }\n return null;\n }\n}\n\n/**\n * 获取客户端状态信息\n *\n * @returns 状态信息对象\n */\nexport async function getClientStatus(): Promise<{\n running: boolean;\n port: number;\n url: string;\n}> {\n const port = getPort();\n const url = getApiBaseUrl();\n const running = await checkClientRunning();\n\n return {\n running,\n port,\n url,\n };\n}\n","/**\n * Claude Code Settings 管理模块\n * Story 3.11: Task 7 - AC #2, #5\n *\n * 管理 Claude Code 的 settings.json 中的 hook 配置\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { ClaudeSettings, ClaudeHookConfig } from \"./types.js\";\n\n/** Hook 配置 */\nconst HOOK_CONFIG: ClaudeHookConfig = {\n command: \"mantra-privacy-hook check\",\n description: \"Mantra Privacy Check\",\n};\n\n/**\n * 获取 Claude Code settings.json 路径(跨平台)\n *\n * - macOS: ~/.claude/settings.json\n * - Linux: ~/.claude/settings.json\n * - Windows: %USERPROFILE%\\.claude\\settings.json\n */\nexport function getClaudeSettingsPath(): string {\n const homeDir = os.homedir();\n return path.join(homeDir, \".claude\", \"settings.json\");\n}\n\n/**\n * 读取 Claude Code settings\n *\n * @returns settings 对象,如果文件不存在则返回空对象\n */\nexport function loadClaudeSettings(): ClaudeSettings {\n const settingsPath = getClaudeSettingsPath();\n\n try {\n if (!fs.existsSync(settingsPath)) {\n return {};\n }\n\n const content = fs.readFileSync(settingsPath, \"utf-8\");\n return JSON.parse(content) as ClaudeSettings;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to read Claude settings: ${error}`);\n return {};\n }\n}\n\n/**\n * 保存 Claude Code settings\n *\n * @param settings - settings 对象\n */\nexport function saveClaudeSettings(settings: ClaudeSettings): void {\n const settingsPath = getClaudeSettingsPath();\n const settingsDir = path.dirname(settingsPath);\n\n // 确保目录存在\n if (!fs.existsSync(settingsDir)) {\n fs.mkdirSync(settingsDir, { recursive: true });\n }\n\n // 写入文件(保持格式化)\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), \"utf-8\");\n}\n\n/**\n * 注册 Hook 到 Claude Code\n *\n * @returns true 如果成功注册\n */\nexport function registerHook(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n // 确保 hooks 对象存在\n if (!settings.hooks) {\n settings.hooks = {};\n }\n\n // 确保 UserPromptSubmit 数组存在\n if (!settings.hooks.UserPromptSubmit) {\n settings.hooks.UserPromptSubmit = [];\n }\n\n // 检查是否已经注册\n const alreadyRegistered = settings.hooks.UserPromptSubmit.some(\n (hook) => hook.command === HOOK_CONFIG.command\n );\n\n if (alreadyRegistered) {\n return true; // 已经注册\n }\n\n // 添加 hook\n settings.hooks.UserPromptSubmit.push(HOOK_CONFIG);\n\n // 保存\n saveClaudeSettings(settings);\n return true;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to register hook: ${error}`);\n return false;\n }\n}\n\n/**\n * 从 Claude Code 移除 Hook\n *\n * @returns true 如果成功移除\n */\nexport function unregisterHook(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n // 如果没有 hooks 配置,无需处理\n if (!settings.hooks?.UserPromptSubmit) {\n return true;\n }\n\n // 过滤掉我们的 hook\n const originalLength = settings.hooks.UserPromptSubmit.length;\n settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(\n (hook) => hook.command !== HOOK_CONFIG.command\n );\n\n // 如果没有变化,说明本来就没注册\n if (settings.hooks.UserPromptSubmit.length === originalLength) {\n return true;\n }\n\n // 如果 UserPromptSubmit 数组为空,可以删除它\n if (settings.hooks.UserPromptSubmit.length === 0) {\n delete settings.hooks.UserPromptSubmit;\n }\n\n // 如果 hooks 对象为空,可以删除它\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n\n // 保存\n saveClaudeSettings(settings);\n return true;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to unregister hook: ${error}`);\n return false;\n }\n}\n\n/**\n * 检查 Hook 是否已注册\n *\n * @returns true 如果已注册\n */\nexport function isHookRegistered(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n if (!settings.hooks?.UserPromptSubmit) {\n return false;\n }\n\n return settings.hooks.UserPromptSubmit.some(\n (hook) => hook.command === HOOK_CONFIG.command\n );\n } catch {\n return false;\n }\n}\n\n/**\n * 检查 Claude Code settings 文件是否存在\n */\nexport function claudeSettingsExists(): boolean {\n return fs.existsSync(getClaudeSettingsPath());\n}\n","/**\n * Hook 处理器\n * Story 3.11: Task 8 - AC #3\n *\n * 处理 Claude Code Hook 调用:\n * - 从 stdin 读取数据\n * - 调用 Mantra 客户端检查\n * - 返回适当的 exit code\n */\n\nimport type { ClaudeHookInput } from \"./types.js\";\nimport { checkPrivacy, checkClientRunning } from \"./client-api.js\";\n\n/**\n * 从 stdin 读取 JSON 数据\n */\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = \"\";\n\n process.stdin.setEncoding(\"utf-8\");\n process.stdin.on(\"data\", (chunk) => {\n data += chunk;\n });\n process.stdin.on(\"end\", () => {\n resolve(data);\n });\n process.stdin.on(\"error\", (err) => {\n reject(err);\n });\n });\n}\n\n/**\n * 输出警告信息到 stderr\n */\nfunction warn(message: string): void {\n console.error(`[Mantra Privacy Hook] ${message}`);\n}\n\n/**\n * 输出错误信息到 stderr\n */\nfunction error(message: string): void {\n console.error(`[Mantra Privacy Hook] ⚠️ ${message}`);\n}\n\n/**\n * 处理 Hook 调用\n *\n * Exit codes:\n * - 0: 允许继续(无敏感信息或客户端未运行)\n * - 2: 阻止提交(检测到敏感信息)\n */\nexport async function handleHook(): Promise<void> {\n try {\n // 读取 stdin\n const input = await readStdin();\n\n if (!input.trim()) {\n // 无输入,放行\n process.exit(0);\n }\n\n // 解析输入\n let hookInput: ClaudeHookInput;\n try {\n hookInput = JSON.parse(input) as ClaudeHookInput;\n } catch {\n error(\"Invalid JSON input\");\n process.exit(0); // 解析失败时放行\n }\n\n // 提取 prompt 内容\n const promptContent = hookInput.prompt?.content;\n if (!promptContent) {\n // 无 prompt 内容,放行\n process.exit(0);\n }\n\n // 检查 Mantra 客户端是否运行\n const clientRunning = await checkClientRunning();\n if (!clientRunning) {\n warn(\"Mantra 客户端未运行,隐私保护未启用\");\n process.exit(0); // 客户端未运行时放行 + 警告\n }\n\n // 调用隐私检查 API\n const result = await checkPrivacy(promptContent);\n\n if (!result) {\n warn(\"无法连接到 Mantra 客户端\");\n process.exit(0); // 连接失败时放行 + 警告\n }\n\n if (result.action === \"block\") {\n // 检测到敏感信息,阻止提交\n error(\"检测到敏感信息!\");\n \n if (result.message) {\n console.error(` ${result.message}`);\n }\n\n if (result.matches && result.matches.length > 0) {\n console.error(\" 详情:\");\n for (const match of result.matches) {\n console.error(` - [${match.severity}] ${match.rule_id}: ${match.preview}`);\n }\n }\n\n console.error(\"\");\n console.error(\" 请在 Mantra 客户端中处理敏感信息后重试。\");\n console.error(\"\");\n\n process.exit(2); // Exit code 2 阻止提交\n }\n\n // 允许继续\n process.exit(0);\n } catch (err) {\n error(`处理错误: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(0); // 错误时放行\n }\n}\n"],"mappings":";AAOA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,YAAY,QAAQ;AAIb,IAAM,eAAe;AAG5B,IAAM,kBAAkB;AASjB,SAAS,qBAA6B;AAC3C,QAAMA,YAAc,YAAS;AAC7B,QAAM,UAAa,WAAQ;AAE3B,UAAQA,WAAU;AAAA,IAChB,KAAK;AACH,aAAY,UAAK,SAAS,WAAW,uBAAuB,gBAAgB;AAAA,IAC9E,KAAK;AACH,aAAY,UAAK,QAAQ,IAAI,WAAgB,UAAK,SAAS,WAAW,SAAS,GAAG,gBAAgB;AAAA,IACpG;AACE,aAAY,UAAK,SAAS,UAAU,SAAS,gBAAgB;AAAA,EACjE;AACF;AAOO,SAAS,mBAAiC;AAC/C,QAAM,YAAY,mBAAmB;AACrC,QAAM,aAAkB,UAAK,WAAW,eAAe;AAEvD,MAAI;AACF,QAAI,CAAI,cAAW,UAAU,GAAG;AAC9B,aAAO,EAAE,gBAAgB,aAAa;AAAA,IACxC;AAEA,UAAM,UAAa,gBAAa,YAAY,OAAO;AAEnD,UAAM,YAAY,QAAQ,MAAM,yBAAyB;AACzD,QAAI,WAAW;AACb,aAAO,EAAE,gBAAgB,SAAS,UAAU,CAAC,GAAG,EAAE,EAAE;AAAA,IACtD;AACA,WAAO,EAAE,gBAAgB,aAAa;AAAA,EACxC,QAAQ;AACN,WAAO,EAAE,gBAAgB,aAAa;AAAA,EACxC;AACF;AAKO,SAAS,UAAkB;AAChC,QAAM,SAAS,iBAAiB;AAChC,SAAO,OAAO,kBAAkB;AAClC;AAKO,SAAS,gBAAwB;AACtC,QAAM,OAAO,QAAQ;AACrB,SAAO,oBAAoB,IAAI;AACjC;;;ACpEA,IAAM,kBAAkB;AAOxB,eAAsB,qBAAuC;AAC3D,QAAM,UAAU,cAAc;AAE9B,MAAI;AACF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,eAAe;AAEtE,UAAM,WAAW,MAAM,MAAM,GAAG,OAAO,eAAe;AAAA,MACpD,QAAQ;AAAA,MACR,QAAQ,WAAW;AAAA,IACrB,CAAC;AAED,iBAAa,SAAS;AACtB,WAAO,SAAS;AAAA,EAClB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,aACpB,QACsC;AACtC,QAAM,UAAU,cAAc;AAE9B,QAAM,UAA+B;AAAA,IACnC;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAAA,EACF;AAEA,MAAI;AACF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,eAAe;AAEtE,UAAM,WAAW,MAAM,MAAM,GAAG,OAAO,sBAAsB;AAAA,MAC3D,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,UAAU,OAAO;AAAA,MAC5B,QAAQ,WAAW;AAAA,IACrB,CAAC;AAED,iBAAa,SAAS;AAEtB,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ;AAAA,QACN,oCAAoC,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,MAC5E;AACA,aAAO;AAAA,IACT;AAEA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC9B,SAASC,QAAO;AACd,QAAIA,kBAAiB,OAAO;AAC1B,UAAIA,OAAM,SAAS,cAAc;AAC/B,gBAAQ,MAAM,uCAAuC;AAAA,MACvD,OAAO;AACL,gBAAQ,MAAM,2CAA2CA,OAAM,OAAO,EAAE;AAAA,MAC1E;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,kBAInB;AACD,QAAM,OAAO,QAAQ;AACrB,QAAM,MAAM,cAAc;AAC1B,QAAM,UAAU,MAAM,mBAAmB;AAEzC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACvGA,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AACtB,YAAYC,SAAQ;AAIpB,IAAM,cAAgC;AAAA,EACpC,SAAS;AAAA,EACT,aAAa;AACf;AASO,SAAS,wBAAgC;AAC9C,QAAM,UAAa,YAAQ;AAC3B,SAAY,WAAK,SAAS,WAAW,eAAe;AACtD;AAOO,SAAS,qBAAqC;AACnD,QAAM,eAAe,sBAAsB;AAE3C,MAAI;AACF,QAAI,CAAI,eAAW,YAAY,GAAG;AAChC,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,UAAa,iBAAa,cAAc,OAAO;AACrD,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,SAASC,QAAO;AACd,YAAQ,MAAM,yDAAyDA,MAAK,EAAE;AAC9E,WAAO,CAAC;AAAA,EACV;AACF;AAOO,SAAS,mBAAmB,UAAgC;AACjE,QAAM,eAAe,sBAAsB;AAC3C,QAAM,cAAmB,cAAQ,YAAY;AAG7C,MAAI,CAAI,eAAW,WAAW,GAAG;AAC/B,IAAG,cAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAAA,EAC/C;AAGA,EAAG,kBAAc,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,OAAO;AAC3E;AAOO,SAAS,eAAwB;AACtC,MAAI;AACF,UAAM,WAAW,mBAAmB;AAGpC,QAAI,CAAC,SAAS,OAAO;AACnB,eAAS,QAAQ,CAAC;AAAA,IACpB;AAGA,QAAI,CAAC,SAAS,MAAM,kBAAkB;AACpC,eAAS,MAAM,mBAAmB,CAAC;AAAA,IACrC;AAGA,UAAM,oBAAoB,SAAS,MAAM,iBAAiB;AAAA,MACxD,CAAC,SAAS,KAAK,YAAY,YAAY;AAAA,IACzC;AAEA,QAAI,mBAAmB;AACrB,aAAO;AAAA,IACT;AAGA,aAAS,MAAM,iBAAiB,KAAK,WAAW;AAGhD,uBAAmB,QAAQ;AAC3B,WAAO;AAAA,EACT,SAASA,QAAO;AACd,YAAQ,MAAM,kDAAkDA,MAAK,EAAE;AACvE,WAAO;AAAA,EACT;AACF;AAOO,SAAS,iBAA0B;AACxC,MAAI;AACF,UAAM,WAAW,mBAAmB;AAGpC,QAAI,CAAC,SAAS,OAAO,kBAAkB;AACrC,aAAO;AAAA,IACT;AAGA,UAAM,iBAAiB,SAAS,MAAM,iBAAiB;AACvD,aAAS,MAAM,mBAAmB,SAAS,MAAM,iBAAiB;AAAA,MAChE,CAAC,SAAS,KAAK,YAAY,YAAY;AAAA,IACzC;AAGA,QAAI,SAAS,MAAM,iBAAiB,WAAW,gBAAgB;AAC7D,aAAO;AAAA,IACT;AAGA,QAAI,SAAS,MAAM,iBAAiB,WAAW,GAAG;AAChD,aAAO,SAAS,MAAM;AAAA,IACxB;AAGA,QAAI,OAAO,KAAK,SAAS,KAAK,EAAE,WAAW,GAAG;AAC5C,aAAO,SAAS;AAAA,IAClB;AAGA,uBAAmB,QAAQ;AAC3B,WAAO;AAAA,EACT,SAASA,QAAO;AACd,YAAQ,MAAM,oDAAoDA,MAAK,EAAE;AACzE,WAAO;AAAA,EACT;AACF;AAOO,SAAS,mBAA4B;AAC1C,MAAI;AACF,UAAM,WAAW,mBAAmB;AAEpC,QAAI,CAAC,SAAS,OAAO,kBAAkB;AACrC,aAAO;AAAA,IACT;AAEA,WAAO,SAAS,MAAM,iBAAiB;AAAA,MACrC,CAAC,SAAS,KAAK,YAAY,YAAY;AAAA,IACzC;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,uBAAgC;AAC9C,SAAU,eAAW,sBAAsB,CAAC;AAC9C;;;ACnKA,eAAe,YAA6B;AAC1C,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,OAAO;AAEX,YAAQ,MAAM,YAAY,OAAO;AACjC,YAAQ,MAAM,GAAG,QAAQ,CAAC,UAAU;AAClC,cAAQ;AAAA,IACV,CAAC;AACD,YAAQ,MAAM,GAAG,OAAO,MAAM;AAC5B,cAAQ,IAAI;AAAA,IACd,CAAC;AACD,YAAQ,MAAM,GAAG,SAAS,CAAC,QAAQ;AACjC,aAAO,GAAG;AAAA,IACZ,CAAC;AAAA,EACH,CAAC;AACH;AAKA,SAAS,KAAK,SAAuB;AACnC,UAAQ,MAAM,yBAAyB,OAAO,EAAE;AAClD;AAKA,SAAS,MAAM,SAAuB;AACpC,UAAQ,MAAM,uCAA6B,OAAO,EAAE;AACtD;AASA,eAAsB,aAA4B;AAChD,MAAI;AAEF,UAAM,QAAQ,MAAM,UAAU;AAE9B,QAAI,CAAC,MAAM,KAAK,GAAG;AAEjB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,QAAI;AACJ,QAAI;AACF,kBAAY,KAAK,MAAM,KAAK;AAAA,IAC9B,QAAQ;AACN,YAAM,oBAAoB;AAC1B,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,UAAM,gBAAgB,UAAU,QAAQ;AACxC,QAAI,CAAC,eAAe;AAElB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,UAAM,gBAAgB,MAAM,mBAAmB;AAC/C,QAAI,CAAC,eAAe;AAClB,WAAK,6FAAuB;AAC5B,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,UAAM,SAAS,MAAM,aAAa,aAAa;AAE/C,QAAI,CAAC,QAAQ;AACX,WAAK,0DAAkB;AACvB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,OAAO,WAAW,SAAS;AAE7B,YAAM,kDAAU;AAEhB,UAAI,OAAO,SAAS;AAClB,gBAAQ,MAAM,MAAM,OAAO,OAAO,EAAE;AAAA,MACtC;AAEA,UAAI,OAAO,WAAW,OAAO,QAAQ,SAAS,GAAG;AAC/C,gBAAQ,MAAM,kBAAQ;AACtB,mBAAW,SAAS,OAAO,SAAS;AAClC,kBAAQ,MAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,OAAO,KAAK,MAAM,OAAO,EAAE;AAAA,QAC7E;AAAA,MACF;AAEA,cAAQ,MAAM,EAAE;AAChB,cAAQ,MAAM,6GAA6B;AAC3C,cAAQ,MAAM,EAAE;AAEhB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,YAAQ,KAAK,CAAC;AAAA,EAChB,SAAS,KAAK;AACZ,UAAM,6BAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AACjE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":["platform","error","fs","path","os","error"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/client-api.ts","../src/claude-settings.ts","../src/hook-handler.ts"],"sourcesContent":["/**\n * Configuration utilities\n * Story 3.11: Task 8, 9 - AC #7\n *\n * 读取 Mantra 配置文件获取端口等设置\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { MantraConfig } from \"./types.js\";\n\n/** 默认端口 */\nexport const DEFAULT_PORT = 19836;\n\n/** 配置文件名 */\nconst CONFIG_FILENAME = \"settings.yaml\";\n\n/**\n * 获取 Mantra 配置目录路径(跨平台)\n *\n * - macOS: ~/Library/Application Support/com.gonewx.mantra/\n * - Linux: ~/.local/share/com.gonewx.mantra/\n * - Windows: %APPDATA%\\com.gonewx.mantra\\\n */\nexport function getMantraConfigDir(): string {\n const platform = os.platform();\n const homeDir = os.homedir();\n\n switch (platform) {\n case \"darwin\": // macOS\n return path.join(homeDir, \"Library\", \"Application Support\", \"com.gonewx.mantra\");\n case \"win32\": // Windows\n return path.join(process.env.APPDATA || path.join(homeDir, \"AppData\", \"Roaming\"), \"com.gonewx.mantra\");\n default: // Linux and others\n return path.join(homeDir, \".local\", \"share\", \"com.gonewx.mantra\");\n }\n}\n\n/**\n * 读取 Mantra 配置\n *\n * @returns 配置对象,如果文件不存在则返回默认配置\n */\nexport function loadMantraConfig(): MantraConfig {\n const configDir = getMantraConfigDir();\n const configPath = path.join(configDir, CONFIG_FILENAME);\n\n try {\n if (!fs.existsSync(configPath)) {\n return { local_api_port: DEFAULT_PORT };\n }\n\n const content = fs.readFileSync(configPath, \"utf-8\");\n // 简单的 YAML 解析(只需要读取 local_api_port)\n const portMatch = content.match(/local_api_port:\\s*(\\d+)/);\n if (portMatch) {\n return { local_api_port: parseInt(portMatch[1], 10) };\n }\n return { local_api_port: DEFAULT_PORT };\n } catch {\n return { local_api_port: DEFAULT_PORT };\n }\n}\n\n/**\n * 获取当前配置的端口\n */\nexport function getPort(): number {\n const config = loadMantraConfig();\n return config.local_api_port ?? DEFAULT_PORT;\n}\n\n/**\n * 获取 Mantra API 基础 URL\n */\nexport function getApiBaseUrl(): string {\n const port = getPort();\n return `http://127.0.0.1:${port}`;\n}\n","/**\n * Client API - Mantra 客户端通信模块\n * Story 3.11: Task 9 - AC #6, #7\n *\n * 提供与 Mantra 客户端 HTTP API 的通信功能\n */\n\nimport type { PrivacyCheckRequest, PrivacyCheckResponse } from \"./types.js\";\nimport { getApiBaseUrl, getPort } from \"./config.js\";\n\n/** 请求超时时间(毫秒) */\nconst REQUEST_TIMEOUT = 3000;\n\n/**\n * 检查 Mantra 客户端是否在运行\n *\n * @returns true 如果客户端正在运行\n */\nexport async function checkClientRunning(): Promise<boolean> {\n const baseUrl = getApiBaseUrl();\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);\n\n const response = await fetch(`${baseUrl}/api/health`, {\n method: \"GET\",\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * 发送隐私检查请求到 Mantra 客户端\n *\n * @param prompt - 待检查的内容\n * @returns 检查响应或 null(如果连接失败)\n */\nexport async function checkPrivacy(\n prompt: string\n): Promise<PrivacyCheckResponse | null> {\n const baseUrl = getApiBaseUrl();\n\n const request: PrivacyCheckRequest = {\n prompt,\n context: {\n tool: \"claude-code\",\n timestamp: new Date().toISOString(),\n },\n };\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);\n\n const response = await fetch(`${baseUrl}/api/privacy/check`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.error(\n `[mantra-privacy-hook] API error: ${response.status} ${response.statusText}`\n );\n return null;\n }\n\n return (await response.json()) as PrivacyCheckResponse;\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n console.error(\"[mantra-privacy-hook] Request timeout\");\n } else {\n console.error(`[mantra-privacy-hook] Connection error: ${error.message}`);\n }\n }\n return null;\n }\n}\n\n/**\n * 获取客户端状态信息\n *\n * @returns 状态信息对象\n */\nexport async function getClientStatus(): Promise<{\n running: boolean;\n port: number;\n url: string;\n}> {\n const port = getPort();\n const url = getApiBaseUrl();\n const running = await checkClientRunning();\n\n return {\n running,\n port,\n url,\n };\n}\n","/**\n * Claude Code Settings 管理模块\n * Story 3.11: Task 7 - AC #2, #5\n *\n * 管理 Claude Code 的 settings.json 中的 hook 配置\n * 使用新的 hooks 格式 (matcher + hooks 数组)\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport type { ClaudeSettings, ClaudeHookEntry, ClaudeHookCommand } from \"./types.js\";\n\n/** Hook 命令 */\nconst HOOK_COMMAND = \"mantra-privacy-hook check\";\n\n/** Hook 配置 (新格式) */\nconst HOOK_ENTRY: ClaudeHookEntry = {\n matcher: {}, // 空 matcher 表示匹配所有\n hooks: [\n {\n type: \"command\",\n command: HOOK_COMMAND,\n },\n ],\n};\n\n/**\n * 获取 Claude Code settings.json 路径(跨平台)\n *\n * - macOS: ~/.claude/settings.json\n * - Linux: ~/.claude/settings.json\n * - Windows: %USERPROFILE%\\.claude\\settings.json\n */\nexport function getClaudeSettingsPath(): string {\n const homeDir = os.homedir();\n return path.join(homeDir, \".claude\", \"settings.json\");\n}\n\n/**\n * 读取 Claude Code settings\n *\n * @returns settings 对象,如果文件不存在则返回空对象\n */\nexport function loadClaudeSettings(): ClaudeSettings {\n const settingsPath = getClaudeSettingsPath();\n\n try {\n if (!fs.existsSync(settingsPath)) {\n return {};\n }\n\n const content = fs.readFileSync(settingsPath, \"utf-8\");\n return JSON.parse(content) as ClaudeSettings;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to read Claude settings: ${error}`);\n return {};\n }\n}\n\n/**\n * 保存 Claude Code settings\n *\n * @param settings - settings 对象\n */\nexport function saveClaudeSettings(settings: ClaudeSettings): void {\n const settingsPath = getClaudeSettingsPath();\n const settingsDir = path.dirname(settingsPath);\n\n // 确保目录存在\n if (!fs.existsSync(settingsDir)) {\n fs.mkdirSync(settingsDir, { recursive: true });\n }\n\n // 写入文件(保持格式化)\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), \"utf-8\");\n}\n\n/**\n * 检查 hook entry 是否包含我们的命令\n */\nfunction hasOurHook(entry: ClaudeHookEntry): boolean {\n return entry.hooks?.some(\n (hook: ClaudeHookCommand) => hook.type === \"command\" && hook.command === HOOK_COMMAND\n ) ?? false;\n}\n\n/**\n * 注册 Hook 到 Claude Code\n *\n * @returns true 如果成功注册\n */\nexport function registerHook(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n // 确保 hooks 对象存在\n if (!settings.hooks) {\n settings.hooks = {};\n }\n\n // 确保 UserPromptSubmit 数组存在\n if (!settings.hooks.UserPromptSubmit) {\n settings.hooks.UserPromptSubmit = [];\n }\n\n // 检查是否已经注册\n const alreadyRegistered = settings.hooks.UserPromptSubmit.some(hasOurHook);\n\n if (alreadyRegistered) {\n return true; // 已经注册\n }\n\n // 添加 hook entry\n settings.hooks.UserPromptSubmit.push(HOOK_ENTRY);\n\n // 保存\n saveClaudeSettings(settings);\n return true;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to register hook: ${error}`);\n return false;\n }\n}\n\n/**\n * 从 Claude Code 移除 Hook\n *\n * @returns true 如果成功移除\n */\nexport function unregisterHook(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n // 如果没有 hooks 配置,无需处理\n if (!settings.hooks?.UserPromptSubmit) {\n return true;\n }\n\n // 过滤掉包含我们 hook 的 entry\n const originalLength = settings.hooks.UserPromptSubmit.length;\n settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(\n (entry) => !hasOurHook(entry)\n );\n\n // 如果没有变化,说明本来就没注册\n if (settings.hooks.UserPromptSubmit.length === originalLength) {\n return true;\n }\n\n // 如果 UserPromptSubmit 数组为空,可以删除它\n if (settings.hooks.UserPromptSubmit.length === 0) {\n delete settings.hooks.UserPromptSubmit;\n }\n\n // 如果 hooks 对象为空,可以删除它\n if (Object.keys(settings.hooks).length === 0) {\n delete settings.hooks;\n }\n\n // 保存\n saveClaudeSettings(settings);\n return true;\n } catch (error) {\n console.error(`[mantra-privacy-hook] Failed to unregister hook: ${error}`);\n return false;\n }\n}\n\n/**\n * 检查 Hook 是否已注册\n *\n * @returns true 如果已注册\n */\nexport function isHookRegistered(): boolean {\n try {\n const settings = loadClaudeSettings();\n\n if (!settings.hooks?.UserPromptSubmit) {\n return false;\n }\n\n return settings.hooks.UserPromptSubmit.some(hasOurHook);\n } catch {\n return false;\n }\n}\n\n/**\n * 检查 Claude Code settings 文件是否存在\n */\nexport function claudeSettingsExists(): boolean {\n return fs.existsSync(getClaudeSettingsPath());\n}\n","/**\n * Hook 处理器\n * Story 3.11: Task 8 - AC #3\n *\n * 处理 Claude Code Hook 调用:\n * - 从 stdin 读取数据\n * - 调用 Mantra 客户端检查\n * - 返回适当的 exit code\n */\n\nimport type { ClaudeHookInput } from \"./types.js\";\nimport { checkPrivacy, checkClientRunning } from \"./client-api.js\";\n\n/**\n * 从 stdin 读取 JSON 数据\n */\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = \"\";\n\n process.stdin.setEncoding(\"utf-8\");\n process.stdin.on(\"data\", (chunk) => {\n data += chunk;\n });\n process.stdin.on(\"end\", () => {\n resolve(data);\n });\n process.stdin.on(\"error\", (err) => {\n reject(err);\n });\n });\n}\n\n/**\n * 输出警告信息到 stderr\n */\nfunction warn(message: string): void {\n console.error(`[Mantra Privacy Hook] ${message}`);\n}\n\n/**\n * 输出错误信息到 stderr\n */\nfunction error(message: string): void {\n console.error(`[Mantra Privacy Hook] ⚠️ ${message}`);\n}\n\n/**\n * 处理 Hook 调用\n *\n * Exit codes:\n * - 0: 允许继续(无敏感信息或客户端未运行)\n * - 2: 阻止提交(检测到敏感信息)\n */\nexport async function handleHook(): Promise<void> {\n try {\n // 读取 stdin\n const input = await readStdin();\n\n if (!input.trim()) {\n // 无输入,放行\n process.exit(0);\n }\n\n // 解析输入\n let hookInput: ClaudeHookInput;\n try {\n hookInput = JSON.parse(input) as ClaudeHookInput;\n } catch {\n error(\"Invalid JSON input\");\n process.exit(0); // 解析失败时放行\n }\n\n // 提取 prompt 内容\n const promptContent = hookInput.prompt?.content;\n if (!promptContent) {\n // 无 prompt 内容,放行\n process.exit(0);\n }\n\n // 检查 Mantra 客户端是否运行\n const clientRunning = await checkClientRunning();\n if (!clientRunning) {\n warn(\"Mantra 客户端未运行,隐私保护未启用\");\n process.exit(0); // 客户端未运行时放行 + 警告\n }\n\n // 调用隐私检查 API\n const result = await checkPrivacy(promptContent);\n\n if (!result) {\n warn(\"无法连接到 Mantra 客户端\");\n process.exit(0); // 连接失败时放行 + 警告\n }\n\n if (result.action === \"block\") {\n // 检测到敏感信息,阻止提交\n error(\"检测到敏感信息!\");\n \n if (result.message) {\n console.error(` ${result.message}`);\n }\n\n if (result.matches && result.matches.length > 0) {\n console.error(\" 详情:\");\n for (const match of result.matches) {\n console.error(` - [${match.severity}] ${match.rule_id}: ${match.preview}`);\n }\n }\n\n console.error(\"\");\n console.error(\" 请在 Mantra 客户端中处理敏感信息后重试。\");\n console.error(\"\");\n\n process.exit(2); // Exit code 2 阻止提交\n }\n\n // 允许继续\n process.exit(0);\n } catch (err) {\n error(`处理错误: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(0); // 错误时放行\n }\n}\n"],"mappings":"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","names":["platform","error","fs","path","os","error"]}
|