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 +111 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -1
- package/lib/index.js +1 -1
- package/package.json +4 -1
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
|
-
|
|
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) {
|