@mantra-hq/privacy-hook 0.1.0 → 0.1.1
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 +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- 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
|
@@ -113,11 +113,11 @@ function getMantraConfigDir() {
|
|
|
113
113
|
const homeDir = os2.homedir();
|
|
114
114
|
switch (platform2) {
|
|
115
115
|
case "darwin":
|
|
116
|
-
return path2.join(homeDir, "Library", "Application Support", "com.mantra
|
|
116
|
+
return path2.join(homeDir, "Library", "Application Support", "com.gonewx.mantra");
|
|
117
117
|
case "win32":
|
|
118
|
-
return path2.join(process.env.APPDATA || path2.join(homeDir, "AppData", "Roaming"), "com.mantra
|
|
118
|
+
return path2.join(process.env.APPDATA || path2.join(homeDir, "AppData", "Roaming"), "com.gonewx.mantra");
|
|
119
119
|
default:
|
|
120
|
-
return path2.join(homeDir, ".local", "share", "com.mantra
|
|
120
|
+
return path2.join(homeDir, ".local", "share", "com.gonewx.mantra");
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
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 */\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"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -103,9 +103,9 @@ declare const DEFAULT_PORT = 19836;
|
|
|
103
103
|
/**
|
|
104
104
|
* 获取 Mantra 配置目录路径(跨平台)
|
|
105
105
|
*
|
|
106
|
-
* - macOS: ~/Library/Application Support/com.mantra
|
|
107
|
-
* - Linux: ~/.local/share/com.mantra
|
|
108
|
-
* - Windows: %APPDATA%\com.mantra
|
|
106
|
+
* - macOS: ~/Library/Application Support/com.gonewx.mantra/
|
|
107
|
+
* - Linux: ~/.local/share/com.gonewx.mantra/
|
|
108
|
+
* - Windows: %APPDATA%\com.gonewx.mantra\
|
|
109
109
|
*/
|
|
110
110
|
declare function getMantraConfigDir(): string;
|
|
111
111
|
/**
|
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() {
|
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":"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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 */\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"]}
|