@iaforged/context-code 1.1.5 → 1.1.7

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.
@@ -34,7 +34,12 @@ const LANGUAGE_NAME_TO_CODE = {
34
34
  export function normalizeLanguageForSTT(language) {
35
35
  if (!language)
36
36
  return { code: DEFAULT_STT_LANGUAGE };
37
- const lower = language.toLowerCase().trim();
37
+ // Normaliza eliminando tildes/diacriticos para que "Español" -> "espanol".
38
+ const lower = language
39
+ .toLowerCase()
40
+ .normalize('NFD')
41
+ .replace(/[\u0300-\u036f]/g, '')
42
+ .trim();
38
43
  if (!lower)
39
44
  return { code: DEFAULT_STT_LANGUAGE };
40
45
  if (lower === 'auto')
@@ -34,7 +34,11 @@ function getConfiguredExecutable() {
34
34
  if (stored?.trim()) {
35
35
  return stored.trim();
36
36
  }
37
- return process.platform === 'win32' ? 'whisper-cli.exe' : 'whisper-cli';
37
+ // Fallback al nombre por defecto en PATH segun plataforma. En macOS/Linux
38
+ // probamos primero `whisper-cli` (releases) y luego `whisper-cpp` (Homebrew).
39
+ if (process.platform === 'win32')
40
+ return 'whisper-cli.exe';
41
+ return 'whisper-cli';
38
42
  }
39
43
  function getConfiguredModel() {
40
44
  const model = process.env.CONTEXT_CODE_DICTATION_MODEL ??
@@ -207,7 +211,7 @@ async function findFileRecursive(rootDir, predicate) {
207
211
  async function findInstalledExecutable(installDir) {
208
212
  const candidateNames = process.platform === 'win32'
209
213
  ? ['whisper-cli.exe', 'main.exe']
210
- : ['whisper-cli', 'main'];
214
+ : ['whisper-cli', 'whisper-cpp', 'main'];
211
215
  return findFileRecursive(installDir, path => candidateNames.some(name => path.toLowerCase().endsWith(name.toLowerCase())));
212
216
  }
213
217
  function persistInstalledDictationConfig(config) {
@@ -280,7 +284,146 @@ export async function getLocalDictationStatus() {
280
284
  }
281
285
  return lines.join('\n');
282
286
  }
287
+ async function isCommandAvailable(command) {
288
+ const probe = process.platform === 'win32' ? 'where' : 'which';
289
+ try {
290
+ const result = await execa(probe, [command], {
291
+ reject: false,
292
+ windowsHide: true,
293
+ timeout: 5_000,
294
+ });
295
+ return result.exitCode === 0;
296
+ }
297
+ catch {
298
+ return false;
299
+ }
300
+ }
301
+ async function getBrewPrefix() {
302
+ try {
303
+ const result = await execa('brew', ['--prefix'], {
304
+ reject: false,
305
+ windowsHide: true,
306
+ timeout: 10_000,
307
+ });
308
+ if (result.exitCode === 0) {
309
+ const prefix = result.stdout.trim();
310
+ return prefix || null;
311
+ }
312
+ }
313
+ catch {
314
+ // ignore
315
+ }
316
+ return null;
317
+ }
318
+ async function installViaHomebrew(modelName) {
319
+ const normalizedModelName = normalizeModelName(modelName);
320
+ const modelFileName = buildModelFileName(normalizedModelName);
321
+ const installDir = getInstallRoot();
322
+ // Instala (o reinstala/actualiza si ya estaba) whisper-cpp con brew.
323
+ const brewResult = await execa('brew', ['install', 'whisper-cpp'], {
324
+ reject: false,
325
+ windowsHide: true,
326
+ timeout: 600_000,
327
+ });
328
+ if (brewResult.exitCode !== 0) {
329
+ throw new Error(`brew install whisper-cpp fallo:\n${brewResult.stderr || brewResult.stdout || 'error desconocido'}`);
330
+ }
331
+ // Homebrew instala el binario como `whisper-cli` (igual que los releases),
332
+ // pero algunas versiones antiguas usaban `whisper-cpp`. Probamos ambos.
333
+ const brewPrefix = await getBrewPrefix();
334
+ const binNames = ['whisper-cli', 'whisper-cpp'];
335
+ const candidatePaths = [];
336
+ for (const name of binNames) {
337
+ if (brewPrefix)
338
+ candidatePaths.push(join(brewPrefix, 'bin', name));
339
+ candidatePaths.push(`/opt/homebrew/bin/${name}`);
340
+ candidatePaths.push(`/usr/local/bin/${name}`);
341
+ }
342
+ let executablePath = candidatePaths.find(fileExists) ?? null;
343
+ if (!executablePath) {
344
+ // Fallback: resolver via `which` para cualquiera de los nombres.
345
+ for (const name of binNames) {
346
+ const which = await execa('which', [name], {
347
+ reject: false,
348
+ windowsHide: true,
349
+ timeout: 5_000,
350
+ });
351
+ if (which.exitCode === 0 && which.stdout.trim()) {
352
+ executablePath = which.stdout.trim();
353
+ break;
354
+ }
355
+ }
356
+ }
357
+ if (!executablePath) {
358
+ throw new Error('brew instalo whisper-cpp pero no encontre el binario en el PATH.');
359
+ }
360
+ // Descarga el modelo GGML (Homebrew no lo trae) en nuestra carpeta privada.
361
+ const modelDir = join(installDir, 'models');
362
+ const modelPath = join(modelDir, modelFileName);
363
+ await mkdir(modelDir, { recursive: true });
364
+ if (!fileExists(modelPath)) {
365
+ await writeFile(modelPath, await fetchBuffer(`${WHISPER_MODEL_BASE_URL}/${modelFileName}?download=1`, `modelo ${modelFileName}`));
366
+ }
367
+ // Intenta resolver la version instalada para guardarla como releaseTag.
368
+ let releaseTag = 'homebrew';
369
+ try {
370
+ const versionResult = await execa(executablePath, ['--version'], {
371
+ reject: false,
372
+ windowsHide: true,
373
+ timeout: 10_000,
374
+ });
375
+ const text = `${versionResult.stdout}\n${versionResult.stderr}`;
376
+ const match = text.match(/v?\d+\.\d+(?:\.\d+)?/);
377
+ if (match)
378
+ releaseTag = `homebrew-${match[0]}`;
379
+ }
380
+ catch {
381
+ // ignore
382
+ }
383
+ persistInstalledDictationConfig({
384
+ executablePath,
385
+ installDir,
386
+ installedAt: new Date().toISOString(),
387
+ modelName: normalizedModelName,
388
+ modelPath,
389
+ releaseTag,
390
+ });
391
+ logForDebugging(`[dictation] Instalacion via Homebrew: ${executablePath}, modelo en ${modelPath}`);
392
+ return { executablePath, installDir, modelPath, releaseTag };
393
+ }
394
+ function getManualInstallInstructions() {
395
+ if (process.platform === 'darwin') {
396
+ return [
397
+ 'En macOS no hay binarios precompilados en los releases oficiales de whisper.cpp.',
398
+ 'Opciones para instalarlo manualmente:',
399
+ '1) Instala Homebrew (https://brew.sh) y luego ejecuta `brew install whisper-cpp`.',
400
+ '2) Compila desde fuente: clona https://github.com/ggml-org/whisper.cpp y',
401
+ ' ejecuta `cmake -B build && cmake --build build -j --config Release`.',
402
+ 'Despues apunta el backend con la variable CONTEXT_CODE_DICTATION_EXECUTABLE',
403
+ 'y el modelo con CONTEXT_CODE_DICTATION_MODEL, o vuelve a ejecutar /dictar install.',
404
+ ].join('\n');
405
+ }
406
+ if (process.platform === 'linux') {
407
+ return [
408
+ 'En Linux no hay binarios precompilados en los releases oficiales de whisper.cpp.',
409
+ 'Compila desde fuente:',
410
+ ' git clone https://github.com/ggml-org/whisper.cpp && cd whisper.cpp',
411
+ ' cmake -B build && cmake --build build -j --config Release',
412
+ 'Despues exporta CONTEXT_CODE_DICTATION_EXECUTABLE y CONTEXT_CODE_DICTATION_MODEL,',
413
+ 'o vuelve a ejecutar /dictar install una vez tengas el binario en el PATH.',
414
+ ].join('\n');
415
+ }
416
+ return 'No encontre un binario compatible para tu plataforma.';
417
+ }
283
418
  export async function installLocalDictation(modelName) {
419
+ // En macOS los releases oficiales solo publican binarios para Windows e iOS,
420
+ // asi que usamos Homebrew como camino preferido.
421
+ if (process.platform === 'darwin') {
422
+ if (await isCommandAvailable('brew')) {
423
+ return installViaHomebrew(modelName);
424
+ }
425
+ throw new Error(`No se encontro Homebrew en el sistema.\n${getManualInstallInstructions()}`);
426
+ }
284
427
  const normalizedModelName = normalizeModelName(modelName);
285
428
  const modelFileName = buildModelFileName(normalizedModelName);
286
429
  const installDir = getInstallRoot();
@@ -289,7 +432,7 @@ export async function installLocalDictation(modelName) {
289
432
  const release = await fetchLatestRelease();
290
433
  const asset = pickReleaseAsset(release.assets);
291
434
  if (!asset) {
292
- throw new Error(`No encontre un binario compatible de whisper.cpp para ${process.platform}/${process.arch}.`);
435
+ throw new Error(`No encontre un binario compatible de whisper.cpp para ${process.platform}/${process.arch}.\n${getManualInstallInstructions()}`);
293
436
  }
294
437
  const archivePath = join(tempDir, asset.name);
295
438
  const modelPath = join(installDir, 'models', modelFileName);
@@ -552,6 +552,8 @@ export function modelDisplayString(model) {
552
552
  }
553
553
  // @[MODEL LAUNCH]: Add a marketing name mapping for the new model below.
554
554
  export function getMarketingNameForModel(modelId) {
555
+ if (!modelId)
556
+ return undefined;
555
557
  if (getAPIProvider() === 'foundry') {
556
558
  // deployment ID is user-defined in Foundry, so it may have no relation to the actual model
557
559
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iaforged/context-code",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "Context Code es un asistente de desarrollo para la terminal. Puede revisar tu proyecto, editar archivos, ejecutar comandos y apoyarte en tareas reales de programacion.",
5
5
  "author": "Context AI",
6
6
  "license": "MIT",