dsh-codebase-chat 0.25.0 → 0.25.1

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.js CHANGED
@@ -2528,6 +2528,68 @@ function buildToolPrompt(tool, opts) {
2528
2528
  return normalizeLabels(builder(opts), opts.lang || "fr");
2529
2529
  }
2530
2530
 
2531
+ // src/local-llm.ts
2532
+ import { existsSync as existsSync2 } from "fs";
2533
+ import { join as join8 } from "path";
2534
+ var DEFAULT_MODEL_URI = "hf:Qwen/Qwen2.5-1.5B-Instruct-GGUF:Q4_K_M";
2535
+ var LOCAL_CONTEXT_SIZE = 8192;
2536
+ var LOCAL_MAX_TOKENS = 1024;
2537
+ function isLocalLlmEnabled() {
2538
+ return !!(process.env.CODEBASE_LOCAL_LLM || "").trim();
2539
+ }
2540
+ function configuredModel() {
2541
+ const v = (process.env.CODEBASE_LOCAL_LLM || "").trim();
2542
+ if (!v || v === "1" || v.toLowerCase() === "true") return DEFAULT_MODEL_URI;
2543
+ return v;
2544
+ }
2545
+ async function importLlama() {
2546
+ try {
2547
+ return await import("node-llama-cpp");
2548
+ } catch {
2549
+ throw new Error("Local LLM needs the optional dependency: npm i node-llama-cpp");
2550
+ }
2551
+ }
2552
+ var modelPromise = null;
2553
+ function loadLocalModel() {
2554
+ if (!modelPromise) {
2555
+ modelPromise = (async () => {
2556
+ const spec = configuredModel();
2557
+ let modelPath = spec;
2558
+ if (spec.startsWith("hf:")) {
2559
+ const { createModelDownloader } = await importLlama();
2560
+ const downloader = await createModelDownloader({
2561
+ modelUri: spec,
2562
+ dirPath: join8(getCacheDir(), "models")
2563
+ });
2564
+ modelPath = await downloader.download();
2565
+ } else if (!existsSync2(spec)) {
2566
+ throw new Error(`Local model not found: ${spec}`);
2567
+ }
2568
+ const { getLlama } = await importLlama();
2569
+ const llama = await getLlama();
2570
+ return llama.loadModel({ modelPath });
2571
+ })();
2572
+ modelPromise.catch(() => {
2573
+ modelPromise = null;
2574
+ });
2575
+ }
2576
+ return modelPromise;
2577
+ }
2578
+ async function callLocalLlm(prompt, lang = "fr") {
2579
+ const model = await loadLocalModel();
2580
+ const { LlamaChatSession } = await importLlama();
2581
+ const context = await model.createContext({ contextSize: LOCAL_CONTEXT_SIZE });
2582
+ try {
2583
+ const session = new LlamaChatSession({
2584
+ contextSequence: context.getSequence(),
2585
+ systemPrompt: lang === "en" ? "You are a senior codebase analyst. Be precise and cite files with [source: path:line]." : "Tu es un analyste codebase senior. Sois precis et cite les fichiers avec [source: chemin:ligne]."
2586
+ });
2587
+ return await session.prompt(prompt, { temperature: 0.2, maxTokens: LOCAL_MAX_TOKENS });
2588
+ } finally {
2589
+ await context.dispose();
2590
+ }
2591
+ }
2592
+
2531
2593
  // src/cli.ts
2532
2594
  var ExitSignal = class {
2533
2595
  constructor(code) {
@@ -2555,6 +2617,8 @@ Usage:
2555
2617
  npx dsh-codebase-chat --project <path> --health --diff main
2556
2618
  npx dsh-codebase-chat --project <path> --watch
2557
2619
  npx dsh-codebase-chat --project <path> --prompt intelligence
2620
+ npx dsh-codebase-chat --project <path> --prompt intelligence --call # answered via DEEPSEEK_API_KEY
2621
+ npx dsh-codebase-chat --project <path> --prompt intelligence --local # answered 100% offline
2558
2622
 
2559
2623
  Options:
2560
2624
  -p, --project <path> Project directory (default: current directory)
@@ -2572,6 +2636,11 @@ Options:
2572
2636
  Pipe it to any LLM (e.g. ... --prompt intelligence | dsh).
2573
2637
  Query modes read --ask/--search/--file; --focus and
2574
2638
  --style (ouf|punchy|dense|pedagogique|minimal) apply.
2639
+ --call With --prompt: send it to the API (needs DEEPSEEK_API_KEY
2640
+ or OPENAI_API_KEY; DEEPSEEK_BASE_URL / CODEBASE_MODEL
2641
+ customize endpoint/model) instead of printing it.
2642
+ --local With --prompt: answer with the embedded local model
2643
+ (node-llama-cpp, ~1 GB download on first use, offline).
2575
2644
  -w, --watch Keep the index hot \u2014 rebuild incrementally on file changes
2576
2645
  -e, --embed Enable local semantic embeddings (slower, more relevant)
2577
2646
  --lang <en|fr> Language for headings (default: .codebase-chat.json lang, else fr)
@@ -2600,6 +2669,8 @@ async function main() {
2600
2669
  prompt: { type: "string" },
2601
2670
  focus: { type: "string" },
2602
2671
  style: { type: "string" },
2672
+ call: { type: "boolean", default: false },
2673
+ local: { type: "boolean", default: false },
2603
2674
  watch: { type: "boolean", short: "w", default: false },
2604
2675
  embed: { type: "boolean", short: "e", default: false },
2605
2676
  lang: { type: "string" },
@@ -2718,8 +2789,9 @@ ${formatHealthReport(report, lang)}`;
2718
2789
  }
2719
2790
  }
2720
2791
  const projectName = basename3(result.absProject);
2792
+ let prompt;
2721
2793
  try {
2722
- console.log(buildToolPrompt(tool, {
2794
+ prompt = buildToolPrompt(tool, {
2723
2795
  context: `${result.context}${staticSection}`,
2724
2796
  projectName,
2725
2797
  lang,
@@ -2728,11 +2800,48 @@ ${formatHealthReport(report, lang)}`;
2728
2800
  focus: values.focus ?? query,
2729
2801
  filePath: values.file ?? "",
2730
2802
  description: query
2731
- }));
2803
+ });
2732
2804
  } catch {
2733
2805
  console.error(lang === "en" ? `unknown prompt mode "${mode}" \u2014 expected: intelligence, report, audit, tasks, ceo, player, chat, search, explain, refactor, crea` : `mode de prompt inconnu "${mode}" \u2014 attendu : intelligence, report, audit, tasks, ceo, player, chat, search, explain, refactor, crea`);
2734
2806
  exit(1);
2735
2807
  }
2808
+ if (values.local || isLocalLlmEnabled()) {
2809
+ console.error(lang === "en" ? "Answering with the embedded local model (first run downloads ~1 GB)..." : "R\xE9ponse via le mod\xE8le local embarqu\xE9 (premier lancement : ~1 Go de t\xE9l\xE9chargement)...");
2810
+ console.log(await callLocalLlm(prompt, lang));
2811
+ exit(0);
2812
+ }
2813
+ if (values.call) {
2814
+ const apiKey = process.env.DEEPSEEK_API_KEY || process.env.OPENAI_API_KEY || "";
2815
+ if (!apiKey) {
2816
+ console.error(lang === "en" ? "--call needs DEEPSEEK_API_KEY or OPENAI_API_KEY in the environment" : "--call n\xE9cessite DEEPSEEK_API_KEY ou OPENAI_API_KEY dans l\u2019environnement");
2817
+ exit(1);
2818
+ }
2819
+ const baseUrl = process.env.DEEPSEEK_BASE_URL || process.env.OPENAI_BASE_URL || "https://api.deepseek.com/v1";
2820
+ const model = process.env.CODEBASE_MODEL || "deepseek-chat";
2821
+ const res = await fetch(`${baseUrl}/chat/completions`, {
2822
+ method: "POST",
2823
+ signal: AbortSignal.timeout(12e4),
2824
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
2825
+ body: JSON.stringify({
2826
+ model,
2827
+ messages: [
2828
+ { role: "system", content: lang === "en" ? "You are a senior codebase analyst. Be precise and cite files." : "Tu es un analyste codebase senior. Sois pr\xE9cis et cite les fichiers." },
2829
+ { role: "user", content: prompt }
2830
+ ],
2831
+ temperature: 0.3,
2832
+ max_tokens: 8192
2833
+ })
2834
+ });
2835
+ if (!res.ok) {
2836
+ const text = await res.text().catch(() => "");
2837
+ console.error(`API error ${res.status}: ${text}`);
2838
+ exit(1);
2839
+ }
2840
+ const data = await res.json();
2841
+ console.log(data.choices?.[0]?.message?.content || "");
2842
+ exit(0);
2843
+ }
2844
+ console.log(prompt);
2736
2845
  exit(0);
2737
2846
  }
2738
2847
  if (values.ask || values.search || values.file) {