Some checks failed
CI / Build & test (Node 24) (push) Has been cancelled
CI / Pack & boot smoke (Node 22) (push) Has been cancelled
CI / No em/en dashes (push) Has been cancelled
CI / Build & test (Node 22) (push) Has been cancelled
Deploy site (production) / build-and-deploy (push) Successful in 20s
Remplace les 547 tirets cadratins (U+2014) et demi-cadratins (U+2013) des fichiers versionnés par la ponctuation contextuelle adaptée (point médian, deux-points, virgule, parenthèses ; tiret simple pour les plages), sur 122 fichiers (appli, vitrine, doc, tests, workflows, scripts). Ajoute le job CI « lint-dashes » (git grep -P) qui échoue si un tiret cadratin/demi-cadratin réapparaît, hors logo binaire et captures brutes du terminal (fidélité des fixtures de détection de dialogue).
104 lines
3.7 KiB
TypeScript
104 lines
3.7 KiB
TypeScript
import { execFileSync } from 'node:child_process';
|
|
import { accessSync, constants } from 'node:fs';
|
|
|
|
export interface SpawnSpec {
|
|
file: string;
|
|
args: string[];
|
|
env: NodeJS.ProcessEnv;
|
|
}
|
|
|
|
export interface SpawnOptions {
|
|
command: 'claude' | 'bash';
|
|
/** reprise d'une session existante (P2) : `--resume <id>`, `--fork-session` si fork. */
|
|
resume?: { claudeSessionId: string; fork?: boolean };
|
|
/** répertoires supplémentaires à relier dans une seule session (P6) : `--add-dir <path>` répété. */
|
|
addDirs?: string[];
|
|
/** chemin explicite du binaire `claude` (réglage UI) ; sinon résolution via PATH (`which`). */
|
|
claudeBinPath?: string | null;
|
|
}
|
|
|
|
/** Diagnostic de résolution du binaire `claude` (exposé en lecture dans Réglages). */
|
|
export interface ClaudeBinDiagnostic {
|
|
/** chemin résolu du binaire, ou null si introuvable. */
|
|
path: string | null;
|
|
/** 'configured' = réglage explicite ; 'path' = trouvé via PATH ; null = introuvable. */
|
|
source: 'configured' | 'path' | null;
|
|
/** true si le binaire est présent et exécutable. */
|
|
ok: boolean;
|
|
}
|
|
|
|
let cachedClaudeBin: string | null = null;
|
|
|
|
/** Recherche `claude` dans le PATH (sans throw). null si absent. */
|
|
function findClaudeOnPath(): string | null {
|
|
try {
|
|
return execFileSync('which', ['claude'], { encoding: 'utf8' }).trim() || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function isExecutable(path: string): boolean {
|
|
try {
|
|
accessSync(path, constants.X_OK);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Résout le binaire `claude`. Si `configuredPath` est fourni (réglage UI), il est utilisé tel quel
|
|
* (validé exécutable, message clair sinon) et JAMAIS mis en cache (modifiable à chaud). Sinon :
|
|
* `which claude`, mis en cache. Un service systemd/launchd démarre avec un PATH minimal sans
|
|
* ~/.local/bin → `which claude` y échoue ; d'où le réglage de chemin explicite (et le PATH figé par
|
|
* `arboretum install`).
|
|
*/
|
|
export function resolveClaudeBin(configuredPath?: string | null): string {
|
|
if (configuredPath) {
|
|
if (!isExecutable(configuredPath)) {
|
|
throw new Error(`Configured Claude CLI path is not executable: ${configuredPath}`);
|
|
}
|
|
return configuredPath;
|
|
}
|
|
if (cachedClaudeBin) return cachedClaudeBin;
|
|
const found = findClaudeOnPath();
|
|
if (!found) {
|
|
throw new Error(
|
|
'Claude Code CLI not found in PATH. Install it first: https://code.claude.com/docs/en/quickstart',
|
|
);
|
|
}
|
|
cachedClaudeBin = found;
|
|
return cachedClaudeBin;
|
|
}
|
|
|
|
/** Diagnostic non-throwing pour l'UI (Réglages) : recalculé à chaque appel, jamais caché. */
|
|
export function diagnoseClaudeBin(configuredPath?: string | null): ClaudeBinDiagnostic {
|
|
if (configuredPath) {
|
|
return { path: configuredPath, source: 'configured', ok: isExecutable(configuredPath) };
|
|
}
|
|
const found = findClaudeOnPath();
|
|
return found ? { path: found, source: 'path', ok: true } : { path: null, source: null, ok: false };
|
|
}
|
|
|
|
/** Module volontairement abstrait : le plan B « BYO API key / Agent SDK » se brancherait ici. */
|
|
export function buildSpawnSpec(opts: SpawnOptions): SpawnSpec {
|
|
const env: NodeJS.ProcessEnv = {
|
|
...process.env,
|
|
TERM: 'xterm-256color',
|
|
COLORTERM: 'truecolor',
|
|
};
|
|
if (opts.command === 'bash') {
|
|
return { file: 'bash', args: ['--norc'], env };
|
|
}
|
|
const args: string[] = [];
|
|
if (opts.resume) {
|
|
// `--resume` doit toujours s'exécuter dans le cwd d'origine (garanti par l'appelant, spike S1).
|
|
args.push('--resume', opts.resume.claudeSessionId);
|
|
if (opts.resume.fork) args.push('--fork-session');
|
|
}
|
|
// Session de groupe : relie plusieurs repos/worktrees dans une seule session (P6).
|
|
for (const dir of opts.addDirs ?? []) args.push('--add-dir', dir);
|
|
return { file: resolveClaudeBin(opts.claudeBinPath), args, env };
|
|
}
|