feat(settings): réglages Claude CLI (binaire + ~/.claude) & notif « disponible »

- Réglages → Claude CLI : override du chemin du binaire `claude` (effet à la
  prochaine session, fallback `which claude`), diagnostic de détection, et
  override de la racine ~/.claude (effet au redémarrage). Validateurs stricts.
- Push : notifie aussi sur le front busy→idle (session redevenue disponible).
- Renomme l'état affiché idle → « disponible » / « available » (web EN/FR + VS Code) ;
  l'enum SessionActivity du protocole reste inchangé.
This commit is contained in:
2026-06-24 10:15:29 +02:00
parent 2506dfb1f3
commit b5236b41c8
16 changed files with 519 additions and 27 deletions

View File

@@ -7,6 +7,7 @@ import type { Config } from '../config.js';
import { type Db, setSetting } from '../db/index.js';
import type { PushService } from '../core/push-service.js';
import { recordAudit } from '../core/audit-log.js';
import { diagnoseClaudeBin } from '../core/claude-launcher.js';
import {
SCAN_INTERVAL_KEY,
SCAN_ROOTS_KEY,
@@ -15,6 +16,14 @@ import {
readScanIntervalMin,
readScanRoots,
} from '../core/scan-settings.js';
import {
CLAUDE_BIN_PATH_KEY,
CLAUDE_HOME_KEY,
normalizeClaudeBinPath,
normalizeClaudeHome,
readClaudeBinPath,
readClaudeHome,
} from '../core/claude-settings.js';
// Réglages modifiables via l'API (allow-list stricte). Les secrets ne figurent JAMAIS ici.
export function registerSettingsRoutes(
@@ -32,11 +41,16 @@ export function registerSettingsRoutes(
dataDir: config.dataDir,
vapidPublicKey: push.publicKey() || null,
vapidContact: config.vapidContact,
claudeHome: config.claudeHome,
// Diagnostic recalculé à chaque GET : reflète l'état réel (override exécutable ? `which claude` ?).
claudeBin: diagnoseClaudeBin(readClaudeBinPath(db)),
});
const snapshot = (): SettingsResponse => ({
settings: {
scanRoots: readScanRoots(db),
scanIntervalMin: readScanIntervalMin(db),
claudeBinPath: readClaudeBinPath(db),
claudeHome: readClaudeHome(db),
},
server: serverInfo(),
});
@@ -59,6 +73,20 @@ export function registerSettingsRoutes(
}
setSetting(db, SCAN_INTERVAL_KEY, String(interval));
}
if ('claudeBinPath' in body) {
const value = normalizeClaudeBinPath(body.claudeBinPath);
if (value === null) {
return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'claudeBinPath must be an absolute path to an executable file (or "" to reset)' } });
}
setSetting(db, CLAUDE_BIN_PATH_KEY, value);
}
if ('claudeHome' in body) {
const value = normalizeClaudeHome(body.claudeHome);
if (value === null) {
return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'claudeHome must be an absolute path to an existing directory (or "" to reset)' } });
}
setSetting(db, CLAUDE_HOME_KEY, value);
}
recordAudit(db, {
actor: req.authContext?.tokenId ?? 'unknown',
action: 'settings.update',