agentmask 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/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-F7TMT2OH.js +96 -0
- package/dist/chunk-F7TMT2OH.js.map +1 -0
- package/dist/chunk-P7BRPZBB.js +211 -0
- package/dist/chunk-P7BRPZBB.js.map +1 -0
- package/dist/chunk-Q7ZBIDBL.js +58 -0
- package/dist/chunk-Q7ZBIDBL.js.map +1 -0
- package/dist/chunk-YASOHGJL.js +44 -0
- package/dist/chunk-YASOHGJL.js.map +1 -0
- package/dist/cli.js +433 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +410 -0
- package/dist/index.js.map +1 -0
- package/dist/post-scan-PZBRRZS6.js +50 -0
- package/dist/post-scan-PZBRRZS6.js.map +1 -0
- package/dist/pre-bash-CQ6UYBND.js +75 -0
- package/dist/pre-bash-CQ6UYBND.js.map +1 -0
- package/dist/pre-read-4YE6QMWV.js +49 -0
- package/dist/pre-read-4YE6QMWV.js.map +1 -0
- package/dist/pre-write-EBMADS22.js +53 -0
- package/dist/pre-write-EBMADS22.js.map +1 -0
- package/dist/server-3SUDWIDY.js +13944 -0
- package/dist/server-3SUDWIDY.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/common.ts"],"sourcesContent":["/**\n * Common utilities for Claude Code hook handlers.\n *\n * Hook contract:\n * - Receives JSON on stdin\n * - Exit 0 = allow (stdout parsed as JSON for hookSpecificOutput)\n * - Exit 2 = block (stderr shown to Claude as error message)\n * - Exit 1 (or any other) = non-blocking warning (graceful degradation)\n */\n\nexport interface HookInput {\n session_id?: string;\n cwd?: string;\n tool_name?: string;\n tool_input?: Record<string, unknown>;\n tool_response?: string;\n hook_event_name?: string;\n}\n\nexport async function readStdin(): Promise<HookInput> {\n return new Promise((resolve, reject) => {\n let data = \"\";\n process.stdin.setEncoding(\"utf-8\");\n process.stdin.on(\"data\", (chunk) => (data += chunk));\n process.stdin.on(\"end\", () => {\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve({});\n }\n });\n process.stdin.on(\"error\", reject);\n });\n}\n\n/** Block the tool call. Stderr message is shown to Claude. */\nexport function block(message: string): never {\n process.stderr.write(message);\n process.exit(2);\n}\n\n/** Allow the tool call, optionally with additional context. */\nexport function allow(additionalContext?: string): void {\n if (additionalContext) {\n const output = {\n hookSpecificOutput: { additionalContext },\n };\n process.stdout.write(JSON.stringify(output));\n }\n process.exit(0);\n}\n\n/** Safety timeout — never let a hook run longer than 4s (hook timeout is 5s). */\nexport function startSafetyTimer(ms = 4000): void {\n setTimeout(() => {\n process.exit(1); // Non-blocking exit — degrade gracefully\n }, ms).unref();\n}\n"],"mappings":";;;AAmBA,eAAsB,YAAgC;AACpD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,OAAO;AACX,YAAQ,MAAM,YAAY,OAAO;AACjC,YAAQ,MAAM,GAAG,QAAQ,CAAC,UAAW,QAAQ,KAAM;AACnD,YAAQ,MAAM,GAAG,OAAO,MAAM;AAC5B,UAAI;AACF,gBAAQ,KAAK,MAAM,IAAI,CAAC;AAAA,MAC1B,QAAQ;AACN,gBAAQ,CAAC,CAAC;AAAA,MACZ;AAAA,IACF,CAAC;AACD,YAAQ,MAAM,GAAG,SAAS,MAAM;AAAA,EAClC,CAAC;AACH;AAGO,SAAS,MAAM,SAAwB;AAC5C,UAAQ,OAAO,MAAM,OAAO;AAC5B,UAAQ,KAAK,CAAC;AAChB;AAGO,SAAS,MAAM,mBAAkC;AACtD,MAAI,mBAAmB;AACrB,UAAM,SAAS;AAAA,MACb,oBAAoB,EAAE,kBAAkB;AAAA,IAC1C;AACA,YAAQ,OAAO,MAAM,KAAK,UAAU,MAAM,CAAC;AAAA,EAC7C;AACA,UAAQ,KAAK,CAAC;AAChB;AAGO,SAAS,iBAAiB,KAAK,KAAY;AAChD,aAAW,MAAM;AACf,YAAQ,KAAK,CAAC;AAAA,EAChB,GAAG,EAAE,EAAE,MAAM;AACf;","names":[]}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
removeFromBlocklist,
|
|
4
|
+
saveBlocklist
|
|
5
|
+
} from "./chunk-F7TMT2OH.js";
|
|
6
|
+
import {
|
|
7
|
+
getGitleaksBinary,
|
|
8
|
+
scanDir,
|
|
9
|
+
scanStaged
|
|
10
|
+
} from "./chunk-P7BRPZBB.js";
|
|
11
|
+
import "./chunk-2H7UOFLK.js";
|
|
12
|
+
|
|
13
|
+
// src/cli.ts
|
|
14
|
+
import { Command } from "commander";
|
|
15
|
+
|
|
16
|
+
// src/cli/scan.ts
|
|
17
|
+
import { resolve, relative } from "path";
|
|
18
|
+
async function runScan(target, options) {
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
let findings;
|
|
21
|
+
if (options.staged) {
|
|
22
|
+
findings = await scanStaged(cwd);
|
|
23
|
+
} else {
|
|
24
|
+
const targetPath = resolve(cwd, target ?? ".");
|
|
25
|
+
findings = await scanDir(targetPath);
|
|
26
|
+
}
|
|
27
|
+
if (options.json) {
|
|
28
|
+
printJSON(findings, cwd);
|
|
29
|
+
} else {
|
|
30
|
+
printHuman(findings, cwd);
|
|
31
|
+
}
|
|
32
|
+
process.exitCode = findings.length > 0 ? 1 : 0;
|
|
33
|
+
}
|
|
34
|
+
function printJSON(findings, cwd) {
|
|
35
|
+
const output = {
|
|
36
|
+
findings: findings.map((f) => ({
|
|
37
|
+
ruleId: f.RuleID,
|
|
38
|
+
description: f.Description,
|
|
39
|
+
file: relative(cwd, f.File),
|
|
40
|
+
line: f.StartLine,
|
|
41
|
+
entropy: f.Entropy
|
|
42
|
+
})),
|
|
43
|
+
summary: {
|
|
44
|
+
secretsFound: findings.length,
|
|
45
|
+
filesAffected: new Set(findings.map((f) => f.File)).size
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
console.log(JSON.stringify(output, null, 2));
|
|
49
|
+
}
|
|
50
|
+
function printHuman(findings, cwd) {
|
|
51
|
+
if (findings.length === 0) {
|
|
52
|
+
console.log("No secrets found.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const f of findings) {
|
|
56
|
+
const relPath = relative(cwd, f.File);
|
|
57
|
+
console.log(` [${f.RuleID}] ${relPath}:${f.StartLine} \u2014 ${f.Description}`);
|
|
58
|
+
}
|
|
59
|
+
const fileCount = new Set(findings.map((f) => f.File)).size;
|
|
60
|
+
console.log("");
|
|
61
|
+
console.log(`Found ${findings.length} secret(s) across ${fileCount} file(s).`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/cli/init.ts
|
|
65
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
66
|
+
import { join, resolve as resolve2, relative as relative2 } from "path";
|
|
67
|
+
import { execSync } from "child_process";
|
|
68
|
+
async function runInit(options) {
|
|
69
|
+
const cwd = process.cwd();
|
|
70
|
+
const gitleaksBin = await getGitleaksBinary();
|
|
71
|
+
console.log("Scanning repository for secrets...");
|
|
72
|
+
const findings = await scanDir(cwd);
|
|
73
|
+
const blocklist = buildBlocklist(findings, cwd);
|
|
74
|
+
const blocklistEntryCount = Object.keys(blocklist.files).length;
|
|
75
|
+
const staticBlocked = findStaticBlockedFiles(cwd);
|
|
76
|
+
if (findings.length > 0) {
|
|
77
|
+
console.log("");
|
|
78
|
+
console.log(` Found secrets in ${blocklistEntryCount} file(s):`);
|
|
79
|
+
for (const f of findings.slice(0, 20)) {
|
|
80
|
+
const relPath = relative2(cwd, f.File);
|
|
81
|
+
const tag = f.Description.toLowerCase().includes("critical") ? "[CRIT]" : "[HIGH]";
|
|
82
|
+
console.log(` ${tag} ${relPath}:${f.StartLine} \u2014 ${f.Description}`);
|
|
83
|
+
}
|
|
84
|
+
if (findings.length > 20) {
|
|
85
|
+
console.log(` ... and ${findings.length - 20} more`);
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
console.log(" No secrets found in source files.");
|
|
89
|
+
}
|
|
90
|
+
if (staticBlocked.length > 0) {
|
|
91
|
+
console.log("");
|
|
92
|
+
console.log(" Protected by default (static patterns):");
|
|
93
|
+
for (const f of staticBlocked.slice(0, 10)) {
|
|
94
|
+
console.log(` [blocked] ${f}`);
|
|
95
|
+
}
|
|
96
|
+
if (staticBlocked.length > 10) {
|
|
97
|
+
console.log(` ... and ${staticBlocked.length - 10} more`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const settingsDir = join(cwd, ".claude");
|
|
101
|
+
mkdirSync(settingsDir, { recursive: true });
|
|
102
|
+
saveBlocklist(cwd, blocklist);
|
|
103
|
+
const binPath = getAgentmaskBinPath();
|
|
104
|
+
const settingsFile = options.team ? join(settingsDir, "settings.json") : join(settingsDir, "settings.local.json");
|
|
105
|
+
const settings = loadJSON(settingsFile);
|
|
106
|
+
settings.hooks = mergeHooks(settings.hooks ?? {}, binPath);
|
|
107
|
+
writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
|
|
108
|
+
const rulesDir = join(settingsDir, "rules");
|
|
109
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
110
|
+
writeFileSync(join(rulesDir, "agentmask.md"), RULES_CONTENT);
|
|
111
|
+
const mcpFile = join(cwd, ".mcp.json");
|
|
112
|
+
const mcpConfig = loadJSON(mcpFile);
|
|
113
|
+
mcpConfig.mcpServers = mcpConfig.mcpServers ?? {};
|
|
114
|
+
mcpConfig.mcpServers.agentmask = {
|
|
115
|
+
command: binPath,
|
|
116
|
+
args: ["serve"]
|
|
117
|
+
};
|
|
118
|
+
writeFileSync(mcpFile, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
119
|
+
const totalProtected = blocklistEntryCount + staticBlocked.length;
|
|
120
|
+
console.log("");
|
|
121
|
+
console.log(` Hooks installed \u2192 ${options.team ? ".claude/settings.json" : ".claude/settings.local.json"}`);
|
|
122
|
+
console.log(" Rules installed \u2192 .claude/rules/agentmask.md");
|
|
123
|
+
console.log(" MCP server registered \u2192 .mcp.json");
|
|
124
|
+
if (blocklistEntryCount > 0) {
|
|
125
|
+
console.log(` Blocklist saved \u2192 .claude/agentmask-blocklist.json (${blocklistEntryCount} file(s))`);
|
|
126
|
+
}
|
|
127
|
+
console.log("");
|
|
128
|
+
console.log(`agentmask is active. ${totalProtected} file(s) protected.`);
|
|
129
|
+
console.log("Re-run `agentmask init` anytime to rescan.");
|
|
130
|
+
}
|
|
131
|
+
function buildBlocklist(findings, cwd) {
|
|
132
|
+
const blocklist = { files: {} };
|
|
133
|
+
for (const f of findings) {
|
|
134
|
+
const relPath = relative2(cwd, f.File).replace(/\\/g, "/");
|
|
135
|
+
if (!blocklist.files[relPath]) {
|
|
136
|
+
blocklist.files[relPath] = {
|
|
137
|
+
secrets: [],
|
|
138
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const entry = blocklist.files[relPath];
|
|
142
|
+
if (!entry.secrets.includes(f.Description)) {
|
|
143
|
+
entry.secrets.push(f.Description);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return blocklist;
|
|
147
|
+
}
|
|
148
|
+
function findStaticBlockedFiles(cwd) {
|
|
149
|
+
const results = [];
|
|
150
|
+
const commonFiles = [
|
|
151
|
+
".env",
|
|
152
|
+
".env.local",
|
|
153
|
+
".env.development",
|
|
154
|
+
".env.production",
|
|
155
|
+
".env.staging",
|
|
156
|
+
".env.test",
|
|
157
|
+
"credentials.json",
|
|
158
|
+
"serviceAccountKey.json",
|
|
159
|
+
".npmrc",
|
|
160
|
+
".pypirc"
|
|
161
|
+
];
|
|
162
|
+
for (const file of commonFiles) {
|
|
163
|
+
if (existsSync(join(cwd, file))) {
|
|
164
|
+
results.push(file);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return results;
|
|
168
|
+
}
|
|
169
|
+
function getAgentmaskBinPath() {
|
|
170
|
+
try {
|
|
171
|
+
const resolved = execSync("which agentmask", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
172
|
+
if (resolved) return resolved;
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
const localBin = resolve2("node_modules", ".bin", "agentmask");
|
|
176
|
+
if (existsSync(localBin)) return localBin;
|
|
177
|
+
return "npx agentmask";
|
|
178
|
+
}
|
|
179
|
+
function mergeHooks(existing, binPath) {
|
|
180
|
+
const preToolUse = existing.PreToolUse ?? [];
|
|
181
|
+
const postToolUse = existing.PostToolUse ?? [];
|
|
182
|
+
const cleanPre = filterOutAgentmask(preToolUse);
|
|
183
|
+
const cleanPost = filterOutAgentmask(postToolUse);
|
|
184
|
+
return {
|
|
185
|
+
...existing,
|
|
186
|
+
PreToolUse: [
|
|
187
|
+
...cleanPre,
|
|
188
|
+
{ matcher: "Read", hooks: [{ type: "command", command: `${binPath} hook pre-read`, timeout: 5 }] },
|
|
189
|
+
{ matcher: "Bash", hooks: [{ type: "command", command: `${binPath} hook pre-bash`, timeout: 5 }] },
|
|
190
|
+
{ matcher: "Write|Edit", hooks: [{ type: "command", command: `${binPath} hook pre-write`, timeout: 5 }] }
|
|
191
|
+
],
|
|
192
|
+
PostToolUse: [
|
|
193
|
+
...cleanPost,
|
|
194
|
+
{ matcher: "Read|Bash", hooks: [{ type: "command", command: `${binPath} hook post-scan`, timeout: 5 }] }
|
|
195
|
+
]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function filterOutAgentmask(hooks) {
|
|
199
|
+
return hooks.filter((h) => {
|
|
200
|
+
const innerHooks = h?.hooks ?? [];
|
|
201
|
+
return !innerHooks.some(
|
|
202
|
+
(ih) => typeof ih?.command === "string" && ih.command.includes("agentmask")
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function loadJSON(filePath) {
|
|
207
|
+
try {
|
|
208
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
209
|
+
} catch {
|
|
210
|
+
return {};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
var RULES_CONTENT = `## agentmask \u2014 Secrets Protection Rules
|
|
214
|
+
|
|
215
|
+
These rules are enforced by agentmask hooks. Follow them to avoid
|
|
216
|
+
blocked operations and protect user secrets.
|
|
217
|
+
|
|
218
|
+
### Reading Sensitive Files
|
|
219
|
+
- When you need to read files that may contain secrets (.env,
|
|
220
|
+
credentials.json, *.pem, *.key, etc.), use the
|
|
221
|
+
\`mcp__agentmask__safe_read\` tool instead of the built-in Read tool.
|
|
222
|
+
- If a Read operation is blocked with a secrets warning, do NOT retry
|
|
223
|
+
the Read. Use \`mcp__agentmask__safe_read\` to get a redacted view.
|
|
224
|
+
- To see what environment variables are defined without their values,
|
|
225
|
+
use \`mcp__agentmask__env_names\`.
|
|
226
|
+
|
|
227
|
+
### Writing Code
|
|
228
|
+
- Never hardcode secret values (API keys, tokens, passwords,
|
|
229
|
+
connection strings) in source code. Use environment variable
|
|
230
|
+
references: process.env.VAR_NAME, os.environ["VAR_NAME"], etc.
|
|
231
|
+
- If a Write/Edit is blocked for containing a secret, rewrite the
|
|
232
|
+
code to use environment variable references.
|
|
233
|
+
|
|
234
|
+
### Committing Code
|
|
235
|
+
- Before any git commit, run \`mcp__agentmask__scan_staged\` to verify
|
|
236
|
+
no secrets are in staged files.
|
|
237
|
+
- If a git commit is blocked for containing secrets, fix the flagged
|
|
238
|
+
files first, then retry the commit.
|
|
239
|
+
|
|
240
|
+
### General
|
|
241
|
+
- Never output raw secret values in your responses. Reference secrets
|
|
242
|
+
by their variable name only (e.g., "your DATABASE_URL" not the
|
|
243
|
+
actual connection string).
|
|
244
|
+
- When you see a [REDACTED:...] placeholder, do not attempt to
|
|
245
|
+
discover the actual value. Work with the variable name.
|
|
246
|
+
`;
|
|
247
|
+
|
|
248
|
+
// src/cli/remove.ts
|
|
249
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
|
|
250
|
+
import { join as join2 } from "path";
|
|
251
|
+
async function runRemove() {
|
|
252
|
+
const cwd = process.cwd();
|
|
253
|
+
const settingsDir = join2(cwd, ".claude");
|
|
254
|
+
for (const filename of ["settings.local.json", "settings.json"]) {
|
|
255
|
+
const settingsFile = join2(settingsDir, filename);
|
|
256
|
+
if (existsSync2(settingsFile)) {
|
|
257
|
+
try {
|
|
258
|
+
const settings = JSON.parse(readFileSync2(settingsFile, "utf-8"));
|
|
259
|
+
if (settings.hooks) {
|
|
260
|
+
settings.hooks.PreToolUse = filterOutAgentmask2(
|
|
261
|
+
settings.hooks.PreToolUse ?? []
|
|
262
|
+
);
|
|
263
|
+
settings.hooks.PostToolUse = filterOutAgentmask2(
|
|
264
|
+
settings.hooks.PostToolUse ?? []
|
|
265
|
+
);
|
|
266
|
+
if (settings.hooks.PreToolUse.length === 0)
|
|
267
|
+
delete settings.hooks.PreToolUse;
|
|
268
|
+
if (settings.hooks.PostToolUse.length === 0)
|
|
269
|
+
delete settings.hooks.PostToolUse;
|
|
270
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
271
|
+
writeFileSync2(settingsFile, JSON.stringify(settings, null, 2) + "\n");
|
|
272
|
+
console.log(` Hooks removed from ${filename}`);
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const blocklistFile = join2(settingsDir, "agentmask-blocklist.json");
|
|
279
|
+
if (existsSync2(blocklistFile)) {
|
|
280
|
+
unlinkSync(blocklistFile);
|
|
281
|
+
console.log(" Blocklist removed: .claude/agentmask-blocklist.json");
|
|
282
|
+
}
|
|
283
|
+
const rulesFile = join2(settingsDir, "rules", "agentmask.md");
|
|
284
|
+
if (existsSync2(rulesFile)) {
|
|
285
|
+
unlinkSync(rulesFile);
|
|
286
|
+
console.log(" Rules removed: .claude/rules/agentmask.md");
|
|
287
|
+
}
|
|
288
|
+
const mcpFile = join2(cwd, ".mcp.json");
|
|
289
|
+
if (existsSync2(mcpFile)) {
|
|
290
|
+
try {
|
|
291
|
+
const mcpConfig = JSON.parse(readFileSync2(mcpFile, "utf-8"));
|
|
292
|
+
if (mcpConfig.mcpServers?.agentmask) {
|
|
293
|
+
delete mcpConfig.mcpServers.agentmask;
|
|
294
|
+
if (Object.keys(mcpConfig.mcpServers).length === 0) {
|
|
295
|
+
delete mcpConfig.mcpServers;
|
|
296
|
+
}
|
|
297
|
+
writeFileSync2(mcpFile, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
298
|
+
console.log(" MCP server deregistered from .mcp.json");
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
console.log("\nagentmask has been removed.");
|
|
304
|
+
}
|
|
305
|
+
function filterOutAgentmask2(hooks) {
|
|
306
|
+
return hooks.filter((h) => {
|
|
307
|
+
const innerHooks = h?.hooks ?? [];
|
|
308
|
+
return !innerHooks.some(
|
|
309
|
+
(ih) => typeof ih?.command === "string" && ih.command.includes("agentmask")
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/cli/allowlist.ts
|
|
315
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
316
|
+
import { join as join3 } from "path";
|
|
317
|
+
import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
|
|
318
|
+
var CONFIG_FILE = ".agentmask.toml";
|
|
319
|
+
function loadOrCreateConfig() {
|
|
320
|
+
const configPath = join3(process.cwd(), CONFIG_FILE);
|
|
321
|
+
if (existsSync3(configPath)) {
|
|
322
|
+
try {
|
|
323
|
+
return parseTOML(readFileSync3(configPath, "utf-8"));
|
|
324
|
+
} catch {
|
|
325
|
+
return {};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return {};
|
|
329
|
+
}
|
|
330
|
+
function saveConfig(config) {
|
|
331
|
+
const configPath = join3(process.cwd(), CONFIG_FILE);
|
|
332
|
+
writeFileSync3(configPath, stringifyTOML(config) + "\n");
|
|
333
|
+
}
|
|
334
|
+
async function runAllowPath(pattern) {
|
|
335
|
+
const config = loadOrCreateConfig();
|
|
336
|
+
if (!config.allowlists) config.allowlists = [];
|
|
337
|
+
const existing = config.allowlists.find(
|
|
338
|
+
(a) => a.paths?.includes(pattern)
|
|
339
|
+
);
|
|
340
|
+
if (existing) {
|
|
341
|
+
console.log(`Path "${pattern}" is already allowlisted.`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
let pathEntry = config.allowlists.find(
|
|
345
|
+
(a) => a.paths && !a.stopwords
|
|
346
|
+
);
|
|
347
|
+
if (!pathEntry) {
|
|
348
|
+
pathEntry = { paths: [], description: "Allowlisted paths" };
|
|
349
|
+
config.allowlists.push(pathEntry);
|
|
350
|
+
}
|
|
351
|
+
if (!pathEntry.paths) pathEntry.paths = [];
|
|
352
|
+
pathEntry.paths.push(pattern);
|
|
353
|
+
saveConfig(config);
|
|
354
|
+
console.log(`Allowlisted path: "${pattern}"`);
|
|
355
|
+
console.log(`Saved to ${CONFIG_FILE}`);
|
|
356
|
+
const cwd = process.cwd();
|
|
357
|
+
if (removeFromBlocklist(pattern, cwd)) {
|
|
358
|
+
console.log(`Removed "${pattern}" from blocklist.`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async function runAllowValue(value) {
|
|
362
|
+
const config = loadOrCreateConfig();
|
|
363
|
+
if (!config.allowlists) config.allowlists = [];
|
|
364
|
+
const existing = config.allowlists.find(
|
|
365
|
+
(a) => a.stopwords?.includes(value)
|
|
366
|
+
);
|
|
367
|
+
if (existing) {
|
|
368
|
+
console.log(`Value "${value}" is already allowlisted.`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
let stopEntry = config.allowlists.find(
|
|
372
|
+
(a) => a.stopwords && !a.paths
|
|
373
|
+
);
|
|
374
|
+
if (!stopEntry) {
|
|
375
|
+
stopEntry = { stopwords: [], description: "Allowlisted values" };
|
|
376
|
+
config.allowlists.push(stopEntry);
|
|
377
|
+
}
|
|
378
|
+
if (!stopEntry.stopwords) stopEntry.stopwords = [];
|
|
379
|
+
stopEntry.stopwords.push(value);
|
|
380
|
+
saveConfig(config);
|
|
381
|
+
console.log(`Allowlisted value: "${value}"`);
|
|
382
|
+
console.log(`Saved to ${CONFIG_FILE}`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/cli.ts
|
|
386
|
+
var program = new Command();
|
|
387
|
+
program.name("agentmask").description(
|
|
388
|
+
"Mask your secrets from AI coding agents. One command. Zero friction."
|
|
389
|
+
).version("0.1.0");
|
|
390
|
+
program.command("scan [path]").description("Scan files for secrets").option("--staged", "Scan git staged files only").option("--json", "Output results as JSON").action(async (path, options) => {
|
|
391
|
+
await runScan(path, options);
|
|
392
|
+
});
|
|
393
|
+
program.command("init").description("Install agentmask hooks and MCP server in the current project").option("--team", "Write to shared .claude/settings.json instead of local").action(async (options) => {
|
|
394
|
+
await runInit(options);
|
|
395
|
+
});
|
|
396
|
+
program.command("remove").description("Remove agentmask hooks, rules, and MCP registration").action(async () => {
|
|
397
|
+
await runRemove();
|
|
398
|
+
});
|
|
399
|
+
program.command("allow-path <pattern>").description('Add a path pattern to the allowlist (e.g., "tests/**")').action(async (pattern) => {
|
|
400
|
+
await runAllowPath(pattern);
|
|
401
|
+
});
|
|
402
|
+
program.command("allow-value <value>").description('Add a value to the stopword allowlist (e.g., "EXAMPLE_KEY")').action(async (value) => {
|
|
403
|
+
await runAllowValue(value);
|
|
404
|
+
});
|
|
405
|
+
program.command("hook <type>").description("Internal: handle a Claude Code hook event").action(async (type) => {
|
|
406
|
+
switch (type) {
|
|
407
|
+
case "pre-read": {
|
|
408
|
+
await import("./pre-read-4YE6QMWV.js");
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
case "pre-bash": {
|
|
412
|
+
await import("./pre-bash-CQ6UYBND.js");
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case "pre-write": {
|
|
416
|
+
await import("./pre-write-EBMADS22.js");
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
case "post-scan": {
|
|
420
|
+
await import("./post-scan-PZBRRZS6.js");
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
default:
|
|
424
|
+
console.error(`Unknown hook type: ${type}`);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
program.command("serve").description("Start the agentmask MCP server (stdio)").action(async () => {
|
|
429
|
+
const { startServer } = await import("./server-3SUDWIDY.js");
|
|
430
|
+
await startServer();
|
|
431
|
+
});
|
|
432
|
+
program.parse();
|
|
433
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/cli/scan.ts","../src/cli/init.ts","../src/cli/remove.ts","../src/cli/allowlist.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { runScan } from \"./cli/scan.js\";\nimport { runInit } from \"./cli/init.js\";\nimport { runRemove } from \"./cli/remove.js\";\nimport { runAllowPath, runAllowValue } from \"./cli/allowlist.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"agentmask\")\n .description(\n \"Mask your secrets from AI coding agents. One command. Zero friction.\",\n )\n .version(\"0.1.0\");\n\nprogram\n .command(\"scan [path]\")\n .description(\"Scan files for secrets\")\n .option(\"--staged\", \"Scan git staged files only\")\n .option(\"--json\", \"Output results as JSON\")\n .action(async (path: string | undefined, options: { staged?: boolean; json?: boolean }) => {\n await runScan(path, options);\n });\n\nprogram\n .command(\"init\")\n .description(\"Install agentmask hooks and MCP server in the current project\")\n .option(\"--team\", \"Write to shared .claude/settings.json instead of local\")\n .action(async (options: { team?: boolean }) => {\n await runInit(options);\n });\n\nprogram\n .command(\"remove\")\n .description(\"Remove agentmask hooks, rules, and MCP registration\")\n .action(async () => {\n await runRemove();\n });\n\nprogram\n .command(\"allow-path <pattern>\")\n .description(\"Add a path pattern to the allowlist (e.g., \\\"tests/**\\\")\")\n .action(async (pattern: string) => {\n await runAllowPath(pattern);\n });\n\nprogram\n .command(\"allow-value <value>\")\n .description(\"Add a value to the stopword allowlist (e.g., \\\"EXAMPLE_KEY\\\")\")\n .action(async (value: string) => {\n await runAllowValue(value);\n });\n\n// Hook dispatch — called by Claude Code hooks\nprogram\n .command(\"hook <type>\")\n .description(\"Internal: handle a Claude Code hook event\")\n .action(async (type: string) => {\n // Dynamic import to keep CLI startup fast for non-hook commands\n switch (type) {\n case \"pre-read\": {\n await import(\"./hooks/pre-read.js\");\n break;\n }\n case \"pre-bash\": {\n await import(\"./hooks/pre-bash.js\");\n break;\n }\n case \"pre-write\": {\n await import(\"./hooks/pre-write.js\");\n break;\n }\n case \"post-scan\": {\n await import(\"./hooks/post-scan.js\");\n break;\n }\n default:\n console.error(`Unknown hook type: ${type}`);\n process.exit(1);\n }\n });\n\n// MCP server — called via .mcp.json\nprogram\n .command(\"serve\")\n .description(\"Start the agentmask MCP server (stdio)\")\n .action(async () => {\n const { startServer } = await import(\"./mcp/server.js\");\n await startServer();\n });\n\nprogram.parse();\n","import { resolve, relative } from \"node:path\";\nimport {\n scanDir,\n scanFile,\n scanStaged,\n type GitleaksFinding,\n} from \"../gitleaks/runner.js\";\n\ninterface ScanOptions {\n staged?: boolean;\n json?: boolean;\n}\n\nexport async function runScan(\n target: string | undefined,\n options: ScanOptions,\n): Promise<void> {\n const cwd = process.cwd();\n\n let findings: GitleaksFinding[];\n\n if (options.staged) {\n findings = await scanStaged(cwd);\n } else {\n const targetPath = resolve(cwd, target ?? \".\");\n findings = await scanDir(targetPath);\n }\n\n if (options.json) {\n printJSON(findings, cwd);\n } else {\n printHuman(findings, cwd);\n }\n\n process.exitCode = findings.length > 0 ? 1 : 0;\n}\n\nfunction printJSON(findings: GitleaksFinding[], cwd: string): void {\n const output = {\n findings: findings.map((f) => ({\n ruleId: f.RuleID,\n description: f.Description,\n file: relative(cwd, f.File),\n line: f.StartLine,\n entropy: f.Entropy,\n })),\n summary: {\n secretsFound: findings.length,\n filesAffected: new Set(findings.map((f) => f.File)).size,\n },\n };\n console.log(JSON.stringify(output, null, 2));\n}\n\nfunction printHuman(findings: GitleaksFinding[], cwd: string): void {\n if (findings.length === 0) {\n console.log(\"No secrets found.\");\n return;\n }\n\n for (const f of findings) {\n const relPath = relative(cwd, f.File);\n console.log(` [${f.RuleID}] ${relPath}:${f.StartLine} — ${f.Description}`);\n }\n\n const fileCount = new Set(findings.map((f) => f.File)).size;\n console.log(\"\");\n console.log(`Found ${findings.length} secret(s) across ${fileCount} file(s).`);\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join, resolve, relative } from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport { getGitleaksBinary } from \"../gitleaks/binary.js\";\nimport { scanDir, type GitleaksFinding } from \"../gitleaks/runner.js\";\nimport { saveBlocklist, type BlocklistData } from \"../hooks/blocklist.js\";\n\ninterface InitOptions {\n team?: boolean;\n}\n\nexport async function runInit(options: InitOptions): Promise<void> {\n const cwd = process.cwd();\n\n // === Step 0: Ensure gitleaks is available (auto-downloads if needed) ===\n const gitleaksBin = await getGitleaksBinary();\n\n // === Step 1: Scan entire repo for secrets ===\n console.log(\"Scanning repository for secrets...\");\n const findings = await scanDir(cwd);\n const blocklist = buildBlocklist(findings, cwd);\n const blocklistEntryCount = Object.keys(blocklist.files).length;\n\n // Count static-blocked files\n const staticBlocked = findStaticBlockedFiles(cwd);\n\n if (findings.length > 0) {\n console.log(\"\");\n console.log(` Found secrets in ${blocklistEntryCount} file(s):`);\n for (const f of findings.slice(0, 20)) {\n const relPath = relative(cwd, f.File);\n const tag = f.Description.toLowerCase().includes(\"critical\") ? \"[CRIT]\" : \"[HIGH]\";\n console.log(` ${tag} ${relPath}:${f.StartLine} — ${f.Description}`);\n }\n if (findings.length > 20) {\n console.log(` ... and ${findings.length - 20} more`);\n }\n } else {\n console.log(\" No secrets found in source files.\");\n }\n\n if (staticBlocked.length > 0) {\n console.log(\"\");\n console.log(\" Protected by default (static patterns):\");\n for (const f of staticBlocked.slice(0, 10)) {\n console.log(` [blocked] ${f}`);\n }\n if (staticBlocked.length > 10) {\n console.log(` ... and ${staticBlocked.length - 10} more`);\n }\n }\n\n // === Step 2: Save blocklist ===\n const settingsDir = join(cwd, \".claude\");\n mkdirSync(settingsDir, { recursive: true });\n saveBlocklist(cwd, blocklist);\n\n // === Step 3: Install hooks ===\n const binPath = getAgentmaskBinPath();\n const settingsFile = options.team\n ? join(settingsDir, \"settings.json\")\n : join(settingsDir, \"settings.local.json\");\n\n const settings = loadJSON(settingsFile);\n settings.hooks = mergeHooks(settings.hooks ?? {}, binPath);\n writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + \"\\n\");\n\n // === Step 4: Install rules ===\n const rulesDir = join(settingsDir, \"rules\");\n mkdirSync(rulesDir, { recursive: true });\n writeFileSync(join(rulesDir, \"agentmask.md\"), RULES_CONTENT);\n\n // === Step 5: Register MCP server ===\n const mcpFile = join(cwd, \".mcp.json\");\n const mcpConfig = loadJSON(mcpFile);\n mcpConfig.mcpServers = mcpConfig.mcpServers ?? {};\n mcpConfig.mcpServers.agentmask = {\n command: binPath,\n args: [\"serve\"],\n };\n writeFileSync(mcpFile, JSON.stringify(mcpConfig, null, 2) + \"\\n\");\n\n // === Summary ===\n const totalProtected = blocklistEntryCount + staticBlocked.length;\n console.log(\"\");\n console.log(` Hooks installed → ${options.team ? \".claude/settings.json\" : \".claude/settings.local.json\"}`);\n console.log(\" Rules installed → .claude/rules/agentmask.md\");\n console.log(\" MCP server registered → .mcp.json\");\n if (blocklistEntryCount > 0) {\n console.log(` Blocklist saved → .claude/agentmask-blocklist.json (${blocklistEntryCount} file(s))`);\n }\n console.log(\"\");\n console.log(`agentmask is active. ${totalProtected} file(s) protected.`);\n console.log(\"Re-run `agentmask init` anytime to rescan.\");\n}\n\nfunction buildBlocklist(\n findings: GitleaksFinding[],\n cwd: string,\n): BlocklistData {\n const blocklist: BlocklistData = { files: {} };\n\n for (const f of findings) {\n const relPath = relative(cwd, f.File).replace(/\\\\/g, \"/\");\n if (!blocklist.files[relPath]) {\n blocklist.files[relPath] = {\n secrets: [],\n addedAt: new Date().toISOString(),\n };\n }\n const entry = blocklist.files[relPath];\n if (!entry.secrets.includes(f.Description)) {\n entry.secrets.push(f.Description);\n }\n }\n\n return blocklist;\n}\n\nfunction findStaticBlockedFiles(cwd: string): string[] {\n const results: string[] = [];\n const commonFiles = [\n \".env\", \".env.local\", \".env.development\", \".env.production\",\n \".env.staging\", \".env.test\",\n \"credentials.json\", \"serviceAccountKey.json\",\n \".npmrc\", \".pypirc\",\n ];\n\n for (const file of commonFiles) {\n if (existsSync(join(cwd, file))) {\n results.push(file);\n }\n }\n\n return results;\n}\n\nfunction getAgentmaskBinPath(): string {\n try {\n const resolved = execSync(\"which agentmask\", { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n if (resolved) return resolved;\n } catch {}\n\n const localBin = resolve(\"node_modules\", \".bin\", \"agentmask\");\n if (existsSync(localBin)) return localBin;\n\n return \"npx agentmask\";\n}\n\nfunction mergeHooks(\n existing: Record<string, unknown>,\n binPath: string,\n): Record<string, unknown> {\n const preToolUse = (existing.PreToolUse as unknown[]) ?? [];\n const postToolUse = (existing.PostToolUse as unknown[]) ?? [];\n\n const cleanPre = filterOutAgentmask(preToolUse);\n const cleanPost = filterOutAgentmask(postToolUse);\n\n return {\n ...existing,\n PreToolUse: [\n ...cleanPre,\n { matcher: \"Read\", hooks: [{ type: \"command\", command: `${binPath} hook pre-read`, timeout: 5 }] },\n { matcher: \"Bash\", hooks: [{ type: \"command\", command: `${binPath} hook pre-bash`, timeout: 5 }] },\n { matcher: \"Write|Edit\", hooks: [{ type: \"command\", command: `${binPath} hook pre-write`, timeout: 5 }] },\n ],\n PostToolUse: [\n ...cleanPost,\n { matcher: \"Read|Bash\", hooks: [{ type: \"command\", command: `${binPath} hook post-scan`, timeout: 5 }] },\n ],\n };\n}\n\nfunction filterOutAgentmask(hooks: unknown[]): unknown[] {\n return hooks.filter((h: any) => {\n const innerHooks = h?.hooks ?? [];\n return !innerHooks.some((ih: any) =>\n typeof ih?.command === \"string\" && ih.command.includes(\"agentmask\"),\n );\n });\n}\n\nfunction loadJSON(filePath: string): Record<string, any> {\n try {\n return JSON.parse(readFileSync(filePath, \"utf-8\"));\n } catch {\n return {};\n }\n}\n\nconst RULES_CONTENT = `## agentmask — Secrets Protection Rules\n\nThese rules are enforced by agentmask hooks. Follow them to avoid\nblocked operations and protect user secrets.\n\n### Reading Sensitive Files\n- When you need to read files that may contain secrets (.env,\n credentials.json, *.pem, *.key, etc.), use the\n \\`mcp__agentmask__safe_read\\` tool instead of the built-in Read tool.\n- If a Read operation is blocked with a secrets warning, do NOT retry\n the Read. Use \\`mcp__agentmask__safe_read\\` to get a redacted view.\n- To see what environment variables are defined without their values,\n use \\`mcp__agentmask__env_names\\`.\n\n### Writing Code\n- Never hardcode secret values (API keys, tokens, passwords,\n connection strings) in source code. Use environment variable\n references: process.env.VAR_NAME, os.environ[\"VAR_NAME\"], etc.\n- If a Write/Edit is blocked for containing a secret, rewrite the\n code to use environment variable references.\n\n### Committing Code\n- Before any git commit, run \\`mcp__agentmask__scan_staged\\` to verify\n no secrets are in staged files.\n- If a git commit is blocked for containing secrets, fix the flagged\n files first, then retry the commit.\n\n### General\n- Never output raw secret values in your responses. Reference secrets\n by their variable name only (e.g., \"your DATABASE_URL\" not the\n actual connection string).\n- When you see a [REDACTED:...] placeholder, do not attempt to\n discover the actual value. Work with the variable name.\n`;\n","import { existsSync, readFileSync, writeFileSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport async function runRemove(): Promise<void> {\n const cwd = process.cwd();\n const settingsDir = join(cwd, \".claude\");\n\n // Remove hooks from both settings files\n for (const filename of [\"settings.local.json\", \"settings.json\"]) {\n const settingsFile = join(settingsDir, filename);\n if (existsSync(settingsFile)) {\n try {\n const settings = JSON.parse(readFileSync(settingsFile, \"utf-8\"));\n if (settings.hooks) {\n settings.hooks.PreToolUse = filterOutAgentmask(\n settings.hooks.PreToolUse ?? [],\n );\n settings.hooks.PostToolUse = filterOutAgentmask(\n settings.hooks.PostToolUse ?? [],\n );\n\n // Clean up empty hook arrays\n if (settings.hooks.PreToolUse.length === 0)\n delete settings.hooks.PreToolUse;\n if (settings.hooks.PostToolUse.length === 0)\n delete settings.hooks.PostToolUse;\n if (Object.keys(settings.hooks).length === 0) delete settings.hooks;\n\n writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + \"\\n\");\n console.log(` Hooks removed from ${filename}`);\n }\n } catch {\n // File isn't valid JSON — skip\n }\n }\n }\n\n // Remove blocklist\n const blocklistFile = join(settingsDir, \"agentmask-blocklist.json\");\n if (existsSync(blocklistFile)) {\n unlinkSync(blocklistFile);\n console.log(\" Blocklist removed: .claude/agentmask-blocklist.json\");\n }\n\n // Remove rules file\n const rulesFile = join(settingsDir, \"rules\", \"agentmask.md\");\n if (existsSync(rulesFile)) {\n unlinkSync(rulesFile);\n console.log(\" Rules removed: .claude/rules/agentmask.md\");\n }\n\n // Remove MCP server from .mcp.json\n const mcpFile = join(cwd, \".mcp.json\");\n if (existsSync(mcpFile)) {\n try {\n const mcpConfig = JSON.parse(readFileSync(mcpFile, \"utf-8\"));\n if (mcpConfig.mcpServers?.agentmask) {\n delete mcpConfig.mcpServers.agentmask;\n if (Object.keys(mcpConfig.mcpServers).length === 0) {\n delete mcpConfig.mcpServers;\n }\n writeFileSync(mcpFile, JSON.stringify(mcpConfig, null, 2) + \"\\n\");\n console.log(\" MCP server deregistered from .mcp.json\");\n }\n } catch {\n // Not valid JSON — skip\n }\n }\n\n console.log(\"\\nagentmask has been removed.\");\n}\n\nfunction filterOutAgentmask(hooks: any[]): any[] {\n return hooks.filter((h: any) => {\n const innerHooks = h?.hooks ?? [];\n return !innerHooks.some(\n (ih: any) =>\n typeof ih?.command === \"string\" && ih.command.includes(\"agentmask\"),\n );\n });\n}\n","import { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parse as parseTOML, stringify as stringifyTOML } from \"smol-toml\";\nimport { removeFromBlocklist } from \"../hooks/blocklist.js\";\n\nconst CONFIG_FILE = \".agentmask.toml\";\n\ninterface Config {\n scan?: { blocked_paths?: string[] };\n allowlists?: Array<{\n paths?: string[];\n stopwords?: string[];\n description?: string;\n }>;\n [key: string]: unknown;\n}\n\nfunction loadOrCreateConfig(): Config {\n const configPath = join(process.cwd(), CONFIG_FILE);\n if (existsSync(configPath)) {\n try {\n return parseTOML(readFileSync(configPath, \"utf-8\")) as unknown as Config;\n } catch {\n return {};\n }\n }\n return {};\n}\n\nfunction saveConfig(config: Config): void {\n const configPath = join(process.cwd(), CONFIG_FILE);\n writeFileSync(configPath, stringifyTOML(config as any) + \"\\n\");\n}\n\nexport async function runAllowPath(pattern: string): Promise<void> {\n const config = loadOrCreateConfig();\n\n if (!config.allowlists) config.allowlists = [];\n\n // Check if this path is already allowlisted\n const existing = config.allowlists.find((a) =>\n a.paths?.includes(pattern),\n );\n if (existing) {\n console.log(`Path \"${pattern}\" is already allowlisted.`);\n return;\n }\n\n // Find or create a path allowlist entry\n let pathEntry = config.allowlists.find(\n (a) => a.paths && !a.stopwords,\n );\n if (!pathEntry) {\n pathEntry = { paths: [], description: \"Allowlisted paths\" };\n config.allowlists.push(pathEntry);\n }\n if (!pathEntry.paths) pathEntry.paths = [];\n pathEntry.paths.push(pattern);\n\n saveConfig(config);\n console.log(`Allowlisted path: \"${pattern}\"`);\n console.log(`Saved to ${CONFIG_FILE}`);\n\n // Also remove from dynamic blocklist if present\n const cwd = process.cwd();\n if (removeFromBlocklist(pattern, cwd)) {\n console.log(`Removed \"${pattern}\" from blocklist.`);\n }\n}\n\nexport async function runAllowValue(value: string): Promise<void> {\n const config = loadOrCreateConfig();\n\n if (!config.allowlists) config.allowlists = [];\n\n // Check if this value is already allowlisted\n const existing = config.allowlists.find((a) =>\n a.stopwords?.includes(value),\n );\n if (existing) {\n console.log(`Value \"${value}\" is already allowlisted.`);\n return;\n }\n\n // Find or create a stopwords allowlist entry\n let stopEntry = config.allowlists.find(\n (a) => a.stopwords && !a.paths,\n );\n if (!stopEntry) {\n stopEntry = { stopwords: [], description: \"Allowlisted values\" };\n config.allowlists.push(stopEntry);\n }\n if (!stopEntry.stopwords) stopEntry.stopwords = [];\n stopEntry.stopwords.push(value);\n\n saveConfig(config);\n console.log(`Allowlisted value: \"${value}\"`);\n console.log(`Saved to ${CONFIG_FILE}`);\n}\n"],"mappings":"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","names":["resolve","relative","relative","resolve","existsSync","readFileSync","writeFileSync","join","filterOutAgentmask","existsSync","readFileSync","writeFileSync","join","join","existsSync","readFileSync","writeFileSync"]}
|