feat(providers): add Claude Code CLI provider

- implement `ClaudeCodeProvider` using `claude -p` with plain text output
- register `claudecode` as a new provider enum option in package.json
- add `enumDescriptions` for all provider choices
- update API key description to note it is not required for Claude Code
- bump version to 1.1.5
This commit is contained in:
2026-05-01 14:59:26 -04:00
parent e309fa3acd
commit ca73d9683f
5 changed files with 97 additions and 8 deletions
+15 -3
View File
@@ -14,7 +14,7 @@ Tu peux interrompre la génération à tout moment depuis le même panneau.
- VS Code 1.85 ou plus récent
- Un dépôt Git avec des changements stagés
- Une clé API pour le fournisseur choisi (non requise pour Ollama)
- Une clé API pour le fournisseur choisi (non requise pour Ollama et Claude Code)
## Installation
@@ -39,8 +39,8 @@ Les paramètres se trouvent dans les préférences VS Code sous **Zemit**.
| Paramètre | Description | Défaut |
|---|---|---|
| `zemit.provider` | Fournisseur d'IA : `anthropic`, `openai` ou `ollama` | `anthropic` |
| `zemit.apiKey` | Clé API du fournisseur (inutile pour Ollama) | _(vide)_ |
| `zemit.provider` | Fournisseur d'IA : `anthropic`, `openai`, `ollama` ou `claudecode` | `anthropic` |
| `zemit.apiKey` | Clé API du fournisseur (inutile pour Ollama et Claude Code) | _(vide)_ |
| `zemit.model` | Modèle à utiliser | `claude-sonnet-4-6` |
| `zemit.baseUrl` | URL de base personnalisée (ex. Ollama local) | _(vide)_ |
| `zemit.promptVersion` | Version du prompt | `zemit-v2` |
@@ -61,6 +61,18 @@ Pour arrêter une génération en cours, clique sur l'icône d'arrêt au même e
- **Anthropic** : modèles Claude (Haiku, Sonnet, Opus)
- **OpenAI** : modèles GPT
- **Ollama** : modèles locaux, aucune clé requise
- **Claude Code** : utilise le CLI `claude` installé localement, aucune clé API requise — l'authentification est gérée par Claude Code lui-même
### Utiliser Claude Code
Assure-toi que le CLI `claude` est installé et accessible (`~/.local/bin/claude` ou dans ton `PATH`), puis configure :
```json
"zemit.provider": "claudecode",
"zemit.model": "claude-sonnet-4-6"
```
> Note : le provider Claude Code ne supporte pas le streaming — le message apparaît d'un coup à la fin de la génération, contrairement aux providers API.
## Développement
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "zemit",
"version": "1.1.4",
"version": "1.1.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "zemit",
"version": "1.1.4",
"version": "1.1.5",
"license": "GPL-3.0-only",
"devDependencies": {
"@types/node": "^18.0.0",
+10 -3
View File
@@ -1,6 +1,6 @@
{
"name": "zemit",
"version": "1.1.4",
"version": "1.1.5",
"displayName": "Zemit",
"description": "Génère des messages de commit, via un modèle d'IA, directement dans VSCode.",
"repository": {
@@ -36,7 +36,14 @@
"enum": [
"anthropic",
"openai",
"ollama"
"ollama",
"claudecode"
],
"enumDescriptions": [
"Anthropic API (clé API requise)",
"OpenAI API (clé API requise)",
"Ollama instance locale",
"Claude Code CLI installation locale, sans clé API"
],
"default": "anthropic",
"description": "Fournisseur d'IA utilisé pour générer les messages de commit."
@@ -44,7 +51,7 @@
"zemit.apiKey": {
"type": "string",
"default": "",
"markdownDescription": "Clé API du fournisseur sélectionné. Non requise pour Ollama."
"markdownDescription": "Clé API du fournisseur sélectionné. Non requise pour Ollama et Claude Code."
},
"zemit.model": {
"type": "string",
+69
View File
@@ -1,3 +1,4 @@
import { spawn } from "child_process"
import * as vscode from "vscode"
import { SYSTEM_PROMPT, getPrompt } from "./prompts"
@@ -91,6 +92,69 @@ class AnthropicProvider implements AIProvider {
}
}
// ─── Claude Code CLI ─────────────────────────────────────────────────────────
class ClaudeCodeProvider implements AIProvider {
constructor(private readonly model: string) {}
async *generateCommitMessage(diff: string, style: string, signal: AbortSignal): AsyncIterable<string> {
const instruction = getPrompt(style)
const input = `<input>${instruction}\n\n${diff}</input>`
// ~/.local/bin may not be in PATH inside the VSCode extension host
const env = { ...process.env, PATH: `${process.env.HOME ?? ""}/.local/bin:${process.env.PATH ?? ""}` }
const proc = spawn("claude", [
"-p", "--output-format", "text",
"--model", this.model,
"--system-prompt", SYSTEM_PROMPT,
], { env })
const onAbort = () => proc.kill()
signal.addEventListener("abort", onAbort, { once: true })
proc.stdin.end(input)
let stderrData = ""
proc.stderr.on("data", (chunk: Buffer) => { stderrData += chunk.toString() })
// When spawn fails (e.g. ENOENT), 'close' never fires — resolve via 'error' too
let spawnError: Error | undefined
const closeOrError = new Promise<number | null>((resolve) => {
proc.on("close", resolve)
proc.on("error", (err: Error) => {
spawnError = err
proc.stdout.destroy() // Unblock the for-await below
resolve(null)
})
})
try {
for await (const chunk of proc.stdout) {
yield (chunk as Buffer).toString()
}
} catch {
// stdout may throw if destroyed due to a spawn error — handled below
} finally {
signal.removeEventListener("abort", onAbort)
}
await closeOrError
if (spawnError) {
const isNotFound = (spawnError as NodeJS.ErrnoException).code === "ENOENT"
throw new Error(isNotFound
? "Claude Code CLI introuvable. Assurez-vous que 'claude' est installé et accessible (ex. ~/.local/bin)."
: `Claude Code CLI: ${spawnError.message}`)
}
const exitCode = await closeOrError
if (exitCode !== 0 && !signal.aborted) {
throw new Error(`Claude Code CLI error: ${stderrData || `exit code ${exitCode}`}`)
}
}
}
// ─── SSE parsing helpers ─────────────────────────────────────────────────────
async function* parseSSEStream(
@@ -161,6 +225,9 @@ export function createProvider(config: vscode.WorkspaceConfiguration): AIProvide
if (!apiKey) throw new Error("Clé API Anthropic requise. Configurez-la dans Paramètres → Zemit AI Commit.")
return new AnthropicProvider(apiKey, model, baseUrl)
}
case "claudecode": {
return new ClaudeCodeProvider(model)
}
case "ollama": {
const baseUrl = customBaseUrl || "http://localhost:11434/v1"
return new OpenAICompatibleProvider("ollama", model, baseUrl)
@@ -207,6 +274,8 @@ export async function fetchAvailableModels(config: vscode.WorkspaceConfiguration
.filter((id) => /^(gpt-|o1|o3|chatgpt-)/.test(id))
.sort()
}
case "claudecode":
return []
case "ollama": {
const baseUrl = customBaseUrl || "http://localhost:11434/v1"
const response = await fetch(`${baseUrl}/models`)
+1
View File
@@ -3,6 +3,7 @@
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"types": ["node"],
"outDir": "dist",
"rootDir": "src",
"strict": true,