@mantra-hq/privacy-hook 0.1.1 → 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/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 HOOK_CONFIG = {
11
- command: "mantra-privacy-hook check",
12
- description: "Mantra Privacy Check"
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(HOOK_CONFIG);
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
- (hook) => hook.command !== HOOK_CONFIG.command
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
  }
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.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"]}
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 settings.json 中的 hook 配置
70
+ * Claude Code hook 命令配置 (新格式)
71
71
  */
72
- interface ClaudeHookConfig {
72
+ interface ClaudeHookCommand {
73
+ type: "command";
73
74
  command: string;
74
- description?: string;
75
75
  }
76
76
  /**
77
- * Claude Code settings.json 结构
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?: ClaudeHookConfig[];
82
- [key: string]: ClaudeHookConfig[] | undefined;
95
+ UserPromptSubmit?: ClaudeHookEntry[];
96
+ [key: string]: ClaudeHookEntry[] | undefined;
83
97
  };
84
98
  [key: string]: unknown;
85
99
  }
@@ -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 ClaudeHookConfig, type ClaudeHookInput, 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 };
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
@@ -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 HOOK_CONFIG = {
117
- command: "mantra-privacy-hook check",
118
- description: "Mantra Privacy Check"
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(HOOK_CONFIG);
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
- (hook) => hook.command !== HOOK_CONFIG.command
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.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 */\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":"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","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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantra-hq/privacy-hook",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Privacy protection hook for AI coding tools - Claude Code, Cursor, etc.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",