@levnikolaevich/hex-line-mcp 1.3.3 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hook.mjs +428 -0
- package/dist/server.mjs +2645 -0
- package/package.json +8 -8
- package/benchmark/atomic.mjs +0 -502
- package/benchmark/graph.mjs +0 -80
- package/benchmark/index.mjs +0 -144
- package/benchmark/workflows.mjs +0 -350
- package/hook.mjs +0 -466
- package/lib/benchmark-helpers.mjs +0 -541
- package/lib/bulk-replace.mjs +0 -65
- package/lib/changes.mjs +0 -176
- package/lib/coerce.mjs +0 -1
- package/lib/edit.mjs +0 -534
- package/lib/format.mjs +0 -138
- package/lib/graph-enrich.mjs +0 -226
- package/lib/hash.mjs +0 -1
- package/lib/info.mjs +0 -91
- package/lib/normalize.mjs +0 -1
- package/lib/outline.mjs +0 -145
- package/lib/read.mjs +0 -138
- package/lib/revisions.mjs +0 -238
- package/lib/search.mjs +0 -268
- package/lib/security.mjs +0 -112
- package/lib/setup.mjs +0 -275
- package/lib/tree.mjs +0 -236
- package/lib/update-check.mjs +0 -1
- package/lib/verify.mjs +0 -70
- package/server.mjs +0 -375
package/dist/hook.mjs
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../hex-common/src/output/normalize.mjs
|
|
4
|
+
var NORM_RULES = [
|
|
5
|
+
[/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<UUID>"],
|
|
6
|
+
[/\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}/g, "<TS>"],
|
|
7
|
+
[/\d{2}-\d{2}-\d{4}\s\d{2}:\d{2}:\d{2}/g, "<TS>"],
|
|
8
|
+
[/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?/g, "<IP>"],
|
|
9
|
+
[/\/[0-9a-f]{8,}/gi, "/<ID>"],
|
|
10
|
+
[/\b\d{3,}(?=\b|[a-zA-Z])/g, "<N>"],
|
|
11
|
+
[/trace_id=[0-9a-fA-F]{1,8}/g, "trace_id=<TRACE>"]
|
|
12
|
+
];
|
|
13
|
+
function normalizeLine(line) {
|
|
14
|
+
let result = line;
|
|
15
|
+
for (const [rx, repl] of NORM_RULES) {
|
|
16
|
+
result = result.replace(rx, repl);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
function deduplicateLines(lines) {
|
|
21
|
+
const groups = /* @__PURE__ */ new Map();
|
|
22
|
+
const order = [];
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
const norm = normalizeLine(line);
|
|
25
|
+
if (groups.has(norm)) groups.get(norm).count++;
|
|
26
|
+
else {
|
|
27
|
+
groups.set(norm, { representative: line, count: 1 });
|
|
28
|
+
order.push(norm);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
order.sort((a, b) => groups.get(b).count - groups.get(a).count);
|
|
32
|
+
return order.map((norm) => {
|
|
33
|
+
const { representative, count } = groups.get(norm);
|
|
34
|
+
return count > 1 ? `${representative} (x${count})` : representative;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function smartTruncate(text, headLines = 40, tailLines = 20) {
|
|
38
|
+
const lines = text.split("\n");
|
|
39
|
+
const total = lines.length;
|
|
40
|
+
const maxLines = headLines + tailLines;
|
|
41
|
+
if (total <= maxLines) return text;
|
|
42
|
+
const head = lines.slice(0, headLines);
|
|
43
|
+
const tail = lines.slice(total - tailLines);
|
|
44
|
+
const skipped = total - maxLines;
|
|
45
|
+
return [...head, `
|
|
46
|
+
--- ${skipped} lines omitted ---
|
|
47
|
+
`, ...tail].join("\n");
|
|
48
|
+
}
|
|
49
|
+
function normalizeOutput(text, opts = {}) {
|
|
50
|
+
const { deduplicate = true, headLines = 40, tailLines = 20 } = opts;
|
|
51
|
+
const lines = text.split("\n");
|
|
52
|
+
const processed = deduplicate ? deduplicateLines(lines) : lines;
|
|
53
|
+
return smartTruncate(processed.join("\n"), headLines, tailLines);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// hook.mjs
|
|
57
|
+
import { readFileSync } from "node:fs";
|
|
58
|
+
import { resolve } from "node:path";
|
|
59
|
+
import { homedir } from "node:os";
|
|
60
|
+
import { fileURLToPath } from "node:url";
|
|
61
|
+
var BINARY_EXT = /* @__PURE__ */ new Set([
|
|
62
|
+
".png",
|
|
63
|
+
".jpg",
|
|
64
|
+
".jpeg",
|
|
65
|
+
".gif",
|
|
66
|
+
".bmp",
|
|
67
|
+
".webp",
|
|
68
|
+
".svg",
|
|
69
|
+
".ico",
|
|
70
|
+
".pdf",
|
|
71
|
+
".ipynb",
|
|
72
|
+
".zip",
|
|
73
|
+
".tar",
|
|
74
|
+
".gz",
|
|
75
|
+
".7z",
|
|
76
|
+
".rar",
|
|
77
|
+
".exe",
|
|
78
|
+
".dll",
|
|
79
|
+
".so",
|
|
80
|
+
".dylib",
|
|
81
|
+
".wasm",
|
|
82
|
+
".mp3",
|
|
83
|
+
".mp4",
|
|
84
|
+
".wav",
|
|
85
|
+
".avi",
|
|
86
|
+
".mkv",
|
|
87
|
+
".ttf",
|
|
88
|
+
".otf",
|
|
89
|
+
".woff",
|
|
90
|
+
".woff2"
|
|
91
|
+
]);
|
|
92
|
+
var REVERSE_TOOL_HINTS = {
|
|
93
|
+
"mcp__hex-line__read_file": "Read (file_path, offset, limit)",
|
|
94
|
+
"mcp__hex-line__edit_file": "Edit (revision-aware hash edits, block rewrite, auto-rebase)",
|
|
95
|
+
"mcp__hex-line__write_file": "Write (file_path, content)",
|
|
96
|
+
"mcp__hex-line__grep_search": "Grep (pattern, path)",
|
|
97
|
+
"mcp__hex-line__directory_tree": "Glob (pattern) or Bash(ls)",
|
|
98
|
+
"mcp__hex-line__get_file_info": "Bash(stat/wc)",
|
|
99
|
+
"mcp__hex-line__outline": "Read with offset/limit",
|
|
100
|
+
"mcp__hex-line__verify": "Verify held checksums / revision without reread",
|
|
101
|
+
"mcp__hex-line__changes": "Bash(git diff)",
|
|
102
|
+
"mcp__hex-line__bulk_replace": "Edit (text rename/refactor across files)",
|
|
103
|
+
"mcp__hex-line__setup_hooks": "Not available (hex-line disabled)"
|
|
104
|
+
};
|
|
105
|
+
var TOOL_HINTS = {
|
|
106
|
+
Read: "mcp__hex-line__read_file (not Read). For writing: write_file (no prior Read needed)",
|
|
107
|
+
Edit: "mcp__hex-line__edit_file for revision-aware hash edits. Batch same-file hunks, carry base_revision, use replace_between for block rewrites",
|
|
108
|
+
Write: "mcp__hex-line__write_file (not Write). No prior Read needed",
|
|
109
|
+
Grep: "mcp__hex-line__grep_search (not Grep). Params: output, literal, context_before, context_after, multiline",
|
|
110
|
+
cat: "mcp__hex-line__read_file (not cat/head/tail/less/more)",
|
|
111
|
+
head: "mcp__hex-line__read_file with limit param (not head)",
|
|
112
|
+
tail: "mcp__hex-line__read_file with offset param (not tail)",
|
|
113
|
+
ls: "mcp__hex-line__directory_tree with pattern param (not ls/find/tree). E.g. pattern='*-mcp' type='dir'",
|
|
114
|
+
stat: "mcp__hex-line__get_file_info (not stat/wc/file)",
|
|
115
|
+
grep: "mcp__hex-line__grep_search (not grep/rg). Params: output, literal, context_before, context_after, multiline",
|
|
116
|
+
sed: "mcp__hex-line__edit_file for hash edits, or mcp__hex-line__bulk_replace for text rename (not sed -i)",
|
|
117
|
+
diff: "mcp__hex-line__changes (not diff). Git-based semantic diff",
|
|
118
|
+
outline: "mcp__hex-line__outline (before reading large code files)",
|
|
119
|
+
verify: "mcp__hex-line__verify (staleness / revision check without re-read)",
|
|
120
|
+
changes: "mcp__hex-line__changes (semantic AST diff)",
|
|
121
|
+
bulk: "mcp__hex-line__bulk_replace (multi-file search-replace)",
|
|
122
|
+
setup: "mcp__hex-line__setup_hooks (configure hooks for agents)"
|
|
123
|
+
};
|
|
124
|
+
var BASH_REDIRECTS = [
|
|
125
|
+
{ regex: /^cat\s+\S+/, key: "cat" },
|
|
126
|
+
{ regex: /^head\s+/, key: "head" },
|
|
127
|
+
{ regex: /^tail\s+(?!-[fF])/, key: "tail" },
|
|
128
|
+
{ regex: /^(less|more)\s+/, key: "cat" },
|
|
129
|
+
{ regex: /^ls\s+-\S*R(\s|$)/, key: "ls" },
|
|
130
|
+
// ls -R, ls -laR (recursive only)
|
|
131
|
+
{ regex: /^dir\s+\/[sS](\s|$)/, key: "ls" },
|
|
132
|
+
// dir /s, dir /S (recursive only)
|
|
133
|
+
{ regex: /^tree\s+/, key: "ls" },
|
|
134
|
+
{ regex: /^find\s+/, key: "ls" },
|
|
135
|
+
{ regex: /^(stat|wc)\s+/, key: "stat" },
|
|
136
|
+
{ regex: /^(grep|rg)\s+/, key: "grep" },
|
|
137
|
+
{ regex: /^sed\s+-i/, key: "sed" }
|
|
138
|
+
];
|
|
139
|
+
var TOOL_REDIRECT_MAP = {
|
|
140
|
+
Read: "Read",
|
|
141
|
+
Edit: "Edit",
|
|
142
|
+
Write: "Write",
|
|
143
|
+
Grep: "Grep"
|
|
144
|
+
};
|
|
145
|
+
var DANGEROUS_PATTERNS = [
|
|
146
|
+
{
|
|
147
|
+
regex: /rm\s+(-[rf]+\s+)*[/~]/,
|
|
148
|
+
reason: "rm -rf on root/home directory"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
regex: /git\s+push\s+(-f|--force)/,
|
|
152
|
+
reason: "force push can overwrite remote history"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
regex: /git\s+reset\s+--hard/,
|
|
156
|
+
reason: "hard reset discards uncommitted changes"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
regex: /DROP\s+(TABLE|DATABASE)/i,
|
|
160
|
+
reason: "DROP destroys data permanently"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
regex: /chmod\s+777/,
|
|
164
|
+
reason: "chmod 777 removes all access restrictions"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
regex: /mkfs/,
|
|
168
|
+
reason: "filesystem format destroys all data"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
regex: /dd\s+if=\/dev\/zero/,
|
|
172
|
+
reason: "direct disk write destroys data"
|
|
173
|
+
}
|
|
174
|
+
];
|
|
175
|
+
var COMPOUND_OPERATORS = /[|]|>>?|&&|\|\||;/;
|
|
176
|
+
var CMD_PATTERNS = [
|
|
177
|
+
[/npm (install|ci|update|add)/i, "npm-install"],
|
|
178
|
+
[/npm test|jest|vitest|mocha|pytest|cargo test/i, "test"],
|
|
179
|
+
[/npm run build|tsc|webpack|vite build|cargo build/i, "build"],
|
|
180
|
+
[/pip install/i, "pip-install"],
|
|
181
|
+
[/git (log|diff|status)/i, "git"]
|
|
182
|
+
];
|
|
183
|
+
var LINE_THRESHOLD = 50;
|
|
184
|
+
var HEAD_LINES = 15;
|
|
185
|
+
var TAIL_LINES = 15;
|
|
186
|
+
function extOf(filePath) {
|
|
187
|
+
const dot = filePath.lastIndexOf(".");
|
|
188
|
+
return dot !== -1 ? filePath.slice(dot).toLowerCase() : "";
|
|
189
|
+
}
|
|
190
|
+
function detectCommandType(cmd) {
|
|
191
|
+
for (const [re, type] of CMD_PATTERNS) {
|
|
192
|
+
if (re.test(cmd)) return type;
|
|
193
|
+
}
|
|
194
|
+
return "generic";
|
|
195
|
+
}
|
|
196
|
+
function extractBashText(response) {
|
|
197
|
+
if (typeof response === "string") return response;
|
|
198
|
+
if (response && typeof response === "object") {
|
|
199
|
+
const parts = [];
|
|
200
|
+
if (response.stdout) parts.push(response.stdout);
|
|
201
|
+
if (response.stderr) parts.push(response.stderr);
|
|
202
|
+
return parts.join("\n") || "";
|
|
203
|
+
}
|
|
204
|
+
return "";
|
|
205
|
+
}
|
|
206
|
+
var _hexLineDisabled = null;
|
|
207
|
+
function isHexLineDisabled(configPath) {
|
|
208
|
+
if (_hexLineDisabled !== null) return _hexLineDisabled;
|
|
209
|
+
_hexLineDisabled = false;
|
|
210
|
+
try {
|
|
211
|
+
const p = configPath || resolve(homedir(), ".claude.json");
|
|
212
|
+
const claudeJson = JSON.parse(readFileSync(p, "utf-8"));
|
|
213
|
+
const projects = claudeJson.projects;
|
|
214
|
+
if (!projects || typeof projects !== "object") return _hexLineDisabled;
|
|
215
|
+
const cwd = process.cwd().replace(/\\/g, "/").replace(/\/$/, "").toLowerCase();
|
|
216
|
+
for (const [path, config] of Object.entries(projects)) {
|
|
217
|
+
if (path.replace(/\\/g, "/").replace(/\/$/, "").toLowerCase() === cwd) {
|
|
218
|
+
const disabled = config.disabledMcpServers;
|
|
219
|
+
if (Array.isArray(disabled) && disabled.includes("hex-line")) {
|
|
220
|
+
_hexLineDisabled = true;
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
return _hexLineDisabled;
|
|
228
|
+
}
|
|
229
|
+
function _resetHexLineDisabledCache() {
|
|
230
|
+
_hexLineDisabled = null;
|
|
231
|
+
}
|
|
232
|
+
function block(reason, context) {
|
|
233
|
+
const output = {
|
|
234
|
+
hookSpecificOutput: {
|
|
235
|
+
hookEventName: "PreToolUse",
|
|
236
|
+
permissionDecision: "deny",
|
|
237
|
+
permissionDecisionReason: reason
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
if (context) output.hookSpecificOutput.additionalContext = context;
|
|
241
|
+
process.stdout.write(JSON.stringify(output));
|
|
242
|
+
process.exit(2);
|
|
243
|
+
}
|
|
244
|
+
function handlePreToolUse(data) {
|
|
245
|
+
const toolName = data.tool_name || "";
|
|
246
|
+
const toolInput = data.tool_input || {};
|
|
247
|
+
if (toolName.startsWith("mcp__hex-line__")) {
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
const hintKey = TOOL_REDIRECT_MAP[toolName];
|
|
251
|
+
if (hintKey) {
|
|
252
|
+
const filePath = toolInput.file_path || toolInput.path || "";
|
|
253
|
+
if (BINARY_EXT.has(extOf(filePath))) {
|
|
254
|
+
process.exit(0);
|
|
255
|
+
}
|
|
256
|
+
const normalPath = filePath.replace(/\\/g, "/");
|
|
257
|
+
if (normalPath.includes(".claude/plans/") || normalPath.includes("AppData")) {
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
const ALLOWED_CONFIGS = /* @__PURE__ */ new Set(["settings.json", "settings.local.json"]);
|
|
261
|
+
const fileName = normalPath.split("/").pop();
|
|
262
|
+
if (ALLOWED_CONFIGS.has(fileName)) {
|
|
263
|
+
let candidate = filePath;
|
|
264
|
+
if (candidate.startsWith("~/")) {
|
|
265
|
+
candidate = homedir().replace(/\\/g, "/") + candidate.slice(1);
|
|
266
|
+
}
|
|
267
|
+
const absPath = resolve(process.cwd(), candidate).replace(/\\/g, "/");
|
|
268
|
+
const projectClaude = resolve(process.cwd(), ".claude").replace(/\\/g, "/") + "/";
|
|
269
|
+
const globalClaude = resolve(homedir(), ".claude").replace(/\\/g, "/") + "/";
|
|
270
|
+
const cmp = process.platform === "win32" ? (a, b) => a.toLowerCase().startsWith(b.toLowerCase()) : (a, b) => a.startsWith(b);
|
|
271
|
+
if (cmp(absPath, projectClaude) || cmp(absPath, globalClaude)) {
|
|
272
|
+
process.exit(0);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const hint = TOOL_HINTS[hintKey];
|
|
276
|
+
const toolName2 = hint.split(" (")[0];
|
|
277
|
+
const pathNote = filePath ? ` with path="${filePath}"` : "";
|
|
278
|
+
block(`Use ${toolName2}${pathNote}`, hint);
|
|
279
|
+
}
|
|
280
|
+
if (toolName === "Bash") {
|
|
281
|
+
const command = (toolInput.command || "").trim();
|
|
282
|
+
if (command.includes("# hex-confirmed")) {
|
|
283
|
+
process.exit(0);
|
|
284
|
+
}
|
|
285
|
+
const cmdCheck = command.replace(/<<['"]?(\w+)['"]?\s*\n[\s\S]*?\n\1/g, "");
|
|
286
|
+
for (const { regex, reason } of DANGEROUS_PATTERNS) {
|
|
287
|
+
if (regex.test(cmdCheck)) {
|
|
288
|
+
block(
|
|
289
|
+
`DANGEROUS: ${reason}. Ask user to confirm, then retry with: # hex-confirmed`,
|
|
290
|
+
`Original command: ${command.slice(0, 100)}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (COMPOUND_OPERATORS.test(command)) {
|
|
295
|
+
const firstCmd = command.split(/\s*[|;&>]\s*/)[0].trim();
|
|
296
|
+
for (const { regex, key } of BASH_REDIRECTS) {
|
|
297
|
+
if (regex.test(firstCmd)) {
|
|
298
|
+
const hint = TOOL_HINTS[key];
|
|
299
|
+
const toolName2 = hint.split(" (")[0];
|
|
300
|
+
block(`Use ${toolName2} instead of piped command`, hint);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
process.exit(0);
|
|
304
|
+
}
|
|
305
|
+
for (const { regex, key } of BASH_REDIRECTS) {
|
|
306
|
+
if (regex.test(command)) {
|
|
307
|
+
const hint = TOOL_HINTS[key];
|
|
308
|
+
const toolName2 = hint.split(" (")[0];
|
|
309
|
+
const args = command.split(/\s+/).slice(1).join(" ");
|
|
310
|
+
const argsNote = args ? ` \u2014 args: "${args}"` : "";
|
|
311
|
+
block(`Use ${toolName2}${argsNote}`, hint);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
process.exit(0);
|
|
316
|
+
}
|
|
317
|
+
function handlePreToolUseReverse(data) {
|
|
318
|
+
const toolName = data.tool_name || "";
|
|
319
|
+
if (toolName.startsWith("mcp__hex-line__")) {
|
|
320
|
+
const builtIn = REVERSE_TOOL_HINTS[toolName];
|
|
321
|
+
if (builtIn) {
|
|
322
|
+
const target = builtIn.split(" ")[0];
|
|
323
|
+
block(
|
|
324
|
+
`hex-line is disabled in this project. Use ${target}`,
|
|
325
|
+
`hex-line disabled. Use built-in: ${builtIn}`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
block("hex-line is disabled in this project", "Disabled via project settings");
|
|
329
|
+
}
|
|
330
|
+
process.exit(0);
|
|
331
|
+
}
|
|
332
|
+
function handlePostToolUse(data) {
|
|
333
|
+
const toolName = data.tool_name || "";
|
|
334
|
+
if (toolName !== "Bash") {
|
|
335
|
+
process.exit(0);
|
|
336
|
+
}
|
|
337
|
+
const toolInput = data.tool_input || {};
|
|
338
|
+
const rawText = extractBashText(data.tool_response);
|
|
339
|
+
const command = toolInput.command || "";
|
|
340
|
+
if (!rawText) {
|
|
341
|
+
process.exit(0);
|
|
342
|
+
}
|
|
343
|
+
const lines = rawText.split("\n");
|
|
344
|
+
const originalCount = lines.length;
|
|
345
|
+
if (originalCount < LINE_THRESHOLD) {
|
|
346
|
+
process.exit(0);
|
|
347
|
+
}
|
|
348
|
+
const type = detectCommandType(command);
|
|
349
|
+
const filtered = normalizeOutput(lines.join("\n"), { headLines: HEAD_LINES, tailLines: TAIL_LINES });
|
|
350
|
+
const filteredCount = filtered.split("\n").length;
|
|
351
|
+
const header = `RTK FILTERED: ${type} (${originalCount} lines -> ${filteredCount} lines)`;
|
|
352
|
+
const output = [
|
|
353
|
+
"=".repeat(50),
|
|
354
|
+
header,
|
|
355
|
+
"=".repeat(50),
|
|
356
|
+
"",
|
|
357
|
+
filtered,
|
|
358
|
+
"",
|
|
359
|
+
"-".repeat(50),
|
|
360
|
+
`Original: ${originalCount} lines | Filtered: ${filteredCount} lines`,
|
|
361
|
+
"=".repeat(50)
|
|
362
|
+
].join("\n");
|
|
363
|
+
process.stderr.write(output);
|
|
364
|
+
process.exit(2);
|
|
365
|
+
}
|
|
366
|
+
function handleSessionStart() {
|
|
367
|
+
const settingsFiles = [
|
|
368
|
+
resolve(process.cwd(), ".claude/settings.local.json"),
|
|
369
|
+
resolve(process.cwd(), ".claude/settings.json"),
|
|
370
|
+
resolve(homedir(), ".claude/settings.json")
|
|
371
|
+
];
|
|
372
|
+
let styleActive = false;
|
|
373
|
+
for (const f of settingsFiles) {
|
|
374
|
+
try {
|
|
375
|
+
const config = JSON.parse(readFileSync(f, "utf-8"));
|
|
376
|
+
if (config.outputStyle === "hex-line") {
|
|
377
|
+
styleActive = true;
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
if (config.outputStyle) break;
|
|
381
|
+
} catch {
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (styleActive) {
|
|
385
|
+
process.stdout.write(JSON.stringify({ systemMessage: "hex-line Output Style active." }));
|
|
386
|
+
process.exit(0);
|
|
387
|
+
}
|
|
388
|
+
const seen = /* @__PURE__ */ new Set();
|
|
389
|
+
const lines = [];
|
|
390
|
+
for (const hint of Object.values(TOOL_HINTS)) {
|
|
391
|
+
const tool = hint.split(" ")[0];
|
|
392
|
+
if (!seen.has(tool)) {
|
|
393
|
+
seen.add(tool);
|
|
394
|
+
lines.push(`- ${hint}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
lines.push("Exceptions: images, PDFs, notebooks, .claude/settings.json, .claude/settings.local.json \u2192 built-in Read; Glob always OK");
|
|
398
|
+
lines.push("Bash OK for: npm/node/git/docker/curl, pipes, scripts");
|
|
399
|
+
const msg = "Hex-line MCP available. Workflow:\n- Discovery: read_file, grep_search, outline, directory_tree\n- Same-file edits: prefer ONE edit_file call per file, carry revision/base_revision\n- Hash edits: edit_file (set_line, replace_lines, insert_after, replace_between)\n- Large rewrites: replace_between instead of reciting old blocks\n- Text rename: bulk_replace (multi-file search-replace)\n- Verify staleness: verify before considering reread\n- Write new: write_file\n" + lines.join("\n");
|
|
400
|
+
process.stdout.write(JSON.stringify({ systemMessage: msg }));
|
|
401
|
+
process.exit(0);
|
|
402
|
+
}
|
|
403
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
404
|
+
let input = "";
|
|
405
|
+
process.stdin.on("data", (chunk) => {
|
|
406
|
+
input += chunk;
|
|
407
|
+
});
|
|
408
|
+
process.stdin.on("end", () => {
|
|
409
|
+
try {
|
|
410
|
+
const data = JSON.parse(input);
|
|
411
|
+
const event = data.hook_event_name || "";
|
|
412
|
+
if (isHexLineDisabled()) {
|
|
413
|
+
if (event === "PreToolUse") handlePreToolUseReverse(data);
|
|
414
|
+
process.exit(0);
|
|
415
|
+
}
|
|
416
|
+
if (event === "SessionStart") handleSessionStart();
|
|
417
|
+
else if (event === "PreToolUse") handlePreToolUse(data);
|
|
418
|
+
else if (event === "PostToolUse") handlePostToolUse(data);
|
|
419
|
+
else process.exit(0);
|
|
420
|
+
} catch {
|
|
421
|
+
process.exit(0);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
export {
|
|
426
|
+
_resetHexLineDisabledCache,
|
|
427
|
+
isHexLineDisabled
|
|
428
|
+
};
|