vibe-code-explainer 0.3.2 → 0.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/chunk-GEAH6PTG.js +37 -0
- package/dist/chunk-GEAH6PTG.js.map +1 -0
- package/dist/chunk-GU4Y5ZWY.js +140 -0
- package/dist/chunk-GU4Y5ZWY.js.map +1 -0
- package/dist/{chunk-2PUO5G3C.js → chunk-KK76JK7S.js} +32 -92
- package/dist/chunk-KK76JK7S.js.map +1 -0
- package/dist/{chunk-Y55I7ZS5.js → chunk-VJN7Y4SI.js} +185 -71
- package/dist/chunk-VJN7Y4SI.js.map +1 -0
- package/dist/{chunk-2IARGRDK.js → chunk-ZZY3IDL2.js} +106 -63
- package/dist/chunk-ZZY3IDL2.js.map +1 -0
- package/dist/cli/index.js +37 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/{config-AHHWBME7.js → config-YLMDBCIR.js} +116 -6
- package/dist/config-YLMDBCIR.js.map +1 -0
- package/dist/hooks/post-tool.js +144 -129
- package/dist/hooks/post-tool.js.map +1 -1
- package/dist/{init-V5BIF357.js → init-UDODKO25.js} +12 -16
- package/dist/init-UDODKO25.js.map +1 -0
- package/dist/ollama-YSRRK7LL.js +12 -0
- package/dist/{schema-YEJIXFMK.js → schema-R3THK35H.js} +8 -4
- package/dist/{tracker-4ORSFJQB.js → tracker-Y2G5DW6Y.js} +2 -2
- package/dist/{uninstall-AIH4HVPZ.js → uninstall-5RVTDKTA.js} +3 -3
- package/package.json +3 -2
- package/dist/chunk-2IARGRDK.js.map +0 -1
- package/dist/chunk-2PUO5G3C.js.map +0 -1
- package/dist/chunk-RK7ZFN4W.js +0 -97
- package/dist/chunk-RK7ZFN4W.js.map +0 -1
- package/dist/chunk-Y55I7ZS5.js.map +0 -1
- package/dist/config-AHHWBME7.js.map +0 -1
- package/dist/init-V5BIF357.js.map +0 -1
- package/dist/ollama-V246A374.js +0 -14
- /package/dist/{ollama-V246A374.js.map → ollama-YSRRK7LL.js.map} +0 -0
- /package/dist/{schema-YEJIXFMK.js.map → schema-R3THK35H.js.map} +0 -0
- /package/dist/{tracker-4ORSFJQB.js.map → tracker-Y2G5DW6Y.js.map} +0 -0
- /package/dist/{uninstall-AIH4HVPZ.js.map → uninstall-5RVTDKTA.js.map} +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/flags.ts
|
|
4
|
+
function parseFlags(args) {
|
|
5
|
+
const flags = {};
|
|
6
|
+
const positional = [];
|
|
7
|
+
let i = 0;
|
|
8
|
+
while (i < args.length) {
|
|
9
|
+
const arg = args[i];
|
|
10
|
+
if (arg.startsWith("--")) {
|
|
11
|
+
const eqIdx = arg.indexOf("=");
|
|
12
|
+
if (eqIdx !== -1) {
|
|
13
|
+
const name = arg.slice(2, eqIdx);
|
|
14
|
+
const value = arg.slice(eqIdx + 1);
|
|
15
|
+
flags[name] = value;
|
|
16
|
+
} else {
|
|
17
|
+
flags[arg.slice(2)] = true;
|
|
18
|
+
}
|
|
19
|
+
} else if (arg.startsWith("-") && arg.length === 2) {
|
|
20
|
+
const name = arg.slice(1);
|
|
21
|
+
flags[name] = true;
|
|
22
|
+
} else {
|
|
23
|
+
positional.push(arg);
|
|
24
|
+
}
|
|
25
|
+
i++;
|
|
26
|
+
}
|
|
27
|
+
return { flags, positional };
|
|
28
|
+
}
|
|
29
|
+
function flagBool(flags, ...names) {
|
|
30
|
+
return names.some((n) => flags[n] === true || flags[n] !== void 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
parseFlags,
|
|
35
|
+
flagBool
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=chunk-GEAH6PTG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/flags.ts"],"sourcesContent":["/**\n * Hand-rolled CLI flag parser — no library dependency.\n *\n * Supports:\n * --flag boolean flag (value: true)\n * --flag=value string flag\n * --flag value string flag (next positional arg is consumed as value)\n *\n * Returns:\n * flags — Record of flag name → string | true\n * positional — remaining args that are not flags or flag values\n */\nexport interface ParsedFlags {\n flags: Record<string, string | true>;\n positional: string[];\n}\n\nexport function parseFlags(args: string[]): ParsedFlags {\n const flags: Record<string, string | true> = {};\n const positional: string[] = [];\n\n let i = 0;\n while (i < args.length) {\n const arg = args[i];\n\n if (arg.startsWith(\"--\")) {\n const eqIdx = arg.indexOf(\"=\");\n if (eqIdx !== -1) {\n // --flag=value (only this form captures a string value)\n const name = arg.slice(2, eqIdx);\n const value = arg.slice(eqIdx + 1);\n flags[name] = value;\n } else {\n // --flag (always boolean; no peek-consume to avoid positional ambiguity)\n flags[arg.slice(2)] = true;\n }\n } else if (arg.startsWith(\"-\") && arg.length === 2) {\n // Short flags: -j, -y\n const name = arg.slice(1);\n flags[name] = true;\n } else {\n positional.push(arg);\n }\n\n i++;\n }\n\n return { flags, positional };\n}\n\nexport function flagBool(flags: Record<string, string | true>, ...names: string[]): boolean {\n return names.some((n) => flags[n] === true || flags[n] !== undefined);\n}\n\nexport function flagString(\n flags: Record<string, string | true>,\n name: string\n): string | undefined {\n const v = flags[name];\n return typeof v === \"string\" ? v : undefined;\n}\n"],"mappings":";;;AAiBO,SAAS,WAAW,MAA6B;AACtD,QAAM,QAAuC,CAAC;AAC9C,QAAM,aAAuB,CAAC;AAE9B,MAAI,IAAI;AACR,SAAO,IAAI,KAAK,QAAQ;AACtB,UAAM,MAAM,KAAK,CAAC;AAElB,QAAI,IAAI,WAAW,IAAI,GAAG;AACxB,YAAM,QAAQ,IAAI,QAAQ,GAAG;AAC7B,UAAI,UAAU,IAAI;AAEhB,cAAM,OAAO,IAAI,MAAM,GAAG,KAAK;AAC/B,cAAM,QAAQ,IAAI,MAAM,QAAQ,CAAC;AACjC,cAAM,IAAI,IAAI;AAAA,MAChB,OAAO;AAEL,cAAM,IAAI,MAAM,CAAC,CAAC,IAAI;AAAA,MACxB;AAAA,IACF,WAAW,IAAI,WAAW,GAAG,KAAK,IAAI,WAAW,GAAG;AAElD,YAAM,OAAO,IAAI,MAAM,CAAC;AACxB,YAAM,IAAI,IAAI;AAAA,IAChB,OAAO;AACL,iBAAW,KAAK,GAAG;AAAA,IACrB;AAEA;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,WAAW;AAC7B;AAEO,SAAS,SAAS,UAAyC,OAA0B;AAC1F,SAAO,MAAM,KAAK,CAAC,MAAM,MAAM,CAAC,MAAM,QAAQ,MAAM,CAAC,MAAM,MAAS;AACtE;","names":[]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/config/schema.ts
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
var LANGUAGE_NAMES = {
|
|
9
|
+
en: "English",
|
|
10
|
+
pt: "Portuguese",
|
|
11
|
+
es: "Spanish",
|
|
12
|
+
fr: "French",
|
|
13
|
+
de: "German",
|
|
14
|
+
it: "Italian",
|
|
15
|
+
zh: "Chinese",
|
|
16
|
+
ja: "Japanese",
|
|
17
|
+
ko: "Korean"
|
|
18
|
+
};
|
|
19
|
+
var LEARNER_LEVEL_NAMES = {
|
|
20
|
+
none: "Never programmed",
|
|
21
|
+
beginner: "Just starting out",
|
|
22
|
+
intermediate: "Read code with difficulty",
|
|
23
|
+
regular: "Code regularly"
|
|
24
|
+
};
|
|
25
|
+
var CONFIG_FILENAME = "code-explainer.config.json";
|
|
26
|
+
function getGlobalConfigPath() {
|
|
27
|
+
return join(homedir(), ".code-explainer.config.json");
|
|
28
|
+
}
|
|
29
|
+
var DEFAULT_CONFIG = {
|
|
30
|
+
engine: "ollama",
|
|
31
|
+
ollamaModel: "qwen3.5:4b",
|
|
32
|
+
ollamaUrl: "http://localhost:11434",
|
|
33
|
+
detailLevel: "standard",
|
|
34
|
+
language: "en",
|
|
35
|
+
learnerLevel: "intermediate",
|
|
36
|
+
hooks: {
|
|
37
|
+
edit: true,
|
|
38
|
+
write: true,
|
|
39
|
+
bash: true
|
|
40
|
+
},
|
|
41
|
+
exclude: ["*.lock", "dist/**", "node_modules/**"],
|
|
42
|
+
skipIfSlowMs: 3e4,
|
|
43
|
+
bashFilter: {
|
|
44
|
+
capturePatterns: [
|
|
45
|
+
"rm",
|
|
46
|
+
"mv",
|
|
47
|
+
"cp",
|
|
48
|
+
"mkdir",
|
|
49
|
+
"npm install",
|
|
50
|
+
"pip install",
|
|
51
|
+
"yarn add",
|
|
52
|
+
"pnpm add",
|
|
53
|
+
"chmod",
|
|
54
|
+
"chown",
|
|
55
|
+
"git checkout",
|
|
56
|
+
"git reset",
|
|
57
|
+
"git revert",
|
|
58
|
+
"sed -i"
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var EngineSchema = z.enum(["ollama", "claude"]);
|
|
63
|
+
var DetailLevelSchema = z.enum(["minimal", "standard", "verbose"]);
|
|
64
|
+
var LanguageSchema = z.enum(["en", "pt", "es", "fr", "de", "it", "zh", "ja", "ko"]);
|
|
65
|
+
var LearnerLevelSchema = z.enum(["none", "beginner", "intermediate", "regular"]);
|
|
66
|
+
var HooksConfigSchema = z.object({
|
|
67
|
+
edit: z.boolean().default(true),
|
|
68
|
+
write: z.boolean().default(true),
|
|
69
|
+
bash: z.boolean().default(true)
|
|
70
|
+
}).default({});
|
|
71
|
+
var BashFilterConfigSchema = z.object({
|
|
72
|
+
capturePatterns: z.array(z.string()).default([])
|
|
73
|
+
}).default({});
|
|
74
|
+
var ConfigSchema = z.object({
|
|
75
|
+
engine: EngineSchema.default("ollama"),
|
|
76
|
+
ollamaModel: z.string().min(1).default("qwen3.5:4b"),
|
|
77
|
+
ollamaUrl: z.string().url().default("http://localhost:11434"),
|
|
78
|
+
detailLevel: DetailLevelSchema.default("standard"),
|
|
79
|
+
language: LanguageSchema.default("en"),
|
|
80
|
+
learnerLevel: LearnerLevelSchema.default("intermediate"),
|
|
81
|
+
hooks: HooksConfigSchema,
|
|
82
|
+
exclude: z.array(z.string()).default(["*.lock", "dist/**", "node_modules/**"]),
|
|
83
|
+
skipIfSlowMs: z.coerce.number().int().min(0).default(3e4),
|
|
84
|
+
bashFilter: BashFilterConfigSchema
|
|
85
|
+
});
|
|
86
|
+
function validateConfig(raw) {
|
|
87
|
+
const result = ConfigSchema.safeParse(raw);
|
|
88
|
+
if (result.success) return result.data;
|
|
89
|
+
const issues = result.error.issues.map((i) => ` ${i.path.join(".") || "<root>"}: ${i.message}`).join("\n");
|
|
90
|
+
throw new Error(`[code-explainer] Invalid config:
|
|
91
|
+
${issues}`);
|
|
92
|
+
}
|
|
93
|
+
function mergeConfig(base, overlay) {
|
|
94
|
+
return {
|
|
95
|
+
...base,
|
|
96
|
+
...overlay,
|
|
97
|
+
hooks: { ...base.hooks, ...overlay.hooks ?? {} },
|
|
98
|
+
bashFilter: {
|
|
99
|
+
...base.bashFilter,
|
|
100
|
+
...overlay.bashFilter ?? {}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function tryReadJson(path) {
|
|
105
|
+
if (!existsSync(path)) return null;
|
|
106
|
+
let raw;
|
|
107
|
+
try {
|
|
108
|
+
raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
109
|
+
} catch (err) {
|
|
110
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
111
|
+
throw new Error(`[code-explainer] Config file ${path} is not valid JSON: ${msg}`);
|
|
112
|
+
}
|
|
113
|
+
const partial = ConfigSchema.partial().safeParse(raw);
|
|
114
|
+
if (!partial.success) {
|
|
115
|
+
const issues = partial.error.issues.map((i) => ` ${i.path.join(".") || "<root>"}: ${i.message}`).join("\n");
|
|
116
|
+
throw new Error(`[code-explainer] Invalid config in ${path}:
|
|
117
|
+
${issues}`);
|
|
118
|
+
}
|
|
119
|
+
return partial.data;
|
|
120
|
+
}
|
|
121
|
+
function loadConfig(configPath) {
|
|
122
|
+
const globalConfig = tryReadJson(getGlobalConfigPath());
|
|
123
|
+
const projectConfig = tryReadJson(configPath);
|
|
124
|
+
let result = DEFAULT_CONFIG;
|
|
125
|
+
if (globalConfig) result = mergeConfig(result, globalConfig);
|
|
126
|
+
if (projectConfig) result = mergeConfig(result, projectConfig);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
LANGUAGE_NAMES,
|
|
132
|
+
LEARNER_LEVEL_NAMES,
|
|
133
|
+
CONFIG_FILENAME,
|
|
134
|
+
getGlobalConfigPath,
|
|
135
|
+
DEFAULT_CONFIG,
|
|
136
|
+
ConfigSchema,
|
|
137
|
+
validateConfig,
|
|
138
|
+
loadConfig
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=chunk-GU4Y5ZWY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/schema.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { z } from \"zod\";\n\nexport type Engine = \"ollama\" | \"claude\";\nexport type DetailLevel = \"minimal\" | \"standard\" | \"verbose\";\nexport type RiskLevel = \"none\" | \"low\" | \"medium\" | \"high\";\n\nexport type Language =\n | \"en\"\n | \"pt\"\n | \"es\"\n | \"fr\"\n | \"de\"\n | \"it\"\n | \"zh\"\n | \"ja\"\n | \"ko\";\n\nexport const LANGUAGE_NAMES: Record<Language, string> = {\n en: \"English\",\n pt: \"Portuguese\",\n es: \"Spanish\",\n fr: \"French\",\n de: \"German\",\n it: \"Italian\",\n zh: \"Chinese\",\n ja: \"Japanese\",\n ko: \"Korean\",\n};\n\nexport type LearnerLevel = \"none\" | \"beginner\" | \"intermediate\" | \"regular\";\n\nexport const LEARNER_LEVEL_NAMES: Record<LearnerLevel, string> = {\n none: \"Never programmed\",\n beginner: \"Just starting out\",\n intermediate: \"Read code with difficulty\",\n regular: \"Code regularly\",\n};\n\nexport interface HooksConfig {\n edit: boolean;\n write: boolean;\n bash: boolean;\n}\n\nexport interface BashFilterConfig {\n capturePatterns: string[];\n}\n\nexport interface Config {\n engine: Engine;\n ollamaModel: string;\n ollamaUrl: string;\n detailLevel: DetailLevel;\n language: Language;\n learnerLevel: LearnerLevel;\n hooks: HooksConfig;\n exclude: string[];\n skipIfSlowMs: number;\n bashFilter: BashFilterConfig;\n}\n\nexport interface DeepDiveItem {\n term: string;\n explanation: string;\n}\n\nexport interface ExplanationResult {\n impact: string;\n howItWorks: string;\n why: string;\n deepDive: DeepDiveItem[];\n isSamePattern: boolean;\n samePatternNote: string;\n risk: RiskLevel;\n riskReason: string;\n}\n\nexport interface HookPayload {\n session_id: string;\n transcript_path: string;\n cwd: string;\n permission_mode: string;\n hook_event_name: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n // Claude Code sends this as an object for Edit/Write/MultiEdit and a string\n // for Bash; type it as unknown so consumers validate before use.\n tool_response: unknown;\n}\n\nexport const CONFIG_FILENAME = \"code-explainer.config.json\";\n\nexport function getGlobalConfigPath(): string {\n return join(homedir(), \".code-explainer.config.json\");\n}\n\nexport const DEFAULT_CONFIG: Config = {\n engine: \"ollama\",\n ollamaModel: \"qwen3.5:4b\",\n ollamaUrl: \"http://localhost:11434\",\n detailLevel: \"standard\",\n language: \"en\",\n learnerLevel: \"intermediate\",\n hooks: {\n edit: true,\n write: true,\n bash: true,\n },\n exclude: [\"*.lock\", \"dist/**\", \"node_modules/**\"],\n skipIfSlowMs: 30000,\n bashFilter: {\n capturePatterns: [\n \"rm\",\n \"mv\",\n \"cp\",\n \"mkdir\",\n \"npm install\",\n \"pip install\",\n \"yarn add\",\n \"pnpm add\",\n \"chmod\",\n \"chown\",\n \"git checkout\",\n \"git reset\",\n \"git revert\",\n \"sed -i\",\n ],\n },\n};\n\n// ---------------------------------------------------------------------------\n// Zod schema — used to validate and parse config files at load time.\n// Using z.coerce where sensible so that older config files with slightly\n// different types (e.g., skipIfSlowMs stored as a string) still work.\n// ---------------------------------------------------------------------------\n\nconst EngineSchema = z.enum([\"ollama\", \"claude\"]);\nconst DetailLevelSchema = z.enum([\"minimal\", \"standard\", \"verbose\"]);\nconst LanguageSchema = z.enum([\"en\", \"pt\", \"es\", \"fr\", \"de\", \"it\", \"zh\", \"ja\", \"ko\"]);\nconst LearnerLevelSchema = z.enum([\"none\", \"beginner\", \"intermediate\", \"regular\"]);\n\nconst HooksConfigSchema = z.object({\n edit: z.boolean().default(true),\n write: z.boolean().default(true),\n bash: z.boolean().default(true),\n}).default({});\n\nconst BashFilterConfigSchema = z.object({\n capturePatterns: z.array(z.string()).default([]),\n}).default({});\n\nexport const ConfigSchema = z.object({\n engine: EngineSchema.default(\"ollama\"),\n ollamaModel: z.string().min(1).default(\"qwen3.5:4b\"),\n ollamaUrl: z.string().url().default(\"http://localhost:11434\"),\n detailLevel: DetailLevelSchema.default(\"standard\"),\n language: LanguageSchema.default(\"en\"),\n learnerLevel: LearnerLevelSchema.default(\"intermediate\"),\n hooks: HooksConfigSchema,\n exclude: z.array(z.string()).default([\"*.lock\", \"dist/**\", \"node_modules/**\"]),\n skipIfSlowMs: z.coerce.number().int().min(0).default(30000),\n bashFilter: BashFilterConfigSchema,\n});\n\nexport type ConfigInput = z.input<typeof ConfigSchema>;\n\n/**\n * Validate a raw JSON object against the config schema.\n * Returns a typed Config on success, or throws a ZodError-derived Error with\n * a human-readable message listing all invalid fields.\n */\nexport function validateConfig(raw: unknown): Config {\n const result = ConfigSchema.safeParse(raw);\n if (result.success) return result.data;\n\n const issues = result.error.issues\n .map((i) => ` ${i.path.join(\".\") || \"<root>\"}: ${i.message}`)\n .join(\"\\n\");\n throw new Error(`[code-explainer] Invalid config:\\n${issues}`);\n}\n\nfunction mergeConfig(base: Config, overlay: Partial<Config>): Config {\n return {\n ...base,\n ...overlay,\n hooks: { ...base.hooks, ...(overlay.hooks ?? {}) },\n bashFilter: {\n ...base.bashFilter,\n ...(overlay.bashFilter ?? {}),\n },\n };\n}\n\n/**\n * Read and parse a config file. Returns the partial config or null if the\n * file is missing. Throws if the JSON is malformed or fails schema validation\n * (so callers surface useful errors rather than silently using defaults).\n */\nfunction tryReadJson(path: string): Partial<Config> | null {\n if (!existsSync(path)) return null;\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(path, \"utf-8\"));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`[code-explainer] Config file ${path} is not valid JSON: ${msg}`);\n }\n // Partial validation: only validate keys that are present. Unknown keys are\n // ignored (forward-compat). We re-use ConfigSchema with .partial() so that\n // missing keys fall through to the DEFAULT_CONFIG merger rather than errors.\n const partial = ConfigSchema.partial().safeParse(raw);\n if (!partial.success) {\n const issues = partial.error.issues\n .map((i) => ` ${i.path.join(\".\") || \"<root>\"}: ${i.message}`)\n .join(\"\\n\");\n throw new Error(`[code-explainer] Invalid config in ${path}:\\n${issues}`);\n }\n return partial.data as Partial<Config>;\n}\n\n/**\n * Load config with three-level resolution, most specific first:\n * 1. Project config (passed as configPath) — overrides everything\n * 2. Global user config (~/.code-explainer.config.json)\n * 3. Built-in defaults\n *\n * A project config that lacks a field falls through to the global; a global\n * that lacks a field falls through to defaults. This lets a global install\n * set everyone's defaults while still allowing per-project overrides.\n */\nexport function loadConfig(configPath: string): Config {\n const globalConfig = tryReadJson(getGlobalConfigPath());\n const projectConfig = tryReadJson(configPath);\n\n let result = DEFAULT_CONFIG;\n if (globalConfig) result = mergeConfig(result, globalConfig);\n if (projectConfig) result = mergeConfig(result, projectConfig);\n return result;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -26,10 +26,7 @@ function buildCodeExplainerEntries(hookScriptPath) {
|
|
|
26
26
|
function isCodeExplainerHook(cmd) {
|
|
27
27
|
return cmd.includes(HOOK_MARKER) && cmd.includes("post-tool");
|
|
28
28
|
}
|
|
29
|
-
function
|
|
30
|
-
const claudeDir = join(projectRoot, ".claude");
|
|
31
|
-
const filename = useLocal ? "settings.local.json" : "settings.json";
|
|
32
|
-
const settingsPath = join(claudeDir, filename);
|
|
29
|
+
function mergeHooksAtPath(settingsPath, hookScriptPath) {
|
|
33
30
|
let settings = {};
|
|
34
31
|
let created = false;
|
|
35
32
|
if (existsSync(settingsPath)) {
|
|
@@ -49,8 +46,9 @@ function mergeHooksIntoSettings(projectRoot, hookScriptPath, { useLocal = true }
|
|
|
49
46
|
}
|
|
50
47
|
} else {
|
|
51
48
|
created = true;
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
const parent = dirname(settingsPath);
|
|
50
|
+
if (!existsSync(parent)) {
|
|
51
|
+
mkdirSync(parent, { recursive: true });
|
|
54
52
|
}
|
|
55
53
|
}
|
|
56
54
|
if (!settings.hooks) settings.hooks = {};
|
|
@@ -64,90 +62,7 @@ function mergeHooksIntoSettings(projectRoot, hookScriptPath, { useLocal = true }
|
|
|
64
62
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
65
63
|
return { created, path: settingsPath };
|
|
66
64
|
}
|
|
67
|
-
function
|
|
68
|
-
const candidates = useLocal ? [".claude/settings.local.json", ".claude/settings.json"] : [".claude/settings.json"];
|
|
69
|
-
let removedAny = false;
|
|
70
|
-
let lastPath = null;
|
|
71
|
-
for (const rel of candidates) {
|
|
72
|
-
const path = join(projectRoot, rel);
|
|
73
|
-
if (!existsSync(path)) continue;
|
|
74
|
-
let settings;
|
|
75
|
-
try {
|
|
76
|
-
settings = JSON.parse(readFileSync(path, "utf-8"));
|
|
77
|
-
} catch {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
if (!settings.hooks?.PostToolUse) continue;
|
|
81
|
-
const before = JSON.stringify(settings.hooks.PostToolUse);
|
|
82
|
-
settings.hooks.PostToolUse = settings.hooks.PostToolUse.map((entry) => ({
|
|
83
|
-
...entry,
|
|
84
|
-
hooks: entry.hooks.filter((h) => !isCodeExplainerHook(h.command))
|
|
85
|
-
})).filter((entry) => entry.hooks.length > 0);
|
|
86
|
-
const after = JSON.stringify(settings.hooks.PostToolUse);
|
|
87
|
-
if (before !== after) {
|
|
88
|
-
if (settings.hooks.PostToolUse.length === 0) {
|
|
89
|
-
delete settings.hooks.PostToolUse;
|
|
90
|
-
}
|
|
91
|
-
if (Object.keys(settings.hooks).length === 0) {
|
|
92
|
-
delete settings.hooks;
|
|
93
|
-
}
|
|
94
|
-
writeFileSync(path, JSON.stringify(settings, null, 2) + "\n");
|
|
95
|
-
removedAny = true;
|
|
96
|
-
lastPath = path;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return { removed: removedAny, path: lastPath };
|
|
100
|
-
}
|
|
101
|
-
function mergeHooksIntoUserSettings(hookScriptPath) {
|
|
102
|
-
const userClaudeDir = join(homedir(), ".claude");
|
|
103
|
-
const settingsPath = join(userClaudeDir, "settings.json");
|
|
104
|
-
let settings = {};
|
|
105
|
-
let created = false;
|
|
106
|
-
if (existsSync(settingsPath)) {
|
|
107
|
-
const raw = readFileSync(settingsPath, "utf-8");
|
|
108
|
-
try {
|
|
109
|
-
settings = JSON.parse(raw);
|
|
110
|
-
} catch (err) {
|
|
111
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
112
|
-
throw new Error(
|
|
113
|
-
`[code-explainer] Cannot merge hooks into ${settingsPath}. The file is not valid JSON. Fix: repair the JSON manually or delete the file to regenerate. Original error: ${msg}`
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
if (typeof settings !== "object" || settings === null || Array.isArray(settings)) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
`[code-explainer] Cannot merge hooks into ${settingsPath}. The file does not contain a JSON object at the top level.`
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
created = true;
|
|
123
|
-
if (!existsSync(userClaudeDir)) {
|
|
124
|
-
mkdirSync(userClaudeDir, { recursive: true });
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (!settings.hooks) settings.hooks = {};
|
|
128
|
-
const ourEntries = {
|
|
129
|
-
PostToolUse: [
|
|
130
|
-
{
|
|
131
|
-
matcher: "Edit|Write|MultiEdit",
|
|
132
|
-
hooks: [{ type: "command", command: `node "${hookScriptPath}"` }]
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
matcher: "Bash",
|
|
136
|
-
hooks: [{ type: "command", command: `node "${hookScriptPath}"` }]
|
|
137
|
-
}
|
|
138
|
-
]
|
|
139
|
-
};
|
|
140
|
-
const existingPostTool = settings.hooks.PostToolUse ?? [];
|
|
141
|
-
const cleaned = existingPostTool.map((entry) => ({
|
|
142
|
-
...entry,
|
|
143
|
-
hooks: entry.hooks.filter((h) => !isCodeExplainerHook(h.command))
|
|
144
|
-
})).filter((entry) => entry.hooks.length > 0);
|
|
145
|
-
settings.hooks.PostToolUse = [...cleaned, ...ourEntries.PostToolUse];
|
|
146
|
-
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
147
|
-
return { created, path: settingsPath };
|
|
148
|
-
}
|
|
149
|
-
function removeHooksFromUserSettings() {
|
|
150
|
-
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
65
|
+
function removeHooksAtPath(settingsPath) {
|
|
151
66
|
if (!existsSync(settingsPath)) return { removed: false, path: null };
|
|
152
67
|
let settings;
|
|
153
68
|
try {
|
|
@@ -168,11 +83,36 @@ function removeHooksFromUserSettings() {
|
|
|
168
83
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
169
84
|
return { removed: true, path: settingsPath };
|
|
170
85
|
}
|
|
86
|
+
function mergeHooksIntoSettings(projectRoot, hookScriptPath, { useLocal = true } = {}) {
|
|
87
|
+
const filename = useLocal ? "settings.local.json" : "settings.json";
|
|
88
|
+
const settingsPath = join(projectRoot, ".claude", filename);
|
|
89
|
+
return mergeHooksAtPath(settingsPath, hookScriptPath);
|
|
90
|
+
}
|
|
91
|
+
function mergeHooksIntoUserSettings(hookScriptPath) {
|
|
92
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
93
|
+
return mergeHooksAtPath(settingsPath, hookScriptPath);
|
|
94
|
+
}
|
|
95
|
+
function removeHooksFromSettings(projectRoot, { useLocal = true } = {}) {
|
|
96
|
+
const candidates = useLocal ? [".claude/settings.local.json", ".claude/settings.json"] : [".claude/settings.json"];
|
|
97
|
+
let removedAny = false;
|
|
98
|
+
let lastPath = null;
|
|
99
|
+
for (const rel of candidates) {
|
|
100
|
+
const r = removeHooksAtPath(join(projectRoot, rel));
|
|
101
|
+
if (r.removed) {
|
|
102
|
+
removedAny = true;
|
|
103
|
+
lastPath = r.path;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { removed: removedAny, path: lastPath };
|
|
107
|
+
}
|
|
108
|
+
function removeHooksFromUserSettings() {
|
|
109
|
+
return removeHooksAtPath(join(homedir(), ".claude", "settings.json"));
|
|
110
|
+
}
|
|
171
111
|
|
|
172
112
|
export {
|
|
173
113
|
mergeHooksIntoSettings,
|
|
174
|
-
removeHooksFromSettings,
|
|
175
114
|
mergeHooksIntoUserSettings,
|
|
115
|
+
removeHooksFromSettings,
|
|
176
116
|
removeHooksFromUserSettings
|
|
177
117
|
};
|
|
178
|
-
//# sourceMappingURL=chunk-
|
|
118
|
+
//# sourceMappingURL=chunk-KK76JK7S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/merge.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport const HOOK_MARKER = \"code-explainer\";\n\ninterface HookMatcherEntry {\n matcher: string;\n hooks: Array<{\n type: \"command\";\n command: string;\n }>;\n}\n\ninterface ClaudeSettings {\n hooks?: Record<string, HookMatcherEntry[]>;\n [key: string]: unknown;\n}\n\nfunction buildHookCommand(hookScriptPath: string): string {\n return `node \"${hookScriptPath}\"`;\n}\n\nfunction buildCodeExplainerEntries(hookScriptPath: string): Record<string, HookMatcherEntry[]> {\n const command = buildHookCommand(hookScriptPath);\n return {\n PostToolUse: [\n {\n matcher: \"Edit|Write|MultiEdit\",\n hooks: [{ type: \"command\", command }],\n },\n {\n matcher: \"Bash\",\n hooks: [{ type: \"command\", command }],\n },\n ],\n };\n}\n\nfunction isCodeExplainerHook(cmd: string): boolean {\n return cmd.includes(HOOK_MARKER) && cmd.includes(\"post-tool\");\n}\n\nexport interface MergeResult {\n created: boolean;\n path: string;\n}\n\n/**\n * Core merge: read/parse/merge-and-write at a specific settings path.\n * Creates the parent dir if needed. Preserves all existing hooks and other\n * top-level keys. Idempotent — removes previous code-explainer entries\n * before adding the new ones so re-running does not duplicate.\n *\n * Throws if the existing file is malformed JSON or not a top-level object.\n */\nfunction mergeHooksAtPath(settingsPath: string, hookScriptPath: string): MergeResult {\n let settings: ClaudeSettings = {};\n let created = false;\n\n if (existsSync(settingsPath)) {\n const raw = readFileSync(settingsPath, \"utf-8\");\n try {\n settings = JSON.parse(raw);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[code-explainer] Cannot merge hooks into ${settingsPath}. The file is not valid JSON. Fix: repair the JSON manually (check for trailing commas, unquoted keys) or delete the file to regenerate. Original error: ${msg}`\n );\n }\n if (typeof settings !== \"object\" || settings === null || Array.isArray(settings)) {\n throw new Error(\n `[code-explainer] Cannot merge hooks into ${settingsPath}. The file does not contain a JSON object at the top level. Fix: ensure the file starts with { and ends with }.`\n );\n }\n } else {\n created = true;\n const parent = dirname(settingsPath);\n if (!existsSync(parent)) {\n mkdirSync(parent, { recursive: true });\n }\n }\n\n if (!settings.hooks) settings.hooks = {};\n\n const ourEntries = buildCodeExplainerEntries(hookScriptPath);\n const existingPostTool = settings.hooks.PostToolUse ?? [];\n\n // Remove any previous code-explainer entries to keep idempotency.\n const cleaned = existingPostTool\n .map((entry) => ({\n ...entry,\n hooks: entry.hooks.filter((h) => !isCodeExplainerHook(h.command)),\n }))\n .filter((entry) => entry.hooks.length > 0);\n\n settings.hooks.PostToolUse = [...cleaned, ...ourEntries.PostToolUse];\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\");\n\n return { created, path: settingsPath };\n}\n\n/**\n * Core remove: read/filter/write at a specific settings path.\n * Silently skips missing or malformed files (we don't want to corrupt\n * unrelated user config during an uninstall).\n */\nfunction removeHooksAtPath(settingsPath: string): { removed: boolean; path: string | null } {\n if (!existsSync(settingsPath)) return { removed: false, path: null };\n\n let settings: ClaudeSettings;\n try {\n settings = JSON.parse(readFileSync(settingsPath, \"utf-8\"));\n } catch {\n return { removed: false, path: null };\n }\n\n if (!settings.hooks?.PostToolUse) return { removed: false, path: null };\n\n const before = JSON.stringify(settings.hooks.PostToolUse);\n settings.hooks.PostToolUse = settings.hooks.PostToolUse\n .map((entry) => ({\n ...entry,\n hooks: entry.hooks.filter((h) => !isCodeExplainerHook(h.command)),\n }))\n .filter((entry) => entry.hooks.length > 0);\n const after = JSON.stringify(settings.hooks.PostToolUse);\n\n if (before === after) return { removed: false, path: null };\n\n if (settings.hooks.PostToolUse.length === 0) delete settings.hooks.PostToolUse;\n if (Object.keys(settings.hooks).length === 0) delete settings.hooks;\n\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + \"\\n\");\n return { removed: true, path: settingsPath };\n}\n\n/**\n * Merge code-explainer hooks into a project's .claude/settings[.local].json.\n */\nexport function mergeHooksIntoSettings(\n projectRoot: string,\n hookScriptPath: string,\n { useLocal = true }: { useLocal?: boolean } = {}\n): MergeResult {\n const filename = useLocal ? \"settings.local.json\" : \"settings.json\";\n const settingsPath = join(projectRoot, \".claude\", filename);\n return mergeHooksAtPath(settingsPath, hookScriptPath);\n}\n\n/**\n * Merge code-explainer hooks into the user-level ~/.claude/settings.json,\n * so hooks fire in every project. Used by the global install path.\n */\nexport function mergeHooksIntoUserSettings(hookScriptPath: string): MergeResult {\n const settingsPath = join(homedir(), \".claude\", \"settings.json\");\n return mergeHooksAtPath(settingsPath, hookScriptPath);\n}\n\n/**\n * Remove all code-explainer hook entries from a project's settings files,\n * preserving other hooks and config.\n */\nexport function removeHooksFromSettings(\n projectRoot: string,\n { useLocal = true }: { useLocal?: boolean } = {}\n): { removed: boolean; path: string | null } {\n const candidates = useLocal\n ? [\".claude/settings.local.json\", \".claude/settings.json\"]\n : [\".claude/settings.json\"];\n\n let removedAny = false;\n let lastPath: string | null = null;\n\n for (const rel of candidates) {\n const r = removeHooksAtPath(join(projectRoot, rel));\n if (r.removed) {\n removedAny = true;\n lastPath = r.path;\n }\n }\n\n return { removed: removedAny, path: lastPath };\n}\n\n/**\n * Remove code-explainer hook entries from ~/.claude/settings.json.\n */\nexport function removeHooksFromUserSettings(): { removed: boolean; path: string | null } {\n return removeHooksAtPath(join(homedir(), \".claude\", \"settings.json\"));\n}\n"],"mappings":";;;AAAA,SAAS,YAAY,cAAc,eAAe,iBAAiB;AACnE,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAEvB,IAAM,cAAc;AAe3B,SAAS,iBAAiB,gBAAgC;AACxD,SAAO,SAAS,cAAc;AAChC;AAEA,SAAS,0BAA0B,gBAA4D;AAC7F,QAAM,UAAU,iBAAiB,cAAc;AAC/C,SAAO;AAAA,IACL,aAAa;AAAA,MACX;AAAA,QACE,SAAS;AAAA,QACT,OAAO,CAAC,EAAE,MAAM,WAAW,QAAQ,CAAC;AAAA,MACtC;AAAA,MACA;AAAA,QACE,SAAS;AAAA,QACT,OAAO,CAAC,EAAE,MAAM,WAAW,QAAQ,CAAC;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,oBAAoB,KAAsB;AACjD,SAAO,IAAI,SAAS,WAAW,KAAK,IAAI,SAAS,WAAW;AAC9D;AAeA,SAAS,iBAAiB,cAAsB,gBAAqC;AACnF,MAAI,WAA2B,CAAC;AAChC,MAAI,UAAU;AAEd,MAAI,WAAW,YAAY,GAAG;AAC5B,UAAM,MAAM,aAAa,cAAc,OAAO;AAC9C,QAAI;AACF,iBAAW,KAAK,MAAM,GAAG;AAAA,IAC3B,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAM,IAAI;AAAA,QACR,4CAA4C,YAAY,4JAA4J,GAAG;AAAA,MACzN;AAAA,IACF;AACA,QAAI,OAAO,aAAa,YAAY,aAAa,QAAQ,MAAM,QAAQ,QAAQ,GAAG;AAChF,YAAM,IAAI;AAAA,QACR,4CAA4C,YAAY;AAAA,MAC1D;AAAA,IACF;AAAA,EACF,OAAO;AACL,cAAU;AACV,UAAM,SAAS,QAAQ,YAAY;AACnC,QAAI,CAAC,WAAW,MAAM,GAAG;AACvB,gBAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AAAA,IACvC;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,MAAO,UAAS,QAAQ,CAAC;AAEvC,QAAM,aAAa,0BAA0B,cAAc;AAC3D,QAAM,mBAAmB,SAAS,MAAM,eAAe,CAAC;AAGxD,QAAM,UAAU,iBACb,IAAI,CAAC,WAAW;AAAA,IACf,GAAG;AAAA,IACH,OAAO,MAAM,MAAM,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,OAAO,CAAC;AAAA,EAClE,EAAE,EACD,OAAO,CAAC,UAAU,MAAM,MAAM,SAAS,CAAC;AAE3C,WAAS,MAAM,cAAc,CAAC,GAAG,SAAS,GAAG,WAAW,WAAW;AAEnE,gBAAc,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AAEpE,SAAO,EAAE,SAAS,MAAM,aAAa;AACvC;AAOA,SAAS,kBAAkB,cAAiE;AAC1F,MAAI,CAAC,WAAW,YAAY,EAAG,QAAO,EAAE,SAAS,OAAO,MAAM,KAAK;AAEnE,MAAI;AACJ,MAAI;AACF,eAAW,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAAA,EAC3D,QAAQ;AACN,WAAO,EAAE,SAAS,OAAO,MAAM,KAAK;AAAA,EACtC;AAEA,MAAI,CAAC,SAAS,OAAO,YAAa,QAAO,EAAE,SAAS,OAAO,MAAM,KAAK;AAEtE,QAAM,SAAS,KAAK,UAAU,SAAS,MAAM,WAAW;AACxD,WAAS,MAAM,cAAc,SAAS,MAAM,YACzC,IAAI,CAAC,WAAW;AAAA,IACf,GAAG;AAAA,IACH,OAAO,MAAM,MAAM,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,OAAO,CAAC;AAAA,EAClE,EAAE,EACD,OAAO,CAAC,UAAU,MAAM,MAAM,SAAS,CAAC;AAC3C,QAAM,QAAQ,KAAK,UAAU,SAAS,MAAM,WAAW;AAEvD,MAAI,WAAW,MAAO,QAAO,EAAE,SAAS,OAAO,MAAM,KAAK;AAE1D,MAAI,SAAS,MAAM,YAAY,WAAW,EAAG,QAAO,SAAS,MAAM;AACnE,MAAI,OAAO,KAAK,SAAS,KAAK,EAAE,WAAW,EAAG,QAAO,SAAS;AAE9D,gBAAc,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AACpE,SAAO,EAAE,SAAS,MAAM,MAAM,aAAa;AAC7C;AAKO,SAAS,uBACd,aACA,gBACA,EAAE,WAAW,KAAK,IAA4B,CAAC,GAClC;AACb,QAAM,WAAW,WAAW,wBAAwB;AACpD,QAAM,eAAe,KAAK,aAAa,WAAW,QAAQ;AAC1D,SAAO,iBAAiB,cAAc,cAAc;AACtD;AAMO,SAAS,2BAA2B,gBAAqC;AAC9E,QAAM,eAAe,KAAK,QAAQ,GAAG,WAAW,eAAe;AAC/D,SAAO,iBAAiB,cAAc,cAAc;AACtD;AAMO,SAAS,wBACd,aACA,EAAE,WAAW,KAAK,IAA4B,CAAC,GACJ;AAC3C,QAAM,aAAa,WACf,CAAC,+BAA+B,uBAAuB,IACvD,CAAC,uBAAuB;AAE5B,MAAI,aAAa;AACjB,MAAI,WAA0B;AAE9B,aAAW,OAAO,YAAY;AAC5B,UAAM,IAAI,kBAAkB,KAAK,aAAa,GAAG,CAAC;AAClD,QAAI,EAAE,SAAS;AACb,mBAAa;AACb,iBAAW,EAAE;AAAA,IACf;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,YAAY,MAAM,SAAS;AAC/C;AAKO,SAAS,8BAAyE;AACvF,SAAO,kBAAkB,KAAK,QAAQ,GAAG,WAAW,eAAe,CAAC;AACtE;","names":[]}
|