@mantra-hq/privacy-hook 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-01-18
9
+
10
+ ### Added
11
+
12
+ - Initial release of `@mantra-hq/privacy-hook`
13
+ - **CLI Commands**:
14
+ - `install` - Install hook to Claude Code settings
15
+ - `uninstall` - Remove hook from Claude Code settings
16
+ - `status` - Show hook and client status
17
+ - `check` - Perform privacy check (called by Claude Code hook)
18
+ - **Privacy Check API**:
19
+ - `checkPrivacy()` - Send prompt to Mantra client for privacy check
20
+ - `checkClientRunning()` - Check if Mantra client is running
21
+ - `getClientStatus()` - Get detailed client status
22
+ - **Claude Code Integration**:
23
+ - `registerHook()` - Register hook in Claude Code settings
24
+ - `unregisterHook()` - Remove hook from Claude Code settings
25
+ - `isHookRegistered()` - Check if hook is registered
26
+ - **Configuration**:
27
+ - Cross-platform config path detection (macOS, Linux, Windows)
28
+ - Configurable API port (default: 19836)
29
+ - Read port from Mantra client settings file
30
+ - **Architecture**:
31
+ - Thin client design - all detection logic in Mantra client
32
+ - HTTP communication with local Mantra client API
33
+ - Graceful handling when client is offline (warn + allow)
34
+
35
+ ### Technical Details
36
+
37
+ - Built with TypeScript 5.x
38
+ - Uses Commander.js for CLI
39
+ - Bundled with tsup
40
+ - Tested with Vitest
41
+ - Requires Node.js >= 18.0.0
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # @mantra-hq/privacy-hook
2
+
3
+ Privacy protection hook for AI coding tools - integrates with Mantra client for real-time sensitive information detection.
4
+
5
+ ## Overview
6
+
7
+ This package provides a hook that integrates with AI coding tools (like Claude Code) to detect and block sensitive information before it's sent to AI services. The hook communicates with the Mantra client running locally to perform privacy checks.
8
+
9
+ ## Architecture
10
+
11
+ ```
12
+ ┌─────────────────┐ HTTP POST ┌──────────────────────┐
13
+ │ Claude Code │ ───────────────▶ │ Mantra Client │
14
+ │ Hook (thin) │ /api/privacy/check │ ┌────────────────┐ │
15
+ │ │ │ │ Detection │ │
16
+ │ - Intercept │ ◀─────────────── │ │ Engine │ │
17
+ │ prompt │ {allow/block} │ │ Rules Manager │ │
18
+ │ - Forward to │ │ │ Records │ │
19
+ │ client │ │ └────────────────┘ │
20
+ └─────────────────┘ └──────────────────────┘
21
+ ```
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install -g @mantra-hq/privacy-hook
27
+ # or
28
+ pnpm add -g @mantra-hq/privacy-hook
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ### Install Hook to Claude Code
34
+
35
+ ```bash
36
+ mantra-privacy-hook install
37
+ ```
38
+
39
+ This will register the hook in `~/.claude/settings.json` to intercept prompts before they're sent.
40
+
41
+ ### Check Status
42
+
43
+ ```bash
44
+ mantra-privacy-hook status
45
+ ```
46
+
47
+ Shows:
48
+ - Hook installation status
49
+ - Mantra client connection status
50
+ - API endpoint configuration
51
+
52
+ ### Uninstall Hook
53
+
54
+ ```bash
55
+ mantra-privacy-hook uninstall
56
+ ```
57
+
58
+ Removes the hook from Claude Code settings.
59
+
60
+ ## How It Works
61
+
62
+ 1. **Claude Code Hook Trigger**: When you submit a prompt in Claude Code, the `UserPromptSubmit` hook is triggered.
63
+
64
+ 2. **Privacy Check**: The hook sends your prompt to the Mantra client's local API (`http://127.0.0.1:19836/api/privacy/check`).
65
+
66
+ 3. **Detection**: The Mantra client scans the prompt using its privacy detection engine.
67
+
68
+ 4. **Response**:
69
+ - If no sensitive information is found: `exit 0` (allow)
70
+ - If sensitive information is detected: `exit 2` (block) + warning message
71
+
72
+ 5. **Client Offline Handling**: If the Mantra client is not running, the hook shows a warning and allows the prompt to proceed.
73
+
74
+ ## Configuration
75
+
76
+ The hook reads the Mantra client's port configuration from the settings file:
77
+
78
+ - **macOS**: `~/Library/Application Support/com.mantra.app/settings.yaml`
79
+ - **Linux**: `~/.local/share/com.mantra.app/settings.yaml`
80
+ - **Windows**: `%APPDATA%\com.mantra.app\settings.yaml`
81
+
82
+ Default port: `19836`
83
+
84
+ You can change the port in the Mantra client's Settings page.
85
+
86
+ ## API Reference
87
+
88
+ ### Types
89
+
90
+ ```typescript
91
+ interface PrivacyCheckRequest {
92
+ prompt: string;
93
+ context?: {
94
+ tool: string;
95
+ timestamp?: string;
96
+ };
97
+ }
98
+
99
+ interface PrivacyCheckResponse {
100
+ action: "allow" | "block";
101
+ matches?: MatchInfo[];
102
+ message?: string;
103
+ }
104
+
105
+ interface MatchInfo {
106
+ rule_id: string;
107
+ severity: string;
108
+ preview: string;
109
+ }
110
+ ```
111
+
112
+ ### Programmatic Usage
113
+
114
+ ```typescript
115
+ import {
116
+ checkPrivacy,
117
+ checkClientRunning,
118
+ registerHook,
119
+ unregisterHook,
120
+ } from "@mantra-hq/privacy-hook";
121
+
122
+ // Check if Mantra client is running
123
+ const isRunning = await checkClientRunning();
124
+
125
+ // Perform privacy check
126
+ const result = await checkPrivacy("Your text to check");
127
+ if (result?.action === "block") {
128
+ console.log("Sensitive information detected:", result.matches);
129
+ }
130
+
131
+ // Register/unregister hook programmatically
132
+ registerHook();
133
+ unregisterHook();
134
+ ```
135
+
136
+ ## Requirements
137
+
138
+ - Node.js >= 18.0.0
139
+ - Mantra client installed and running for privacy protection
140
+
141
+ ## License
142
+
143
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/claude-settings.ts
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import * as os from "os";
10
+ var HOOK_CONFIG = {
11
+ command: "mantra-privacy-hook check",
12
+ description: "Mantra Privacy Check"
13
+ };
14
+ function getClaudeSettingsPath() {
15
+ const homeDir = os.homedir();
16
+ return path.join(homeDir, ".claude", "settings.json");
17
+ }
18
+ function loadClaudeSettings() {
19
+ const settingsPath = getClaudeSettingsPath();
20
+ try {
21
+ if (!fs.existsSync(settingsPath)) {
22
+ return {};
23
+ }
24
+ const content = fs.readFileSync(settingsPath, "utf-8");
25
+ return JSON.parse(content);
26
+ } catch (error2) {
27
+ console.error(`[mantra-privacy-hook] Failed to read Claude settings: ${error2}`);
28
+ return {};
29
+ }
30
+ }
31
+ function saveClaudeSettings(settings) {
32
+ const settingsPath = getClaudeSettingsPath();
33
+ const settingsDir = path.dirname(settingsPath);
34
+ if (!fs.existsSync(settingsDir)) {
35
+ fs.mkdirSync(settingsDir, { recursive: true });
36
+ }
37
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
38
+ }
39
+ function registerHook() {
40
+ try {
41
+ const settings = loadClaudeSettings();
42
+ if (!settings.hooks) {
43
+ settings.hooks = {};
44
+ }
45
+ if (!settings.hooks.UserPromptSubmit) {
46
+ settings.hooks.UserPromptSubmit = [];
47
+ }
48
+ const alreadyRegistered = settings.hooks.UserPromptSubmit.some(
49
+ (hook) => hook.command === HOOK_CONFIG.command
50
+ );
51
+ if (alreadyRegistered) {
52
+ return true;
53
+ }
54
+ settings.hooks.UserPromptSubmit.push(HOOK_CONFIG);
55
+ saveClaudeSettings(settings);
56
+ return true;
57
+ } catch (error2) {
58
+ console.error(`[mantra-privacy-hook] Failed to register hook: ${error2}`);
59
+ return false;
60
+ }
61
+ }
62
+ function unregisterHook() {
63
+ try {
64
+ const settings = loadClaudeSettings();
65
+ if (!settings.hooks?.UserPromptSubmit) {
66
+ return true;
67
+ }
68
+ const originalLength = settings.hooks.UserPromptSubmit.length;
69
+ settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
70
+ (hook) => hook.command !== HOOK_CONFIG.command
71
+ );
72
+ if (settings.hooks.UserPromptSubmit.length === originalLength) {
73
+ return true;
74
+ }
75
+ if (settings.hooks.UserPromptSubmit.length === 0) {
76
+ delete settings.hooks.UserPromptSubmit;
77
+ }
78
+ if (Object.keys(settings.hooks).length === 0) {
79
+ delete settings.hooks;
80
+ }
81
+ saveClaudeSettings(settings);
82
+ return true;
83
+ } catch (error2) {
84
+ console.error(`[mantra-privacy-hook] Failed to unregister hook: ${error2}`);
85
+ return false;
86
+ }
87
+ }
88
+ function isHookRegistered() {
89
+ try {
90
+ const settings = loadClaudeSettings();
91
+ if (!settings.hooks?.UserPromptSubmit) {
92
+ return false;
93
+ }
94
+ return settings.hooks.UserPromptSubmit.some(
95
+ (hook) => hook.command === HOOK_CONFIG.command
96
+ );
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+ function claudeSettingsExists() {
102
+ return fs.existsSync(getClaudeSettingsPath());
103
+ }
104
+
105
+ // src/config.ts
106
+ import * as fs2 from "fs";
107
+ import * as path2 from "path";
108
+ import * as os2 from "os";
109
+ var DEFAULT_PORT = 19836;
110
+ var CONFIG_FILENAME = "settings.yaml";
111
+ function getMantraConfigDir() {
112
+ const platform2 = os2.platform();
113
+ const homeDir = os2.homedir();
114
+ switch (platform2) {
115
+ case "darwin":
116
+ return path2.join(homeDir, "Library", "Application Support", "com.mantra.app");
117
+ case "win32":
118
+ return path2.join(process.env.APPDATA || path2.join(homeDir, "AppData", "Roaming"), "com.mantra.app");
119
+ default:
120
+ return path2.join(homeDir, ".local", "share", "com.mantra.app");
121
+ }
122
+ }
123
+ function loadMantraConfig() {
124
+ const configDir = getMantraConfigDir();
125
+ const configPath = path2.join(configDir, CONFIG_FILENAME);
126
+ try {
127
+ if (!fs2.existsSync(configPath)) {
128
+ return { local_api_port: DEFAULT_PORT };
129
+ }
130
+ const content = fs2.readFileSync(configPath, "utf-8");
131
+ const portMatch = content.match(/local_api_port:\s*(\d+)/);
132
+ if (portMatch) {
133
+ return { local_api_port: parseInt(portMatch[1], 10) };
134
+ }
135
+ return { local_api_port: DEFAULT_PORT };
136
+ } catch {
137
+ return { local_api_port: DEFAULT_PORT };
138
+ }
139
+ }
140
+ function getPort() {
141
+ const config = loadMantraConfig();
142
+ return config.local_api_port ?? DEFAULT_PORT;
143
+ }
144
+ function getApiBaseUrl() {
145
+ const port = getPort();
146
+ return `http://127.0.0.1:${port}`;
147
+ }
148
+
149
+ // src/client-api.ts
150
+ var REQUEST_TIMEOUT = 3e3;
151
+ async function checkClientRunning() {
152
+ const baseUrl = getApiBaseUrl();
153
+ try {
154
+ const controller = new AbortController();
155
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
156
+ const response = await fetch(`${baseUrl}/api/health`, {
157
+ method: "GET",
158
+ signal: controller.signal
159
+ });
160
+ clearTimeout(timeoutId);
161
+ return response.ok;
162
+ } catch {
163
+ return false;
164
+ }
165
+ }
166
+ async function checkPrivacy(prompt) {
167
+ const baseUrl = getApiBaseUrl();
168
+ const request = {
169
+ prompt,
170
+ context: {
171
+ tool: "claude-code",
172
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
173
+ }
174
+ };
175
+ try {
176
+ const controller = new AbortController();
177
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
178
+ const response = await fetch(`${baseUrl}/api/privacy/check`, {
179
+ method: "POST",
180
+ headers: {
181
+ "Content-Type": "application/json"
182
+ },
183
+ body: JSON.stringify(request),
184
+ signal: controller.signal
185
+ });
186
+ clearTimeout(timeoutId);
187
+ if (!response.ok) {
188
+ console.error(
189
+ `[mantra-privacy-hook] API error: ${response.status} ${response.statusText}`
190
+ );
191
+ return null;
192
+ }
193
+ return await response.json();
194
+ } catch (error2) {
195
+ if (error2 instanceof Error) {
196
+ if (error2.name === "AbortError") {
197
+ console.error("[mantra-privacy-hook] Request timeout");
198
+ } else {
199
+ console.error(`[mantra-privacy-hook] Connection error: ${error2.message}`);
200
+ }
201
+ }
202
+ return null;
203
+ }
204
+ }
205
+ async function getClientStatus() {
206
+ const port = getPort();
207
+ const url = getApiBaseUrl();
208
+ const running = await checkClientRunning();
209
+ return {
210
+ running,
211
+ port,
212
+ url
213
+ };
214
+ }
215
+
216
+ // src/commands/install.ts
217
+ async function installCommand() {
218
+ console.log("\u{1F527} Installing Mantra Privacy Hook for Claude Code...\n");
219
+ if (isHookRegistered()) {
220
+ console.log("\u2705 Hook is already installed.\n");
221
+ await showStatus();
222
+ return;
223
+ }
224
+ if (!claudeSettingsExists()) {
225
+ console.log(`\u{1F4C1} Creating Claude Code settings at: ${getClaudeSettingsPath()}
226
+ `);
227
+ }
228
+ const success = registerHook();
229
+ if (success) {
230
+ console.log("\u2705 Hook installed successfully!\n");
231
+ console.log(" The hook will check your prompts for sensitive information");
232
+ console.log(" before sending them to Claude Code.\n");
233
+ await showStatus();
234
+ } else {
235
+ console.error("\u274C Failed to install hook.\n");
236
+ console.error(" Please check that you have write permission to:");
237
+ console.error(` ${getClaudeSettingsPath()}
238
+ `);
239
+ process.exit(1);
240
+ }
241
+ }
242
+ async function showStatus() {
243
+ const status = await getClientStatus();
244
+ console.log("\u{1F4CA} Status:");
245
+ console.log(` \u2022 Hook installed: \u2713`);
246
+ console.log(` \u2022 Mantra client: ${status.running ? "\u2713 Running" : "\u2717 Not running"}`);
247
+ console.log(` \u2022 API endpoint: ${status.url}`);
248
+ if (!status.running) {
249
+ console.log("\n\u{1F4A1} Note: Start Mantra client to enable privacy protection.");
250
+ }
251
+ console.log("");
252
+ }
253
+
254
+ // src/commands/uninstall.ts
255
+ async function uninstallCommand() {
256
+ console.log("\u{1F527} Uninstalling Mantra Privacy Hook from Claude Code...\n");
257
+ if (!isHookRegistered()) {
258
+ console.log("\u2139\uFE0F Hook is not installed.\n");
259
+ return;
260
+ }
261
+ const success = unregisterHook();
262
+ if (success) {
263
+ console.log("\u2705 Hook uninstalled successfully!\n");
264
+ console.log(" Privacy protection is now disabled for Claude Code.");
265
+ console.log(" Run 'mantra-privacy-hook install' to re-enable.\n");
266
+ } else {
267
+ console.error("\u274C Failed to uninstall hook.\n");
268
+ process.exit(1);
269
+ }
270
+ }
271
+
272
+ // src/commands/status.ts
273
+ async function statusCommand() {
274
+ console.log("\u{1F4CA} Mantra Privacy Hook Status\n");
275
+ const hookInstalled = isHookRegistered();
276
+ console.log(`Hook Installation:`);
277
+ console.log(` \u2022 Installed: ${hookInstalled ? "\u2713 Yes" : "\u2717 No"}`);
278
+ console.log(` \u2022 Claude settings: ${getClaudeSettingsPath()}`);
279
+ console.log(` \u2022 Settings exists: ${claudeSettingsExists() ? "\u2713 Yes" : "\u2717 No"}`);
280
+ console.log("");
281
+ const status = await getClientStatus();
282
+ console.log(`Mantra Client:`);
283
+ console.log(` \u2022 Running: ${status.running ? "\u2713 Yes" : "\u2717 No"}`);
284
+ console.log(` \u2022 API Port: ${status.port}`);
285
+ console.log(` \u2022 API URL: ${status.url}`);
286
+ console.log(` \u2022 Config dir: ${getMantraConfigDir()}`);
287
+ console.log("");
288
+ if (hookInstalled && status.running) {
289
+ console.log("\u{1F7E2} Privacy protection is ACTIVE\n");
290
+ } else if (hookInstalled && !status.running) {
291
+ console.log("\u{1F7E1} Hook installed but client not running");
292
+ console.log(" Start Mantra client to enable protection.\n");
293
+ } else if (!hookInstalled && status.running) {
294
+ console.log("\u{1F7E1} Client running but hook not installed");
295
+ console.log(" Run 'mantra-privacy-hook install' to enable.\n");
296
+ } else {
297
+ console.log("\u{1F534} Privacy protection is INACTIVE");
298
+ console.log(" Run 'mantra-privacy-hook install' and start Mantra client.\n");
299
+ }
300
+ }
301
+
302
+ // src/hook-handler.ts
303
+ async function readStdin() {
304
+ return new Promise((resolve, reject) => {
305
+ let data = "";
306
+ process.stdin.setEncoding("utf-8");
307
+ process.stdin.on("data", (chunk) => {
308
+ data += chunk;
309
+ });
310
+ process.stdin.on("end", () => {
311
+ resolve(data);
312
+ });
313
+ process.stdin.on("error", (err) => {
314
+ reject(err);
315
+ });
316
+ });
317
+ }
318
+ function warn(message) {
319
+ console.error(`[Mantra Privacy Hook] ${message}`);
320
+ }
321
+ function error(message) {
322
+ console.error(`[Mantra Privacy Hook] \u26A0\uFE0F ${message}`);
323
+ }
324
+ async function handleHook() {
325
+ try {
326
+ const input = await readStdin();
327
+ if (!input.trim()) {
328
+ process.exit(0);
329
+ }
330
+ let hookInput;
331
+ try {
332
+ hookInput = JSON.parse(input);
333
+ } catch {
334
+ error("Invalid JSON input");
335
+ process.exit(0);
336
+ }
337
+ const promptContent = hookInput.prompt?.content;
338
+ if (!promptContent) {
339
+ process.exit(0);
340
+ }
341
+ const clientRunning = await checkClientRunning();
342
+ if (!clientRunning) {
343
+ warn("Mantra \u5BA2\u6237\u7AEF\u672A\u8FD0\u884C\uFF0C\u9690\u79C1\u4FDD\u62A4\u672A\u542F\u7528");
344
+ process.exit(0);
345
+ }
346
+ const result = await checkPrivacy(promptContent);
347
+ if (!result) {
348
+ warn("\u65E0\u6CD5\u8FDE\u63A5\u5230 Mantra \u5BA2\u6237\u7AEF");
349
+ process.exit(0);
350
+ }
351
+ if (result.action === "block") {
352
+ error("\u68C0\u6D4B\u5230\u654F\u611F\u4FE1\u606F\uFF01");
353
+ if (result.message) {
354
+ console.error(` ${result.message}`);
355
+ }
356
+ if (result.matches && result.matches.length > 0) {
357
+ console.error(" \u8BE6\u60C5:");
358
+ for (const match of result.matches) {
359
+ console.error(` - [${match.severity}] ${match.rule_id}: ${match.preview}`);
360
+ }
361
+ }
362
+ console.error("");
363
+ console.error(" \u8BF7\u5728 Mantra \u5BA2\u6237\u7AEF\u4E2D\u5904\u7406\u654F\u611F\u4FE1\u606F\u540E\u91CD\u8BD5\u3002");
364
+ console.error("");
365
+ process.exit(2);
366
+ }
367
+ process.exit(0);
368
+ } catch (err) {
369
+ error(`\u5904\u7406\u9519\u8BEF: ${err instanceof Error ? err.message : String(err)}`);
370
+ process.exit(0);
371
+ }
372
+ }
373
+
374
+ // src/commands/check.ts
375
+ async function checkCommand() {
376
+ await handleHook();
377
+ }
378
+
379
+ // src/cli.ts
380
+ var program = new Command();
381
+ program.name("mantra-privacy-hook").description("Privacy protection hook for AI coding tools").version("0.1.0");
382
+ program.command("install").description("Install the privacy hook to Claude Code").action(async () => {
383
+ await installCommand();
384
+ });
385
+ program.command("uninstall").description("Remove the privacy hook from Claude Code").action(async () => {
386
+ await uninstallCommand();
387
+ });
388
+ program.command("status").description("Show hook installation and client connection status").action(async () => {
389
+ await statusCommand();
390
+ });
391
+ program.command("check").description("Check prompt for sensitive information (called by Claude Code)").action(async () => {
392
+ await checkCommand();
393
+ });
394
+ program.parse();
395
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Types for Mantra Privacy Hook
3
+ * Story 3.11
4
+ */
5
+ /**
6
+ * Claude Code Hook 传入的数据
7
+ */
8
+ interface ClaudeHookInput {
9
+ /** 会话 ID */
10
+ session_id?: string;
11
+ /** Prompt 内容 */
12
+ prompt: {
13
+ content: string;
14
+ };
15
+ /** 其他元数据 */
16
+ [key: string]: unknown;
17
+ }
18
+ /**
19
+ * 隐私检查请求
20
+ */
21
+ interface PrivacyCheckRequest {
22
+ /** 待检查的 prompt 内容 */
23
+ prompt: string;
24
+ /** 上下文信息 */
25
+ context?: PrivacyCheckContext;
26
+ }
27
+ /**
28
+ * 上下文信息
29
+ */
30
+ interface PrivacyCheckContext {
31
+ /** 工具名称 */
32
+ tool: string;
33
+ /** 时间戳 */
34
+ timestamp?: string;
35
+ }
36
+ /**
37
+ * 隐私检查响应
38
+ */
39
+ interface PrivacyCheckResponse {
40
+ /** 动作:allow 或 block */
41
+ action: "allow" | "block";
42
+ /** 匹配信息(仅当 action=block 时) */
43
+ matches?: MatchInfo[];
44
+ /** 提示消息(仅当 action=block 时) */
45
+ message?: string;
46
+ }
47
+ /**
48
+ * 匹配信息
49
+ */
50
+ interface MatchInfo {
51
+ /** 规则 ID */
52
+ rule_id: string;
53
+ /** 严重程度 */
54
+ severity: string;
55
+ /** 预览(脱敏后的内容) */
56
+ preview: string;
57
+ }
58
+ /**
59
+ * Hook 安装状态
60
+ */
61
+ interface HookStatus {
62
+ /** 是否已安装 */
63
+ installed: boolean;
64
+ /** Mantra 客户端是否运行中 */
65
+ clientRunning: boolean;
66
+ /** 当前配置的端口 */
67
+ port: number;
68
+ }
69
+ /**
70
+ * Claude Code settings.json 中的 hook 配置
71
+ */
72
+ interface ClaudeHookConfig {
73
+ command: string;
74
+ description?: string;
75
+ }
76
+ /**
77
+ * Claude Code settings.json 结构
78
+ */
79
+ interface ClaudeSettings {
80
+ hooks?: {
81
+ UserPromptSubmit?: ClaudeHookConfig[];
82
+ [key: string]: ClaudeHookConfig[] | undefined;
83
+ };
84
+ [key: string]: unknown;
85
+ }
86
+ /**
87
+ * Mantra 配置文件结构
88
+ */
89
+ interface MantraConfig {
90
+ /** 本地 API 端口 */
91
+ local_api_port?: number;
92
+ }
93
+
94
+ /**
95
+ * Configuration utilities
96
+ * Story 3.11: Task 8, 9 - AC #7
97
+ *
98
+ * 读取 Mantra 配置文件获取端口等设置
99
+ */
100
+
101
+ /** 默认端口 */
102
+ declare const DEFAULT_PORT = 19836;
103
+ /**
104
+ * 获取 Mantra 配置目录路径(跨平台)
105
+ *
106
+ * - macOS: ~/Library/Application Support/com.mantra.app/
107
+ * - Linux: ~/.local/share/com.mantra.app/
108
+ * - Windows: %APPDATA%\com.mantra.app\
109
+ */
110
+ declare function getMantraConfigDir(): string;
111
+ /**
112
+ * 读取 Mantra 配置
113
+ *
114
+ * @returns 配置对象,如果文件不存在则返回默认配置
115
+ */
116
+ declare function loadMantraConfig(): MantraConfig;
117
+ /**
118
+ * 获取当前配置的端口
119
+ */
120
+ declare function getPort(): number;
121
+ /**
122
+ * 获取 Mantra API 基础 URL
123
+ */
124
+ declare function getApiBaseUrl(): string;
125
+
126
+ /**
127
+ * Client API - Mantra 客户端通信模块
128
+ * Story 3.11: Task 9 - AC #6, #7
129
+ *
130
+ * 提供与 Mantra 客户端 HTTP API 的通信功能
131
+ */
132
+
133
+ /**
134
+ * 检查 Mantra 客户端是否在运行
135
+ *
136
+ * @returns true 如果客户端正在运行
137
+ */
138
+ declare function checkClientRunning(): Promise<boolean>;
139
+ /**
140
+ * 发送隐私检查请求到 Mantra 客户端
141
+ *
142
+ * @param prompt - 待检查的内容
143
+ * @returns 检查响应或 null(如果连接失败)
144
+ */
145
+ declare function checkPrivacy(prompt: string): Promise<PrivacyCheckResponse | null>;
146
+ /**
147
+ * 获取客户端状态信息
148
+ *
149
+ * @returns 状态信息对象
150
+ */
151
+ declare function getClientStatus(): Promise<{
152
+ running: boolean;
153
+ port: number;
154
+ url: string;
155
+ }>;
156
+
157
+ /**
158
+ * Claude Code Settings 管理模块
159
+ * Story 3.11: Task 7 - AC #2, #5
160
+ *
161
+ * 管理 Claude Code 的 settings.json 中的 hook 配置
162
+ */
163
+
164
+ /**
165
+ * 获取 Claude Code settings.json 路径(跨平台)
166
+ *
167
+ * - macOS: ~/.claude/settings.json
168
+ * - Linux: ~/.claude/settings.json
169
+ * - Windows: %USERPROFILE%\.claude\settings.json
170
+ */
171
+ declare function getClaudeSettingsPath(): string;
172
+ /**
173
+ * 读取 Claude Code settings
174
+ *
175
+ * @returns settings 对象,如果文件不存在则返回空对象
176
+ */
177
+ declare function loadClaudeSettings(): ClaudeSettings;
178
+ /**
179
+ * 保存 Claude Code settings
180
+ *
181
+ * @param settings - settings 对象
182
+ */
183
+ declare function saveClaudeSettings(settings: ClaudeSettings): void;
184
+ /**
185
+ * 注册 Hook 到 Claude Code
186
+ *
187
+ * @returns true 如果成功注册
188
+ */
189
+ declare function registerHook(): boolean;
190
+ /**
191
+ * 从 Claude Code 移除 Hook
192
+ *
193
+ * @returns true 如果成功移除
194
+ */
195
+ declare function unregisterHook(): boolean;
196
+ /**
197
+ * 检查 Hook 是否已注册
198
+ *
199
+ * @returns true 如果已注册
200
+ */
201
+ declare function isHookRegistered(): boolean;
202
+ /**
203
+ * 检查 Claude Code settings 文件是否存在
204
+ */
205
+ declare function claudeSettingsExists(): boolean;
206
+
207
+ /**
208
+ * Hook 处理器
209
+ * Story 3.11: Task 8 - AC #3
210
+ *
211
+ * 处理 Claude Code Hook 调用:
212
+ * - 从 stdin 读取数据
213
+ * - 调用 Mantra 客户端检查
214
+ * - 返回适当的 exit code
215
+ */
216
+ /**
217
+ * 处理 Hook 调用
218
+ *
219
+ * Exit codes:
220
+ * - 0: 允许继续(无敏感信息或客户端未运行)
221
+ * - 2: 阻止提交(检测到敏感信息)
222
+ */
223
+ declare function handleHook(): Promise<void>;
224
+
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 };
package/dist/index.js ADDED
@@ -0,0 +1,300 @@
1
+ // src/config.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as os from "os";
5
+ var DEFAULT_PORT = 19836;
6
+ var CONFIG_FILENAME = "settings.yaml";
7
+ function getMantraConfigDir() {
8
+ const platform2 = os.platform();
9
+ const homeDir = os.homedir();
10
+ switch (platform2) {
11
+ case "darwin":
12
+ return path.join(homeDir, "Library", "Application Support", "com.mantra.app");
13
+ case "win32":
14
+ return path.join(process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"), "com.mantra.app");
15
+ default:
16
+ return path.join(homeDir, ".local", "share", "com.mantra.app");
17
+ }
18
+ }
19
+ function loadMantraConfig() {
20
+ const configDir = getMantraConfigDir();
21
+ const configPath = path.join(configDir, CONFIG_FILENAME);
22
+ try {
23
+ if (!fs.existsSync(configPath)) {
24
+ return { local_api_port: DEFAULT_PORT };
25
+ }
26
+ const content = fs.readFileSync(configPath, "utf-8");
27
+ const portMatch = content.match(/local_api_port:\s*(\d+)/);
28
+ if (portMatch) {
29
+ return { local_api_port: parseInt(portMatch[1], 10) };
30
+ }
31
+ return { local_api_port: DEFAULT_PORT };
32
+ } catch {
33
+ return { local_api_port: DEFAULT_PORT };
34
+ }
35
+ }
36
+ function getPort() {
37
+ const config = loadMantraConfig();
38
+ return config.local_api_port ?? DEFAULT_PORT;
39
+ }
40
+ function getApiBaseUrl() {
41
+ const port = getPort();
42
+ return `http://127.0.0.1:${port}`;
43
+ }
44
+
45
+ // src/client-api.ts
46
+ var REQUEST_TIMEOUT = 3e3;
47
+ async function checkClientRunning() {
48
+ const baseUrl = getApiBaseUrl();
49
+ try {
50
+ const controller = new AbortController();
51
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
52
+ const response = await fetch(`${baseUrl}/api/health`, {
53
+ method: "GET",
54
+ signal: controller.signal
55
+ });
56
+ clearTimeout(timeoutId);
57
+ return response.ok;
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+ async function checkPrivacy(prompt) {
63
+ const baseUrl = getApiBaseUrl();
64
+ const request = {
65
+ prompt,
66
+ context: {
67
+ tool: "claude-code",
68
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
69
+ }
70
+ };
71
+ try {
72
+ const controller = new AbortController();
73
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
74
+ const response = await fetch(`${baseUrl}/api/privacy/check`, {
75
+ method: "POST",
76
+ headers: {
77
+ "Content-Type": "application/json"
78
+ },
79
+ body: JSON.stringify(request),
80
+ signal: controller.signal
81
+ });
82
+ clearTimeout(timeoutId);
83
+ if (!response.ok) {
84
+ console.error(
85
+ `[mantra-privacy-hook] API error: ${response.status} ${response.statusText}`
86
+ );
87
+ return null;
88
+ }
89
+ return await response.json();
90
+ } catch (error2) {
91
+ if (error2 instanceof Error) {
92
+ if (error2.name === "AbortError") {
93
+ console.error("[mantra-privacy-hook] Request timeout");
94
+ } else {
95
+ console.error(`[mantra-privacy-hook] Connection error: ${error2.message}`);
96
+ }
97
+ }
98
+ return null;
99
+ }
100
+ }
101
+ async function getClientStatus() {
102
+ const port = getPort();
103
+ const url = getApiBaseUrl();
104
+ const running = await checkClientRunning();
105
+ return {
106
+ running,
107
+ port,
108
+ url
109
+ };
110
+ }
111
+
112
+ // src/claude-settings.ts
113
+ import * as fs2 from "fs";
114
+ import * as path2 from "path";
115
+ import * as os2 from "os";
116
+ var HOOK_CONFIG = {
117
+ command: "mantra-privacy-hook check",
118
+ description: "Mantra Privacy Check"
119
+ };
120
+ function getClaudeSettingsPath() {
121
+ const homeDir = os2.homedir();
122
+ return path2.join(homeDir, ".claude", "settings.json");
123
+ }
124
+ function loadClaudeSettings() {
125
+ const settingsPath = getClaudeSettingsPath();
126
+ try {
127
+ if (!fs2.existsSync(settingsPath)) {
128
+ return {};
129
+ }
130
+ const content = fs2.readFileSync(settingsPath, "utf-8");
131
+ return JSON.parse(content);
132
+ } catch (error2) {
133
+ console.error(`[mantra-privacy-hook] Failed to read Claude settings: ${error2}`);
134
+ return {};
135
+ }
136
+ }
137
+ function saveClaudeSettings(settings) {
138
+ const settingsPath = getClaudeSettingsPath();
139
+ const settingsDir = path2.dirname(settingsPath);
140
+ if (!fs2.existsSync(settingsDir)) {
141
+ fs2.mkdirSync(settingsDir, { recursive: true });
142
+ }
143
+ fs2.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
144
+ }
145
+ function registerHook() {
146
+ try {
147
+ const settings = loadClaudeSettings();
148
+ if (!settings.hooks) {
149
+ settings.hooks = {};
150
+ }
151
+ if (!settings.hooks.UserPromptSubmit) {
152
+ settings.hooks.UserPromptSubmit = [];
153
+ }
154
+ const alreadyRegistered = settings.hooks.UserPromptSubmit.some(
155
+ (hook) => hook.command === HOOK_CONFIG.command
156
+ );
157
+ if (alreadyRegistered) {
158
+ return true;
159
+ }
160
+ settings.hooks.UserPromptSubmit.push(HOOK_CONFIG);
161
+ saveClaudeSettings(settings);
162
+ return true;
163
+ } catch (error2) {
164
+ console.error(`[mantra-privacy-hook] Failed to register hook: ${error2}`);
165
+ return false;
166
+ }
167
+ }
168
+ function unregisterHook() {
169
+ try {
170
+ const settings = loadClaudeSettings();
171
+ if (!settings.hooks?.UserPromptSubmit) {
172
+ return true;
173
+ }
174
+ const originalLength = settings.hooks.UserPromptSubmit.length;
175
+ settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
176
+ (hook) => hook.command !== HOOK_CONFIG.command
177
+ );
178
+ if (settings.hooks.UserPromptSubmit.length === originalLength) {
179
+ return true;
180
+ }
181
+ if (settings.hooks.UserPromptSubmit.length === 0) {
182
+ delete settings.hooks.UserPromptSubmit;
183
+ }
184
+ if (Object.keys(settings.hooks).length === 0) {
185
+ delete settings.hooks;
186
+ }
187
+ saveClaudeSettings(settings);
188
+ return true;
189
+ } catch (error2) {
190
+ console.error(`[mantra-privacy-hook] Failed to unregister hook: ${error2}`);
191
+ return false;
192
+ }
193
+ }
194
+ function isHookRegistered() {
195
+ try {
196
+ const settings = loadClaudeSettings();
197
+ if (!settings.hooks?.UserPromptSubmit) {
198
+ return false;
199
+ }
200
+ return settings.hooks.UserPromptSubmit.some(
201
+ (hook) => hook.command === HOOK_CONFIG.command
202
+ );
203
+ } catch {
204
+ return false;
205
+ }
206
+ }
207
+ function claudeSettingsExists() {
208
+ return fs2.existsSync(getClaudeSettingsPath());
209
+ }
210
+
211
+ // src/hook-handler.ts
212
+ async function readStdin() {
213
+ return new Promise((resolve, reject) => {
214
+ let data = "";
215
+ process.stdin.setEncoding("utf-8");
216
+ process.stdin.on("data", (chunk) => {
217
+ data += chunk;
218
+ });
219
+ process.stdin.on("end", () => {
220
+ resolve(data);
221
+ });
222
+ process.stdin.on("error", (err) => {
223
+ reject(err);
224
+ });
225
+ });
226
+ }
227
+ function warn(message) {
228
+ console.error(`[Mantra Privacy Hook] ${message}`);
229
+ }
230
+ function error(message) {
231
+ console.error(`[Mantra Privacy Hook] \u26A0\uFE0F ${message}`);
232
+ }
233
+ async function handleHook() {
234
+ try {
235
+ const input = await readStdin();
236
+ if (!input.trim()) {
237
+ process.exit(0);
238
+ }
239
+ let hookInput;
240
+ try {
241
+ hookInput = JSON.parse(input);
242
+ } catch {
243
+ error("Invalid JSON input");
244
+ process.exit(0);
245
+ }
246
+ const promptContent = hookInput.prompt?.content;
247
+ if (!promptContent) {
248
+ process.exit(0);
249
+ }
250
+ const clientRunning = await checkClientRunning();
251
+ if (!clientRunning) {
252
+ warn("Mantra \u5BA2\u6237\u7AEF\u672A\u8FD0\u884C\uFF0C\u9690\u79C1\u4FDD\u62A4\u672A\u542F\u7528");
253
+ process.exit(0);
254
+ }
255
+ const result = await checkPrivacy(promptContent);
256
+ if (!result) {
257
+ warn("\u65E0\u6CD5\u8FDE\u63A5\u5230 Mantra \u5BA2\u6237\u7AEF");
258
+ process.exit(0);
259
+ }
260
+ if (result.action === "block") {
261
+ error("\u68C0\u6D4B\u5230\u654F\u611F\u4FE1\u606F\uFF01");
262
+ if (result.message) {
263
+ console.error(` ${result.message}`);
264
+ }
265
+ if (result.matches && result.matches.length > 0) {
266
+ console.error(" \u8BE6\u60C5:");
267
+ for (const match of result.matches) {
268
+ console.error(` - [${match.severity}] ${match.rule_id}: ${match.preview}`);
269
+ }
270
+ }
271
+ console.error("");
272
+ console.error(" \u8BF7\u5728 Mantra \u5BA2\u6237\u7AEF\u4E2D\u5904\u7406\u654F\u611F\u4FE1\u606F\u540E\u91CD\u8BD5\u3002");
273
+ console.error("");
274
+ process.exit(2);
275
+ }
276
+ process.exit(0);
277
+ } catch (err) {
278
+ error(`\u5904\u7406\u9519\u8BEF: ${err instanceof Error ? err.message : String(err)}`);
279
+ process.exit(0);
280
+ }
281
+ }
282
+ export {
283
+ DEFAULT_PORT,
284
+ checkClientRunning,
285
+ checkPrivacy,
286
+ claudeSettingsExists,
287
+ getApiBaseUrl,
288
+ getClaudeSettingsPath,
289
+ getClientStatus,
290
+ getMantraConfigDir,
291
+ getPort,
292
+ handleHook,
293
+ isHookRegistered,
294
+ loadClaudeSettings,
295
+ loadMantraConfig,
296
+ registerHook,
297
+ saveClaudeSettings,
298
+ unregisterHook
299
+ };
300
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@mantra-hq/privacy-hook",
3
+ "version": "0.1.0",
4
+ "description": "Privacy protection hook for AI coding tools - Claude Code, Cursor, etc.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "mantra-privacy-hook": "./dist/cli.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "CHANGELOG.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
28
+ "typecheck": "tsc --noEmit",
29
+ "lint": "eslint src --ext .ts",
30
+ "clean": "rimraf dist",
31
+ "prepublishOnly": "pnpm run build && pnpm run test"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "registry": "https://registry.npmjs.org/"
36
+ },
37
+ "keywords": [
38
+ "mantra",
39
+ "privacy",
40
+ "hook",
41
+ "claude-code",
42
+ "cursor",
43
+ "ai",
44
+ "security"
45
+ ],
46
+ "author": "Mantra Team",
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/mantra-hq/privacy-hook",
51
+ "directory": ""
52
+ },
53
+ "homepage": "https://mantra.gonewx.com",
54
+ "dependencies": {
55
+ "commander": "^12.0.0"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^20.0.0",
59
+ "rimraf": "^5.0.0",
60
+ "tsup": "^8.0.0",
61
+ "typescript": "^5.0.0",
62
+ "vitest": "^2.0.0"
63
+ },
64
+ "engines": {
65
+ "node": ">=18.0.0"
66
+ }
67
+ }