vibe-code-explainer 0.1.1 → 0.1.2

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/cli/index.js CHANGED
@@ -6,7 +6,7 @@ var command = args[0];
6
6
  async function main() {
7
7
  switch (command) {
8
8
  case "init": {
9
- const { runInit } = await import("../init-RE4ENXBR.js");
9
+ const { runInit } = await import("../init-ORTBGNSE.js");
10
10
  await runInit(args.slice(1));
11
11
  break;
12
12
  }
@@ -22,7 +22,7 @@ import { intro, outro, select, confirm, cancel, isCancel, spinner, note } from "
22
22
  import pc from "picocolors";
23
23
  import { execFileSync, spawn } from "child_process";
24
24
  import { existsSync, writeFileSync } from "fs";
25
- import { join, resolve } from "path";
25
+ import { join, resolve, basename } from "path";
26
26
  import { fileURLToPath } from "url";
27
27
 
28
28
  // src/detect/platform.ts
@@ -58,10 +58,37 @@ function ollamaInstallCommand(p) {
58
58
 
59
59
  // src/cli/init.ts
60
60
  var CONFIG_FILE = "code-explainer.config.json";
61
- function resolveHookScriptPath() {
61
+ function isInsideNpxCache(path) {
62
+ const norm = path.replace(/\\/g, "/").toLowerCase();
63
+ return norm.includes("/_npx/") || norm.includes("/.npm/_npx/");
64
+ }
65
+ async function ensureLocalInstall(projectRoot) {
62
66
  const thisFile = fileURLToPath(import.meta.url);
63
- const distDir = resolve(thisFile, "..", "..");
64
- return join(distDir, "hooks", "post-tool.js");
67
+ if (!isInsideNpxCache(thisFile)) {
68
+ const distDir = resolve(thisFile, "..");
69
+ return join(distDir, "hooks", "post-tool.js");
70
+ }
71
+ note(
72
+ "Installing vibe-code-explainer as a dev dependency so the hook path is stable...",
73
+ "Local install"
74
+ );
75
+ const pkgPath = join(projectRoot, "package.json");
76
+ if (!existsSync(pkgPath)) {
77
+ writeFileSync(pkgPath, JSON.stringify({ name: basename(projectRoot), private: true, version: "0.0.0" }, null, 2) + "\n");
78
+ }
79
+ await new Promise((resolvePromise, rejectPromise) => {
80
+ const child = spawn(
81
+ "npm",
82
+ ["install", "--save-dev", "vibe-code-explainer"],
83
+ { stdio: "inherit", cwd: projectRoot, shell: process.platform === "win32" }
84
+ );
85
+ child.on("error", rejectPromise);
86
+ child.on("close", (code) => {
87
+ if (code === 0) resolvePromise();
88
+ else rejectPromise(new Error(`npm install exited with code ${code}`));
89
+ });
90
+ });
91
+ return join(projectRoot, "node_modules", "vibe-code-explainer", "dist", "hooks", "post-tool.js");
65
92
  }
66
93
  async function checkOllama() {
67
94
  try {
@@ -237,7 +264,7 @@ Start it with: ${pc.cyan("ollama serve")} (in a separate terminal).`,
237
264
  }
238
265
  }
239
266
  writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
240
- const hookScript = resolveHookScriptPath();
267
+ const hookScript = await ensureLocalInstall(projectRoot);
241
268
  const mergeResult = mergeHooksIntoSettings(projectRoot, hookScript);
242
269
  note(
243
270
  `${pc.green("\u2713")} Wrote ${pc.cyan(CONFIG_FILE)}
@@ -254,4 +281,4 @@ ${pc.green("\u2713")} ${mergeResult.created ? "Created" : "Updated"} ${pc.cyan(m
254
281
  export {
255
282
  runInit
256
283
  };
257
- //# sourceMappingURL=init-RE4ENXBR.js.map
284
+ //# sourceMappingURL=init-ORTBGNSE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/init.ts","../src/detect/platform.ts"],"sourcesContent":["import { intro, outro, select, confirm, cancel, isCancel, spinner, note } from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { execFile, execFileSync, spawn } from \"node:child_process\";\nimport { existsSync, writeFileSync } from \"node:fs\";\nimport { join, resolve, basename } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DEFAULT_CONFIG, type Config, type Engine, type DetailLevel } from \"../config/schema.js\";\nimport { detectPlatform, ollamaInstallCommand } from \"../detect/platform.js\";\nimport { detectNvidiaVram, pickModelForVram, MODEL_OPTIONS } from \"../detect/vram.js\";\nimport { mergeHooksIntoSettings } from \"../config/merge.js\";\nimport { callOllama } from \"../engines/ollama.js\";\n\nconst CONFIG_FILE = \"code-explainer.config.json\";\n\nfunction isInsideNpxCache(path: string): boolean {\n const norm = path.replace(/\\\\/g, \"/\").toLowerCase();\n return norm.includes(\"/_npx/\") || norm.includes(\"/.npm/_npx/\");\n}\n\nasync function ensureLocalInstall(projectRoot: string): Promise<string> {\n // If we're running from the npx temp cache, that path disappears after the\n // cache is cleaned. Install the package into the project so the hook path\n // is stable across runs.\n const thisFile = fileURLToPath(import.meta.url);\n\n if (!isInsideNpxCache(thisFile)) {\n // Running from an already-installed copy (local or global). Use it directly.\n const distDir = resolve(thisFile, \"..\");\n return join(distDir, \"hooks\", \"post-tool.js\");\n }\n\n note(\n \"Installing vibe-code-explainer as a dev dependency so the hook path is stable...\",\n \"Local install\"\n );\n\n // Make sure there's a package.json so npm install has something to attach to.\n const pkgPath = join(projectRoot, \"package.json\");\n if (!existsSync(pkgPath)) {\n writeFileSync(pkgPath, JSON.stringify({ name: basename(projectRoot), private: true, version: \"0.0.0\" }, null, 2) + \"\\n\");\n }\n\n await new Promise<void>((resolvePromise, rejectPromise) => {\n const child = spawn(\n \"npm\",\n [\"install\", \"--save-dev\", \"vibe-code-explainer\"],\n { stdio: \"inherit\", cwd: projectRoot, shell: process.platform === \"win32\" }\n );\n child.on(\"error\", rejectPromise);\n child.on(\"close\", (code) => {\n if (code === 0) resolvePromise();\n else rejectPromise(new Error(`npm install exited with code ${code}`));\n });\n });\n\n return join(projectRoot, \"node_modules\", \"vibe-code-explainer\", \"dist\", \"hooks\", \"post-tool.js\");\n}\n\nasync function checkOllama(): Promise<\"running\" | \"installed-not-running\" | \"missing\"> {\n // First, try the HTTP endpoint.\n try {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), 1500);\n const res = await fetch(\"http://localhost:11434/api/tags\", { signal: ctrl.signal });\n clearTimeout(timer);\n if (res.ok) return \"running\";\n } catch {\n // fall through\n }\n\n // Check if the binary exists.\n try {\n execFileSync(\"ollama\", [\"--version\"], { stdio: \"ignore\" });\n return \"installed-not-running\";\n } catch {\n return \"missing\";\n }\n}\n\nasync function pullModel(model: string): Promise<boolean> {\n // Show note first so the user knows what's happening, then stream ollama's\n // own progress bar straight to the terminal by inheriting stdio.\n note(\n `Pulling ${pc.cyan(model)}\\n${pc.dim(\"This can take a while on the first run (several GB download).\")}`,\n \"Downloading model\"\n );\n\n return new Promise((resolvePromise) => {\n const child = spawn(\"ollama\", [\"pull\", model], { stdio: \"inherit\" });\n child.on(\"error\", () => {\n process.stderr.write(pc.red(\"\\nFailed to run `ollama pull`. Make sure Ollama is running.\\n\"));\n resolvePromise(false);\n });\n child.on(\"close\", (code) => {\n if (code === 0) {\n process.stdout.write(pc.green(`\\n\\u2713 Pulled ${model}\\n`));\n resolvePromise(true);\n } else {\n process.stderr.write(pc.red(`\\n\\u2717 ollama pull exited with code ${code}\\n`));\n resolvePromise(false);\n }\n });\n });\n}\n\nasync function runWarmup(config: Config): Promise<void> {\n const s = spinner();\n s.start(`Warming up ${config.ollamaModel}`);\n\n const outcome = await callOllama({\n filePath: \"warmup.txt\",\n diff: \"+ hello world\",\n config: { ...config, skipIfSlowMs: 60000 },\n });\n\n if (outcome.kind === \"ok\") {\n s.stop(\"Warmup complete. First real explanation will be fast.\");\n } else if (outcome.kind === \"error\") {\n s.stop(`Warmup failed: ${outcome.problem}`);\n } else {\n s.stop(`Warmup skipped: ${outcome.reason}`);\n }\n}\n\nasync function pickModel(): Promise<string | symbol> {\n const vram = detectNvidiaVram();\n if (vram) {\n const recommended = pickModelForVram(vram.totalMb);\n note(\n `Detected ${pc.green(vram.gpuName)} with ${pc.green(`${Math.round(vram.totalMb / 1024)} GB VRAM`)}.\\nRecommended model: ${pc.cyan(recommended)}`,\n \"GPU detection\"\n );\n return recommended;\n }\n\n // No auto-detect → show chooser with VRAM hints.\n note(\"No NVIDIA GPU detected (or nvidia-smi unavailable). Pick a model that fits your machine.\", \"GPU detection\");\n const choice = await select({\n message: \"Which model should code-explainer use?\",\n options: MODEL_OPTIONS.map((m) => ({\n label: m.label,\n value: m.model,\n hint: m.hint,\n })),\n initialValue: \"qwen3-coder:30b\",\n });\n return choice;\n}\n\nfunction handleCancel<T>(value: T | symbol): asserts value is T {\n if (isCancel(value)) {\n cancel(\"Setup cancelled.\");\n process.exit(0);\n }\n}\n\nexport async function runInit(args: string[]): Promise<void> {\n const skipWarmup = args.includes(\"--skip-warmup\");\n\n intro(pc.bold(\"code-explainer setup\"));\n\n const engineChoice = await select<Engine>({\n message: \"Which explanation engine do you want to use?\",\n options: [\n { label: \"Local LLM (Ollama)\", value: \"ollama\", hint: \"free, private, works offline\" },\n { label: \"Claude Code (native)\", value: \"claude\", hint: \"best quality, uses API tokens\" },\n ],\n initialValue: \"ollama\",\n });\n handleCancel(engineChoice);\n\n const detailChoice = await select<DetailLevel>({\n message: \"How detailed should explanations be?\",\n options: [\n { label: \"Standard\", value: \"standard\", hint: \"1-2 sentence explanation per change (recommended)\" },\n { label: \"Minimal\", value: \"minimal\", hint: \"one short sentence per change\" },\n { label: \"Verbose\", value: \"verbose\", hint: \"detailed bullet-point breakdown\" },\n ],\n initialValue: \"standard\",\n });\n handleCancel(detailChoice);\n\n let ollamaModel = DEFAULT_CONFIG.ollamaModel;\n\n if (engineChoice === \"ollama\") {\n const status = await checkOllama();\n\n if (status === \"missing\") {\n const platform = detectPlatform();\n const installCmd = ollamaInstallCommand(platform);\n note(\n `Ollama is not installed.\\nInstall with: ${pc.cyan(installCmd)}\\nOr visit: ${pc.cyan(\"https://ollama.com/download\")}`,\n \"Missing prerequisite\"\n );\n const proceed = await confirm({\n message: \"Install Ollama manually and continue after it's ready?\",\n initialValue: true,\n });\n handleCancel(proceed);\n if (!proceed) {\n cancel(\"Setup paused. Run 'npx vibe-code-explainer init' again after installing Ollama.\");\n process.exit(0);\n }\n } else if (status === \"installed-not-running\") {\n note(\n `Ollama is installed but the service isn't running.\\nStart it with: ${pc.cyan(\"ollama serve\")} (in a separate terminal).`,\n \"Ollama not running\"\n );\n }\n\n const modelChoice = await pickModel();\n handleCancel(modelChoice);\n ollamaModel = modelChoice;\n\n const pullOk = await pullModel(ollamaModel);\n if (!pullOk) {\n const skipPull = await confirm({\n message: \"Continue without pulling the model? (You'll need to run 'ollama pull' manually.)\",\n initialValue: false,\n });\n handleCancel(skipPull);\n if (!skipPull) {\n cancel(\"Setup aborted.\");\n process.exit(1);\n }\n }\n }\n\n // Build config.\n const config: Config = {\n ...DEFAULT_CONFIG,\n engine: engineChoice,\n detailLevel: detailChoice,\n ollamaModel,\n };\n\n const projectRoot = process.cwd();\n const configPath = join(projectRoot, CONFIG_FILE);\n\n if (existsSync(configPath)) {\n const overwrite = await confirm({\n message: `${CONFIG_FILE} already exists. Overwrite?`,\n initialValue: false,\n });\n handleCancel(overwrite);\n if (!overwrite) {\n cancel(\"Setup aborted to avoid overwriting existing config.\");\n process.exit(0);\n }\n }\n\n writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\");\n\n // Merge hooks into settings.local.json.\n const hookScript = await ensureLocalInstall(projectRoot);\n const mergeResult = mergeHooksIntoSettings(projectRoot, hookScript);\n\n note(\n `${pc.green(\"\\u2713\")} Wrote ${pc.cyan(CONFIG_FILE)}\\n${pc.green(\"\\u2713\")} ${mergeResult.created ? \"Created\" : \"Updated\"} ${pc.cyan(mergeResult.path)}`,\n \"Configuration saved\"\n );\n\n // Warmup.\n if (engineChoice === \"ollama\" && !skipWarmup) {\n await runWarmup(config);\n }\n\n outro(\n pc.bold(\"code-explainer is active.\") +\n \"\\nClaude Code will now explain every Edit, Write, and destructive Bash command.\"\n );\n}\n","import { platform } from \"node:os\";\n\nexport type Platform = \"windows\" | \"macos\" | \"linux\" | \"wsl\" | \"unknown\";\n\nexport function detectPlatform(): Platform {\n const p = platform();\n if (p === \"win32\") return \"windows\";\n if (p === \"darwin\") return \"macos\";\n if (p === \"linux\") {\n // WSL detection: Microsoft string in /proc/version\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const fs = require(\"node:fs\") as typeof import(\"node:fs\");\n const release = fs.readFileSync(\"/proc/version\", \"utf-8\").toLowerCase();\n if (release.includes(\"microsoft\") || release.includes(\"wsl\")) return \"wsl\";\n } catch {\n // ignore\n }\n return \"linux\";\n }\n return \"unknown\";\n}\n\nexport function ollamaInstallCommand(p: Platform): string {\n switch (p) {\n case \"macos\":\n return \"brew install ollama\";\n case \"windows\":\n return \"winget install Ollama.Ollama\";\n case \"linux\":\n case \"wsl\":\n return \"curl -fsSL https://ollama.com/install.sh | sh\";\n default:\n return \"Visit https://ollama.com/download to install Ollama\";\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,OAAO,QAAQ,SAAS,QAAQ,UAAU,SAAS,YAAY;AAC/E,OAAO,QAAQ;AACf,SAAmB,cAAc,aAAa;AAC9C,SAAS,YAAY,qBAAqB;AAC1C,SAAS,MAAM,SAAS,gBAAgB;AACxC,SAAS,qBAAqB;;;ACL9B,SAAS,gBAAgB;AAIlB,SAAS,iBAA2B;AACzC,QAAM,IAAI,SAAS;AACnB,MAAI,MAAM,QAAS,QAAO;AAC1B,MAAI,MAAM,SAAU,QAAO;AAC3B,MAAI,MAAM,SAAS;AAEjB,QAAI;AAEF,YAAM,KAAK,UAAQ,IAAS;AAC5B,YAAM,UAAU,GAAG,aAAa,iBAAiB,OAAO,EAAE,YAAY;AACtE,UAAI,QAAQ,SAAS,WAAW,KAAK,QAAQ,SAAS,KAAK,EAAG,QAAO;AAAA,IACvE,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,GAAqB;AACxD,UAAQ,GAAG;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;;;ADvBA,IAAM,cAAc;AAEpB,SAAS,iBAAiB,MAAuB;AAC/C,QAAM,OAAO,KAAK,QAAQ,OAAO,GAAG,EAAE,YAAY;AAClD,SAAO,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,aAAa;AAC/D;AAEA,eAAe,mBAAmB,aAAsC;AAItE,QAAM,WAAW,cAAc,YAAY,GAAG;AAE9C,MAAI,CAAC,iBAAiB,QAAQ,GAAG;AAE/B,UAAM,UAAU,QAAQ,UAAU,IAAI;AACtC,WAAO,KAAK,SAAS,SAAS,cAAc;AAAA,EAC9C;AAEA;AAAA,IACE;AAAA,IACA;AAAA,EACF;AAGA,QAAM,UAAU,KAAK,aAAa,cAAc;AAChD,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,kBAAc,SAAS,KAAK,UAAU,EAAE,MAAM,SAAS,WAAW,GAAG,SAAS,MAAM,SAAS,QAAQ,GAAG,MAAM,CAAC,IAAI,IAAI;AAAA,EACzH;AAEA,QAAM,IAAI,QAAc,CAAC,gBAAgB,kBAAkB;AACzD,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA,CAAC,WAAW,cAAc,qBAAqB;AAAA,MAC/C,EAAE,OAAO,WAAW,KAAK,aAAa,OAAO,QAAQ,aAAa,QAAQ;AAAA,IAC5E;AACA,UAAM,GAAG,SAAS,aAAa;AAC/B,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,UAAI,SAAS,EAAG,gBAAe;AAAA,UAC1B,eAAc,IAAI,MAAM,gCAAgC,IAAI,EAAE,CAAC;AAAA,IACtE,CAAC;AAAA,EACH,CAAC;AAED,SAAO,KAAK,aAAa,gBAAgB,uBAAuB,QAAQ,SAAS,cAAc;AACjG;AAEA,eAAe,cAAwE;AAErF,MAAI;AACF,UAAM,OAAO,IAAI,gBAAgB;AACjC,UAAM,QAAQ,WAAW,MAAM,KAAK,MAAM,GAAG,IAAI;AACjD,UAAM,MAAM,MAAM,MAAM,mCAAmC,EAAE,QAAQ,KAAK,OAAO,CAAC;AAClF,iBAAa,KAAK;AAClB,QAAI,IAAI,GAAI,QAAO;AAAA,EACrB,QAAQ;AAAA,EAER;AAGA,MAAI;AACF,iBAAa,UAAU,CAAC,WAAW,GAAG,EAAE,OAAO,SAAS,CAAC;AACzD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,UAAU,OAAiC;AAGxD;AAAA,IACE,WAAW,GAAG,KAAK,KAAK,CAAC;AAAA,EAAK,GAAG,IAAI,+DAA+D,CAAC;AAAA,IACrG;AAAA,EACF;AAEA,SAAO,IAAI,QAAQ,CAAC,mBAAmB;AACrC,UAAM,QAAQ,MAAM,UAAU,CAAC,QAAQ,KAAK,GAAG,EAAE,OAAO,UAAU,CAAC;AACnE,UAAM,GAAG,SAAS,MAAM;AACtB,cAAQ,OAAO,MAAM,GAAG,IAAI,+DAA+D,CAAC;AAC5F,qBAAe,KAAK;AAAA,IACtB,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,UAAI,SAAS,GAAG;AACd,gBAAQ,OAAO,MAAM,GAAG,MAAM;AAAA,gBAAmB,KAAK;AAAA,CAAI,CAAC;AAC3D,uBAAe,IAAI;AAAA,MACrB,OAAO;AACL,gBAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,sCAAyC,IAAI;AAAA,CAAI,CAAC;AAC9E,uBAAe,KAAK;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAe,UAAU,QAA+B;AACtD,QAAM,IAAI,QAAQ;AAClB,IAAE,MAAM,cAAc,OAAO,WAAW,EAAE;AAE1C,QAAM,UAAU,MAAM,WAAW;AAAA,IAC/B,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ,EAAE,GAAG,QAAQ,cAAc,IAAM;AAAA,EAC3C,CAAC;AAED,MAAI,QAAQ,SAAS,MAAM;AACzB,MAAE,KAAK,uDAAuD;AAAA,EAChE,WAAW,QAAQ,SAAS,SAAS;AACnC,MAAE,KAAK,kBAAkB,QAAQ,OAAO,EAAE;AAAA,EAC5C,OAAO;AACL,MAAE,KAAK,mBAAmB,QAAQ,MAAM,EAAE;AAAA,EAC5C;AACF;AAEA,eAAe,YAAsC;AACnD,QAAM,OAAO,iBAAiB;AAC9B,MAAI,MAAM;AACR,UAAM,cAAc,iBAAiB,KAAK,OAAO;AACjD;AAAA,MACE,YAAY,GAAG,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,MAAM,GAAG,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC,UAAU,CAAC;AAAA,qBAAyB,GAAG,KAAK,WAAW,CAAC;AAAA,MAC9I;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAGA,OAAK,4FAA4F,eAAe;AAChH,QAAM,SAAS,MAAM,OAAO;AAAA,IAC1B,SAAS;AAAA,IACT,SAAS,cAAc,IAAI,CAAC,OAAO;AAAA,MACjC,OAAO,EAAE;AAAA,MACT,OAAO,EAAE;AAAA,MACT,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,IACF,cAAc;AAAA,EAChB,CAAC;AACD,SAAO;AACT;AAEA,SAAS,aAAgB,OAAuC;AAC9D,MAAI,SAAS,KAAK,GAAG;AACnB,WAAO,kBAAkB;AACzB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAsB,QAAQ,MAA+B;AAC3D,QAAM,aAAa,KAAK,SAAS,eAAe;AAEhD,QAAM,GAAG,KAAK,sBAAsB,CAAC;AAErC,QAAM,eAAe,MAAM,OAAe;AAAA,IACxC,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,OAAO,sBAAsB,OAAO,UAAU,MAAM,+BAA+B;AAAA,MACrF,EAAE,OAAO,wBAAwB,OAAO,UAAU,MAAM,gCAAgC;AAAA,IAC1F;AAAA,IACA,cAAc;AAAA,EAChB,CAAC;AACD,eAAa,YAAY;AAEzB,QAAM,eAAe,MAAM,OAAoB;AAAA,IAC7C,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,OAAO,YAAY,OAAO,YAAY,MAAM,oDAAoD;AAAA,MAClG,EAAE,OAAO,WAAW,OAAO,WAAW,MAAM,gCAAgC;AAAA,MAC5E,EAAE,OAAO,WAAW,OAAO,WAAW,MAAM,kCAAkC;AAAA,IAChF;AAAA,IACA,cAAc;AAAA,EAChB,CAAC;AACD,eAAa,YAAY;AAEzB,MAAI,cAAc,eAAe;AAEjC,MAAI,iBAAiB,UAAU;AAC7B,UAAM,SAAS,MAAM,YAAY;AAEjC,QAAI,WAAW,WAAW;AACxB,YAAMA,YAAW,eAAe;AAChC,YAAM,aAAa,qBAAqBA,SAAQ;AAChD;AAAA,QACE;AAAA,gBAA2C,GAAG,KAAK,UAAU,CAAC;AAAA,YAAe,GAAG,KAAK,6BAA6B,CAAC;AAAA,QACnH;AAAA,MACF;AACA,YAAM,UAAU,MAAM,QAAQ;AAAA,QAC5B,SAAS;AAAA,QACT,cAAc;AAAA,MAChB,CAAC;AACD,mBAAa,OAAO;AACpB,UAAI,CAAC,SAAS;AACZ,eAAO,iFAAiF;AACxF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,WAAW,WAAW,yBAAyB;AAC7C;AAAA,QACE;AAAA,iBAAsE,GAAG,KAAK,cAAc,CAAC;AAAA,QAC7F;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAAc,MAAM,UAAU;AACpC,iBAAa,WAAW;AACxB,kBAAc;AAEd,UAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,QAAI,CAAC,QAAQ;AACX,YAAM,WAAW,MAAM,QAAQ;AAAA,QAC7B,SAAS;AAAA,QACT,cAAc;AAAA,MAChB,CAAC;AACD,mBAAa,QAAQ;AACrB,UAAI,CAAC,UAAU;AACb,eAAO,gBAAgB;AACvB,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAAiB;AAAA,IACrB,GAAG;AAAA,IACH,QAAQ;AAAA,IACR,aAAa;AAAA,IACb;AAAA,EACF;AAEA,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,aAAa,KAAK,aAAa,WAAW;AAEhD,MAAI,WAAW,UAAU,GAAG;AAC1B,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,SAAS,GAAG,WAAW;AAAA,MACvB,cAAc;AAAA,IAChB,CAAC;AACD,iBAAa,SAAS;AACtB,QAAI,CAAC,WAAW;AACd,aAAO,qDAAqD;AAC5D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,gBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,IAAI;AAGhE,QAAM,aAAa,MAAM,mBAAmB,WAAW;AACvD,QAAM,cAAc,uBAAuB,aAAa,UAAU;AAElE;AAAA,IACE,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,GAAG,KAAK,WAAW,CAAC;AAAA,EAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,YAAY,UAAU,YAAY,SAAS,IAAI,GAAG,KAAK,YAAY,IAAI,CAAC;AAAA,IACtJ;AAAA,EACF;AAGA,MAAI,iBAAiB,YAAY,CAAC,YAAY;AAC5C,UAAM,UAAU,MAAM;AAAA,EACxB;AAEA;AAAA,IACE,GAAG,KAAK,2BAA2B,IACjC;AAAA,EACJ;AACF;","names":["platform"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-code-explainer",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Real-time diff explanations for vibe coders using Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/init.ts","../src/detect/platform.ts"],"sourcesContent":["import { intro, outro, select, confirm, cancel, isCancel, spinner, note } from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { execFile, execFileSync, spawn } from \"node:child_process\";\nimport { existsSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DEFAULT_CONFIG, type Config, type Engine, type DetailLevel } from \"../config/schema.js\";\nimport { detectPlatform, ollamaInstallCommand } from \"../detect/platform.js\";\nimport { detectNvidiaVram, pickModelForVram, MODEL_OPTIONS } from \"../detect/vram.js\";\nimport { mergeHooksIntoSettings } from \"../config/merge.js\";\nimport { callOllama } from \"../engines/ollama.js\";\n\nconst CONFIG_FILE = \"code-explainer.config.json\";\n\nfunction resolveHookScriptPath(): string {\n // The compiled dist/hooks/post-tool.js lives next to dist/cli/index.js.\n const thisFile = fileURLToPath(import.meta.url);\n const distDir = resolve(thisFile, \"..\", \"..\");\n return join(distDir, \"hooks\", \"post-tool.js\");\n}\n\nasync function checkOllama(): Promise<\"running\" | \"installed-not-running\" | \"missing\"> {\n // First, try the HTTP endpoint.\n try {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), 1500);\n const res = await fetch(\"http://localhost:11434/api/tags\", { signal: ctrl.signal });\n clearTimeout(timer);\n if (res.ok) return \"running\";\n } catch {\n // fall through\n }\n\n // Check if the binary exists.\n try {\n execFileSync(\"ollama\", [\"--version\"], { stdio: \"ignore\" });\n return \"installed-not-running\";\n } catch {\n return \"missing\";\n }\n}\n\nasync function pullModel(model: string): Promise<boolean> {\n // Show note first so the user knows what's happening, then stream ollama's\n // own progress bar straight to the terminal by inheriting stdio.\n note(\n `Pulling ${pc.cyan(model)}\\n${pc.dim(\"This can take a while on the first run (several GB download).\")}`,\n \"Downloading model\"\n );\n\n return new Promise((resolvePromise) => {\n const child = spawn(\"ollama\", [\"pull\", model], { stdio: \"inherit\" });\n child.on(\"error\", () => {\n process.stderr.write(pc.red(\"\\nFailed to run `ollama pull`. Make sure Ollama is running.\\n\"));\n resolvePromise(false);\n });\n child.on(\"close\", (code) => {\n if (code === 0) {\n process.stdout.write(pc.green(`\\n\\u2713 Pulled ${model}\\n`));\n resolvePromise(true);\n } else {\n process.stderr.write(pc.red(`\\n\\u2717 ollama pull exited with code ${code}\\n`));\n resolvePromise(false);\n }\n });\n });\n}\n\nasync function runWarmup(config: Config): Promise<void> {\n const s = spinner();\n s.start(`Warming up ${config.ollamaModel}`);\n\n const outcome = await callOllama({\n filePath: \"warmup.txt\",\n diff: \"+ hello world\",\n config: { ...config, skipIfSlowMs: 60000 },\n });\n\n if (outcome.kind === \"ok\") {\n s.stop(\"Warmup complete. First real explanation will be fast.\");\n } else if (outcome.kind === \"error\") {\n s.stop(`Warmup failed: ${outcome.problem}`);\n } else {\n s.stop(`Warmup skipped: ${outcome.reason}`);\n }\n}\n\nasync function pickModel(): Promise<string | symbol> {\n const vram = detectNvidiaVram();\n if (vram) {\n const recommended = pickModelForVram(vram.totalMb);\n note(\n `Detected ${pc.green(vram.gpuName)} with ${pc.green(`${Math.round(vram.totalMb / 1024)} GB VRAM`)}.\\nRecommended model: ${pc.cyan(recommended)}`,\n \"GPU detection\"\n );\n return recommended;\n }\n\n // No auto-detect → show chooser with VRAM hints.\n note(\"No NVIDIA GPU detected (or nvidia-smi unavailable). Pick a model that fits your machine.\", \"GPU detection\");\n const choice = await select({\n message: \"Which model should code-explainer use?\",\n options: MODEL_OPTIONS.map((m) => ({\n label: m.label,\n value: m.model,\n hint: m.hint,\n })),\n initialValue: \"qwen3-coder:30b\",\n });\n return choice;\n}\n\nfunction handleCancel<T>(value: T | symbol): asserts value is T {\n if (isCancel(value)) {\n cancel(\"Setup cancelled.\");\n process.exit(0);\n }\n}\n\nexport async function runInit(args: string[]): Promise<void> {\n const skipWarmup = args.includes(\"--skip-warmup\");\n\n intro(pc.bold(\"code-explainer setup\"));\n\n const engineChoice = await select<Engine>({\n message: \"Which explanation engine do you want to use?\",\n options: [\n { label: \"Local LLM (Ollama)\", value: \"ollama\", hint: \"free, private, works offline\" },\n { label: \"Claude Code (native)\", value: \"claude\", hint: \"best quality, uses API tokens\" },\n ],\n initialValue: \"ollama\",\n });\n handleCancel(engineChoice);\n\n const detailChoice = await select<DetailLevel>({\n message: \"How detailed should explanations be?\",\n options: [\n { label: \"Standard\", value: \"standard\", hint: \"1-2 sentence explanation per change (recommended)\" },\n { label: \"Minimal\", value: \"minimal\", hint: \"one short sentence per change\" },\n { label: \"Verbose\", value: \"verbose\", hint: \"detailed bullet-point breakdown\" },\n ],\n initialValue: \"standard\",\n });\n handleCancel(detailChoice);\n\n let ollamaModel = DEFAULT_CONFIG.ollamaModel;\n\n if (engineChoice === \"ollama\") {\n const status = await checkOllama();\n\n if (status === \"missing\") {\n const platform = detectPlatform();\n const installCmd = ollamaInstallCommand(platform);\n note(\n `Ollama is not installed.\\nInstall with: ${pc.cyan(installCmd)}\\nOr visit: ${pc.cyan(\"https://ollama.com/download\")}`,\n \"Missing prerequisite\"\n );\n const proceed = await confirm({\n message: \"Install Ollama manually and continue after it's ready?\",\n initialValue: true,\n });\n handleCancel(proceed);\n if (!proceed) {\n cancel(\"Setup paused. Run 'npx vibe-code-explainer init' again after installing Ollama.\");\n process.exit(0);\n }\n } else if (status === \"installed-not-running\") {\n note(\n `Ollama is installed but the service isn't running.\\nStart it with: ${pc.cyan(\"ollama serve\")} (in a separate terminal).`,\n \"Ollama not running\"\n );\n }\n\n const modelChoice = await pickModel();\n handleCancel(modelChoice);\n ollamaModel = modelChoice;\n\n const pullOk = await pullModel(ollamaModel);\n if (!pullOk) {\n const skipPull = await confirm({\n message: \"Continue without pulling the model? (You'll need to run 'ollama pull' manually.)\",\n initialValue: false,\n });\n handleCancel(skipPull);\n if (!skipPull) {\n cancel(\"Setup aborted.\");\n process.exit(1);\n }\n }\n }\n\n // Build config.\n const config: Config = {\n ...DEFAULT_CONFIG,\n engine: engineChoice,\n detailLevel: detailChoice,\n ollamaModel,\n };\n\n const projectRoot = process.cwd();\n const configPath = join(projectRoot, CONFIG_FILE);\n\n if (existsSync(configPath)) {\n const overwrite = await confirm({\n message: `${CONFIG_FILE} already exists. Overwrite?`,\n initialValue: false,\n });\n handleCancel(overwrite);\n if (!overwrite) {\n cancel(\"Setup aborted to avoid overwriting existing config.\");\n process.exit(0);\n }\n }\n\n writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\");\n\n // Merge hooks into settings.local.json.\n const hookScript = resolveHookScriptPath();\n const mergeResult = mergeHooksIntoSettings(projectRoot, hookScript);\n\n note(\n `${pc.green(\"\\u2713\")} Wrote ${pc.cyan(CONFIG_FILE)}\\n${pc.green(\"\\u2713\")} ${mergeResult.created ? \"Created\" : \"Updated\"} ${pc.cyan(mergeResult.path)}`,\n \"Configuration saved\"\n );\n\n // Warmup.\n if (engineChoice === \"ollama\" && !skipWarmup) {\n await runWarmup(config);\n }\n\n outro(\n pc.bold(\"code-explainer is active.\") +\n \"\\nClaude Code will now explain every Edit, Write, and destructive Bash command.\"\n );\n}\n","import { platform } from \"node:os\";\n\nexport type Platform = \"windows\" | \"macos\" | \"linux\" | \"wsl\" | \"unknown\";\n\nexport function detectPlatform(): Platform {\n const p = platform();\n if (p === \"win32\") return \"windows\";\n if (p === \"darwin\") return \"macos\";\n if (p === \"linux\") {\n // WSL detection: Microsoft string in /proc/version\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const fs = require(\"node:fs\") as typeof import(\"node:fs\");\n const release = fs.readFileSync(\"/proc/version\", \"utf-8\").toLowerCase();\n if (release.includes(\"microsoft\") || release.includes(\"wsl\")) return \"wsl\";\n } catch {\n // ignore\n }\n return \"linux\";\n }\n return \"unknown\";\n}\n\nexport function ollamaInstallCommand(p: Platform): string {\n switch (p) {\n case \"macos\":\n return \"brew install ollama\";\n case \"windows\":\n return \"winget install Ollama.Ollama\";\n case \"linux\":\n case \"wsl\":\n return \"curl -fsSL https://ollama.com/install.sh | sh\";\n default:\n return \"Visit https://ollama.com/download to install Ollama\";\n }\n}\n"],"mappings":"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","names":["platform"]}