@iaforged/context-code 1.0.89 → 1.0.91

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.
@@ -88,12 +88,19 @@ export async function call(onDone, context, args) {
88
88
  targetProvider: 'minimax',
89
89
  targetProfileName: profileName,
90
90
  })
91
- : switchProviderPreference({
92
- currentModel: previousModel,
93
- currentProvider: previousProvider,
94
- targetProvider: 'claude',
95
- targetProfileName: profileName,
96
- });
91
+ : targetProvider === 'nvidia'
92
+ ? switchProviderPreference({
93
+ currentModel: previousModel,
94
+ currentProvider: previousProvider,
95
+ targetProvider: 'nvidia',
96
+ targetProfileName: profileName,
97
+ })
98
+ : switchProviderPreference({
99
+ currentModel: previousModel,
100
+ currentProvider: previousProvider,
101
+ targetProvider: 'claude',
102
+ targetProfileName: profileName,
103
+ });
97
104
  resetCostState();
98
105
  void refreshRemoteManagedSettings();
99
106
  void refreshPolicyLimits();
@@ -1,9 +1,12 @@
1
1
  import { CHANGELOG_URL, fetchAndStoreChangelog, getAllReleaseNotes, getStoredChangelog, } from '../../utils/releaseNotes.js';
2
+ import { translateReleaseNoteToSpanish } from '../../utils/releaseNoteTranslations.js';
2
3
  function formatReleaseNotes(notes) {
3
4
  return notes
4
5
  .map(([version, notes]) => {
5
- const header = `Version ${version}:`;
6
- const bulletPoints = notes.map(note => `· ${note}`).join('\n');
6
+ const header = `Versión ${version}:`;
7
+ const bulletPoints = notes
8
+ .map(note => `- ${translateReleaseNoteToSpanish(note)}`)
9
+ .join('\n');
7
10
  return `${header}\n${bulletPoints}`;
8
11
  })
9
12
  .join('\n\n');
@@ -33,6 +36,6 @@ export async function call() {
33
36
  // Nothing available, show link
34
37
  return {
35
38
  type: 'text',
36
- value: `See the full changelog at: ${CHANGELOG_URL}`,
39
+ value: `Ver changelog completo en: ${CHANGELOG_URL}`,
37
40
  };
38
41
  }
@@ -27,6 +27,7 @@ const LOGIN_PROVIDER_ORDER = [
27
27
  'minimax',
28
28
  'openrouter',
29
29
  'zai',
30
+ 'nvidia',
30
31
  'ollama',
31
32
  'openai',
32
33
  'claudeai',
@@ -144,6 +145,15 @@ export function ConsoleOAuthFlow({ onDone, startingMessage, mode = 'login', forc
144
145
  context: 'Confirmation',
145
146
  isActive: oauthStatus.state === 'error' && !!oauthStatus.toRetry
146
147
  });
148
+ // Handle Esc to cancel from API key input or other setup states
149
+ useKeybinding('cancel', () => {
150
+ setOAuthStatus({
151
+ state: 'idle'
152
+ });
153
+ }, {
154
+ context: 'Confirmation',
155
+ isActive: oauthStatus.state === 'provider_api_key_input' || oauthStatus.state === 'provider_local_setup' || oauthStatus.state === 'provider_google_setup' || oauthStatus.state === 'error'
156
+ });
147
157
  useEffect(() => {
148
158
  if (pastedCode === 'c' && oauthStatus.state === 'waiting_for_login' && showPastePrompt && !urlCopied) {
149
159
  void setClipboard(oauthStatus.url).then(raw => {
@@ -206,9 +216,6 @@ export function ConsoleOAuthFlow({ onDone, startingMessage, mode = 'login', forc
206
216
  await saveProviderApiKey(provider, trimmedValue);
207
217
  setProviderApiKey('');
208
218
  setProviderApiKeyCursorOffset(0);
209
- setOAuthStatus({
210
- state: 'success'
211
- });
212
219
  const providerLabel = getVisibleProvider(provider).label;
213
220
  void sendNotification({
214
221
  message: `API key de ${providerLabel} guardada`,
@@ -491,6 +498,9 @@ function OAuthStatusMessage(t0) {
491
498
  }, {
492
499
  label: _jsxs(Text, { children: ["MiniMax \u00B7", " ", _jsx(Text, { dimColor: true, children: "API key / Anthropic-compatible" }), "\n"] }),
493
500
  value: "minimax"
501
+ }, {
502
+ label: _jsxs(Text, { children: ["NVIDIA API \u00B7", " ", _jsx(Text, { dimColor: true, children: "API key / build.nvidia.com" }), "\n"] }),
503
+ value: "nvidia"
494
504
  }];
495
505
  $[5] = t6;
496
506
  }
@@ -520,7 +530,7 @@ function OAuthStatusMessage(t0) {
520
530
  provider: "openrouter"
521
531
  });
522
532
  }
523
- else if (value_0 === "gemini-api" || value_0 === "zai" || value_0 === "minimax") {
533
+ else if (value_0 === "gemini-api" || value_0 === "zai" || value_0 === "minimax" || value_0 === "nvidia") {
524
534
  setOAuthStatus({
525
535
  state: "provider_api_key_input",
526
536
  provider: value_0
@@ -5,6 +5,7 @@ import { Box, Text } from '../../ink.js';
5
5
  import { formatCreditAmount, getCachedReferrerReward } from '../../services/api/referral.js';
6
6
  import { getCwd } from '../../utils/cwd.js';
7
7
  import { formatRelativeTimeAgo } from '../../utils/format.js';
8
+ import { translateReleaseNoteToSpanish } from '../../utils/releaseNoteTranslations.js';
8
9
  export function createRecentActivityFeed(activities) {
9
10
  const lines = activities.map(log => {
10
11
  const time = formatRelativeTimeAgo(log.modified);
@@ -33,12 +34,12 @@ export function createWhatsNewFeed(releaseNotes) {
33
34
  }
34
35
  }
35
36
  return {
36
- text: note
37
+ text: translateReleaseNoteToSpanish(note)
37
38
  };
38
39
  });
39
- const emptyMessage = "external" === 'ant' ? 'Unable to fetch latest claude-cli-internal commits' : 'Check the Context Code changelog for updates';
40
+ const emptyMessage = "external" === 'ant' ? 'No se pudieron obtener los últimos commits de claude-cli-internal' : 'Consulta el changelog de Context Code para ver actualizaciones';
40
41
  return {
41
- title: "external" === 'ant' ? "What's new [ANT-ONLY: Latest CC commits]" : "What's new",
42
+ title: "external" === 'ant' ? 'Novedades [SOLO ANT: últimos commits de CC]' : 'Novedades',
42
43
  lines,
43
44
  footer: lines.length > 0 ? '/release-notes para ver más' : undefined,
44
45
  emptyMessage
@@ -63,7 +63,8 @@ export function ModelPicker(t0) {
63
63
  provider === 'gemini-api' ||
64
64
  provider === 'gemini-google' ||
65
65
  provider === 'zai' ||
66
- provider === 'minimax') {
66
+ provider === 'minimax' ||
67
+ provider === 'nvidia') {
67
68
  const { getCachedProviderModels, fetchProviderModels } = await import('../utils/model/providerModels.js');
68
69
  const cached = getCachedProviderModels(provider);
69
70
  if (cached) {
@@ -13,7 +13,7 @@ function formatShortcut(shortcut) {
13
13
  return shortcut.replace(/\+/g, ' + ');
14
14
  }
15
15
  export function PromptInputHelpMenu(props) {
16
- const $ = _c(99);
16
+ const $ = _c(110);
17
17
  const { dimColor, fixedWidth, gap, paddingX } = props;
18
18
  const t0 = useShortcutDisplay("app:toggleTranscript", "Global", "ctrl+o");
19
19
  let t1;
@@ -70,6 +70,17 @@ export function PromptInputHelpMenu(props) {
70
70
  t9 = $[9];
71
71
  }
72
72
  const cycleModeShortcut = t9;
73
+ const tThinking = useShortcutDisplay("chat:thinkingToggle", "Chat", "meta+t");
74
+ let tThinkingFormatted;
75
+ if ($[100] !== tThinking) {
76
+ tThinkingFormatted = formatShortcut(tThinking);
77
+ $[100] = tThinking;
78
+ $[101] = tThinkingFormatted;
79
+ }
80
+ else {
81
+ tThinkingFormatted = $[101];
82
+ }
83
+ const thinkingShortcut = tThinkingFormatted;
73
84
  const t10 = useShortcutDisplay("chat:modelPicker", "Chat", "alt+p");
74
85
  let t11;
75
86
  if ($[10] !== t10) {
@@ -208,7 +219,7 @@ export function PromptInputHelpMenu(props) {
208
219
  }
209
220
  let t30;
210
221
  if ($[42] !== cycleModeShortcut || $[43] !== dimColor) {
211
- t30 = _jsx(Box, { children: _jsxs(Text, { dimColor: dimColor, children: [cycleModeShortcut, " ", false ? "para cambiar modos" : "para auto-aceptar ediciones"] }) });
222
+ t30 = _jsx(Box, { children: _jsxs(Text, { dimColor: dimColor, children: [cycleModeShortcut, " ", "external" === 'ant' ? "para alternar modos" : "para alternar entre modo predeterminado, auto-aceptar y plan"] }) });
212
223
  $[42] = cycleModeShortcut;
213
224
  $[43] = dimColor;
214
225
  $[44] = t30;
@@ -268,6 +279,16 @@ export function PromptInputHelpMenu(props) {
268
279
  else {
269
280
  t35 = $[61];
270
281
  }
282
+ let tThinkingElement;
283
+ if ($[102] !== dimColor || $[103] !== thinkingShortcut) {
284
+ tThinkingElement = _jsx(Box, { children: _jsxs(Text, { dimColor: dimColor, children: [thinkingShortcut, " para alternar pensamiento"] }) });
285
+ $[102] = dimColor;
286
+ $[103] = thinkingShortcut;
287
+ $[104] = tThinkingElement;
288
+ }
289
+ else {
290
+ tThinkingElement = $[104];
291
+ }
271
292
  let t36;
272
293
  if ($[62] !== dimColor || $[63] !== undoShortcut) {
273
294
  t36 = _jsx(Box, { children: _jsxs(Text, { dimColor: dimColor, children: [undoShortcut, " para deshacer"] }) });
@@ -347,8 +368,8 @@ export function PromptInputHelpMenu(props) {
347
368
  t43 = $[83];
348
369
  }
349
370
  let t44;
350
- if ($[84] !== t36 || $[85] !== t37 || $[86] !== t38 || $[87] !== t39 || $[88] !== t40 || $[89] !== t41 || $[90] !== t42 || $[91] !== t43) {
351
- t44 = _jsxs(Box, { flexDirection: "column", children: [t36, t37, t38, t39, t40, t41, t42, t43] });
371
+ if ($[84] !== t36 || $[85] !== t37 || $[86] !== t38 || $[87] !== t39 || $[88] !== t40 || $[89] !== t41 || $[90] !== t42 || $[91] !== t43 || $[105] !== tThinkingElement) {
372
+ t44 = _jsxs(Box, { flexDirection: "column", children: [tThinkingElement, t36, t37, t38, t39, t40, t41, t42, t43] });
352
373
  $[84] = t36;
353
374
  $[85] = t37;
354
375
  $[86] = t38;
@@ -18,12 +18,12 @@ export function ThinkingToggle(t0) {
18
18
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
19
19
  t1 = [{
20
20
  value: "true",
21
- label: "Enabled",
22
- description: "Claude will think before responding"
21
+ label: "Activado",
22
+ description: "Context pensará antes de responder"
23
23
  }, {
24
24
  value: "false",
25
- label: "Disabled",
26
- description: "Claude will respond without extended thinking"
25
+ label: "Desactivado",
26
+ description: "Context responderá sin un pensamiento extendido"
27
27
  }];
28
28
  $[0] = t1;
29
29
  }
@@ -109,7 +109,7 @@ export function ThinkingToggle(t0) {
109
109
  const handleSelectChange = t7;
110
110
  let t8;
111
111
  if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
112
- t8 = _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { color: "remember", bold: true, children: "Toggle thinking mode" }), _jsx(Text, { dimColor: true, children: "Enable or disable thinking for this session." })] });
112
+ t8 = _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { color: "remember", bold: true, children: "Alternar modo de pensamiento" }), _jsx(Text, { dimColor: true, children: "Activa o desactiva el pensamiento para esta sesi\u00F3n." })] });
113
113
  $[14] = t8;
114
114
  }
115
115
  else {
@@ -117,7 +117,7 @@ export function ThinkingToggle(t0) {
117
117
  }
118
118
  let t9;
119
119
  if ($[15] !== confirmationPending || $[16] !== currentValue || $[17] !== handleSelectChange || $[18] !== onCancel) {
120
- t9 = _jsxs(Box, { flexDirection: "column", children: [t8, confirmationPending !== null ? _jsxs(Box, { flexDirection: "column", marginBottom: 1, gap: 1, children: [_jsx(Text, { color: "warning", children: "Changing thinking mode mid-conversation will increase latency and may reduce quality. For best results, set this at the start of a session." }), _jsx(Text, { color: "warning", children: "Do you want to proceed?" })] }) : _jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(Select, { defaultValue: currentValue ? "true" : "false", defaultFocusValue: currentValue ? "true" : "false", options: options, onChange: handleSelectChange, onCancel: onCancel ?? _temp, visibleOptionCount: 2 }) })] });
120
+ t9 = _jsxs(Box, { flexDirection: "column", children: [t8, confirmationPending !== null ? _jsxs(Box, { flexDirection: "column", marginBottom: 1, gap: 1, children: [_jsx(Text, { color: "warning", children: "Cambiar el modo de pensamiento a mitad de la conversaci\u00F3n aumentar\u00E1 la latencia y puede reducir la calidad. Para obtener mejores resultados, config\u00FAralo al inicio de la sesi\u00F3n." }), _jsx(Text, { color: "warning", children: "\u00BFDeseas continuar?" })] }) : _jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(Select, { defaultValue: currentValue ? "true" : "false", defaultFocusValue: currentValue ? "true" : "false", options: options, onChange: handleSelectChange, onCancel: onCancel ?? _temp, visibleOptionCount: 2 }) })] });
121
121
  $[15] = confirmationPending;
122
122
  $[16] = currentValue;
123
123
  $[17] = handleSelectChange;
@@ -129,7 +129,7 @@ export function ThinkingToggle(t0) {
129
129
  }
130
130
  let t10;
131
131
  if ($[20] !== confirmationPending || $[21] !== exitState.keyName || $[22] !== exitState.pending) {
132
- t10 = _jsx(Text, { dimColor: true, italic: true, children: exitState.pending ? _jsxs(_Fragment, { children: ["Press ", exitState.keyName, " again to exit"] }) : confirmationPending !== null ? _jsxs(Byline, { children: [_jsx(KeyboardShortcutHint, { shortcut: "Enter", action: "confirm" }), _jsx(ConfigurableShortcutHint, { action: "confirm:no", context: "Confirmation", fallback: "Esc", description: "cancel" })] }) : _jsxs(Byline, { children: [_jsx(KeyboardShortcutHint, { shortcut: "Enter", action: "confirm" }), _jsx(ConfigurableShortcutHint, { action: "confirm:no", context: "Confirmation", fallback: "Esc", description: "exit" })] }) });
132
+ t10 = _jsx(Text, { dimColor: true, italic: true, children: exitState.pending ? _jsxs(_Fragment, { children: ["Presiona ", exitState.keyName, " de nuevo para salir"] }) : confirmationPending !== null ? _jsxs(Byline, { children: [_jsx(KeyboardShortcutHint, { shortcut: "Enter", action: "confirm" }), _jsx(ConfigurableShortcutHint, { action: "confirm:no", context: "Confirmation", fallback: "Esc", description: "cancelar" })] }) : _jsxs(Byline, { children: [_jsx(KeyboardShortcutHint, { shortcut: "Enter", action: "confirm" }), _jsx(ConfigurableShortcutHint, { action: "confirm:no", context: "Confirmation", fallback: "Esc", description: "salir" })] }) });
133
133
  $[20] = confirmationPending;
134
134
  $[21] = exitState.keyName;
135
135
  $[22] = exitState.pending;
@@ -1564,7 +1564,7 @@ export function useTypeahead({ commands, onInputChange, onSubmit, setCursorOffse
1564
1564
  e.preventDefault();
1565
1565
  addNotification({
1566
1566
  key: 'thinking-toggle-hint',
1567
- jsx: _jsxs(Text, { dimColor: true, children: ["Use ", thinkingToggleShortcut, " to toggle thinking"] }),
1567
+ jsx: _jsxs(Text, { dimColor: true, children: ["Usa ", thinkingToggleShortcut, " para alternar el modo de razonamiento"] }),
1568
1568
  priority: 'immediate',
1569
1569
  timeoutMs: 3000
1570
1570
  });
@@ -51,7 +51,8 @@ function getOpenAICompatibleProvider() {
51
51
  provider === 'ollama-cloud' ||
52
52
  provider === 'gemini-api' ||
53
53
  provider === 'gemini-google' ||
54
- provider === 'zai'
54
+ provider === 'zai' ||
55
+ provider === 'nvidia'
55
56
  ? provider
56
57
  : 'openai';
57
58
  }
@@ -69,6 +70,8 @@ function getOpenAICompatibleProviderLabel(provider = getOpenAICompatibleProvider
69
70
  return 'Gemini Google';
70
71
  case 'zai':
71
72
  return 'Z.AI';
73
+ case 'nvidia':
74
+ return 'NVIDIA NIM';
72
75
  default:
73
76
  return 'OpenAI';
74
77
  }
@@ -124,6 +127,14 @@ function getOpenAIBaseUrl(provider = getOpenAICompatibleProvider()) {
124
127
  ? raw.slice(0, -1)
125
128
  : raw;
126
129
  }
130
+ if (provider === 'nvidia') {
131
+ const raw = process.env.NVIDIA_BASE_URL ||
132
+ getConfiguredProviderBaseUrl('nvidia') ||
133
+ 'https://integrate.api.nvidia.com/v1';
134
+ return raw.endsWith('/')
135
+ ? raw.slice(0, -1)
136
+ : raw;
137
+ }
127
138
  const raw = process.env.OPENAI_API_BASE_URL ||
128
139
  process.env.OPENAI_BASE_URL ||
129
140
  'https://api.openai.com/v1';
@@ -1239,33 +1250,56 @@ async function createChatCompletionResponse({ messages, systemPrompt, tools, sig
1239
1250
  temperature: options.temperatureOverride,
1240
1251
  });
1241
1252
  }
1242
- const sendRequest = async (bearerToken = getOpenAICompatibleAccessToken(provider) ?? '') => {
1253
+ const sendRequest = async (bearerToken = getOpenAICompatibleAccessToken(provider) ?? '', retryCount = 0) => {
1243
1254
  const url = `${getOpenAIBaseUrl(provider)}/chat/completions`;
1244
- logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] chat.completions request model=${options.model} messages=${chatMessages.length} tools=${chatTools.length} endpoint=${url}`);
1245
- const response = await fetch(url, {
1246
- method: 'POST',
1247
- headers: await getOpenAIRequestHeaders(provider),
1248
- body: jsonStringify(body),
1249
- signal,
1250
- });
1251
- const data = await readOpenAIResponseBody(response);
1252
- if (response.status === 401) {
1253
- const refreshedToken = await maybeRecoverOpenAICompatible401(bearerToken, provider);
1254
- if (refreshedToken) {
1255
- return sendRequest(refreshedToken);
1255
+ logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] chat.completions request model=${options.model} messages=${chatMessages.length} tools=${chatTools.length} endpoint=${url}${retryCount > 0 ? ` (retry ${retryCount})` : ''}`);
1256
+ const timeoutMs = 120000;
1257
+ const controller = new AbortController();
1258
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1259
+ const onAbort = () => controller.abort();
1260
+ signal?.addEventListener('abort', onAbort);
1261
+ try {
1262
+ const response = await fetch(url, {
1263
+ method: 'POST',
1264
+ headers: await getOpenAIRequestHeaders(provider),
1265
+ body: jsonStringify(body),
1266
+ signal: controller.signal,
1267
+ });
1268
+ const data = (await readOpenAIResponseBody(response));
1269
+ if (response.status === 504 && retryCount < 1) {
1270
+ logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] 504 Gateway Timeout, reintentando...`, { level: 'warn' });
1271
+ return sendRequest(bearerToken, retryCount + 1);
1272
+ }
1273
+ if (response.status === 401) {
1274
+ const refreshedToken = await maybeRecoverOpenAICompatible401(bearerToken, provider);
1275
+ if (refreshedToken) {
1276
+ return sendRequest(refreshedToken, retryCount);
1277
+ }
1278
+ }
1279
+ if (!response.ok) {
1280
+ logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] chat.completions error status=${response.status} model=${options.model} endpoint=${url} body=${(data.error?.message ?? '').replace(/\s+/g, ' ').slice(0, 500)}`, { level: 'error' });
1281
+ throw new Error(buildOpenAIErrorMessage({
1282
+ status: response.status,
1283
+ statusText: response.statusText,
1284
+ url,
1285
+ model: String(options.model),
1286
+ data,
1287
+ }));
1256
1288
  }
1289
+ return data;
1257
1290
  }
1258
- if (!response.ok) {
1259
- logForDebugging(`[${getOpenAICompatibleProviderLabel(provider)}] chat.completions error status=${response.status} model=${options.model} endpoint=${url} body=${(data.error?.message ?? '').replace(/\s+/g, ' ').slice(0, 500)}`, { level: 'error' });
1260
- throw new Error(buildOpenAIErrorMessage({
1261
- status: response.status,
1262
- statusText: response.statusText,
1263
- url,
1264
- model: String(options.model),
1265
- data,
1266
- }));
1291
+ catch (err) {
1292
+ if (err instanceof Error &&
1293
+ err.name === 'AbortError' &&
1294
+ !signal?.aborted) {
1295
+ throw new Error(`La solicitud expiró tras ${timeoutMs / 1000}s. El servidor tardó demasiado en responder.`);
1296
+ }
1297
+ throw err;
1298
+ }
1299
+ finally {
1300
+ clearTimeout(timeoutId);
1301
+ signal?.removeEventListener('abort', onAbort);
1267
1302
  }
1268
- return data;
1269
1303
  };
1270
1304
  getOpenAIAccessTokenOrThrow(provider);
1271
1305
  return sendRequest();
@@ -59,7 +59,7 @@ async function isMarketplacePluginRelevant(pluginName, context, signals) {
59
59
  const externalTips = [
60
60
  {
61
61
  id: 'new-user-warmup',
62
- content: async () => `Start with small features or bug fixes, tell Claude to propose a plan, and verify its suggested edits`,
62
+ content: async () => `Comienza con pequeñas funciones o correcciones, pide a Context que proponga un plan y verifica sus ediciones sugeridas`,
63
63
  cooldownSessions: 3,
64
64
  async isRelevant() {
65
65
  const config = getGlobalConfig();
@@ -68,7 +68,7 @@ const externalTips = [
68
68
  },
69
69
  {
70
70
  id: 'plan-mode-for-complex-tasks',
71
- content: async () => `Use Plan Mode to prepare for a complex request before making changes. Press ${getShortcutDisplay('chat:cycleMode', 'Chat', 'shift+tab')} twice to enable.`,
71
+ content: async () => `Usa el Modo Plan para preparar una solicitud compleja antes de realizar cambios. Presiona ${getShortcutDisplay('chat:cycleMode', 'Chat', 'shift+tab')} dos veces para activarlo.`,
72
72
  cooldownSessions: 5,
73
73
  isRelevant: async () => {
74
74
  if (process.env.USER_TYPE === 'ant')
@@ -83,7 +83,7 @@ const externalTips = [
83
83
  },
84
84
  {
85
85
  id: 'default-permission-mode-config',
86
- content: async () => `Use /config to change your default permission mode (including Plan Mode)`,
86
+ content: async () => `Usa /config para cambiar tu modo de permisos predeterminado (incluyendo el Modo Plan)`,
87
87
  cooldownSessions: 10,
88
88
  isRelevant: async () => {
89
89
  try {
@@ -102,7 +102,7 @@ const externalTips = [
102
102
  },
103
103
  {
104
104
  id: 'git-worktrees',
105
- content: async () => 'Use git worktrees to run multiple Claude sessions in parallel.',
105
+ content: async () => 'Usa git worktrees para ejecutar múltiples sesiones de Context en paralelo.',
106
106
  cooldownSessions: 10,
107
107
  isRelevant: async () => {
108
108
  try {
@@ -117,7 +117,7 @@ const externalTips = [
117
117
  },
118
118
  {
119
119
  id: 'color-when-multi-clauding',
120
- content: async () => 'Running multiple Claude sessions? Use /color and /rename to tell them apart at a glance.',
120
+ content: async () => '¿Ejecutando múltiples sesiones de Context? Usa /color y /rename para distinguirlas de un vistazo.',
121
121
  cooldownSessions: 10,
122
122
  isRelevant: async () => {
123
123
  if (getCurrentSessionAgentColor())
@@ -129,8 +129,8 @@ const externalTips = [
129
129
  {
130
130
  id: 'terminal-setup',
131
131
  content: async () => env.terminal === 'Apple_Terminal'
132
- ? 'Run /terminal-setup to enable convenient terminal integration like Option + Enter for new line and more'
133
- : 'Run /terminal-setup to enable convenient terminal integration like Shift + Enter for new line and more',
132
+ ? 'Ejecuta /terminal-setup para habilitar la integración del terminal, como Option + Enter para nueva línea y más'
133
+ : 'Ejecuta /terminal-setup para habilitar la integración del terminal, como Shift + Enter para nueva línea y más',
134
134
  cooldownSessions: 10,
135
135
  async isRelevant() {
136
136
  const config = getGlobalConfig();
@@ -143,8 +143,8 @@ const externalTips = [
143
143
  {
144
144
  id: 'shift-enter',
145
145
  content: async () => env.terminal === 'Apple_Terminal'
146
- ? 'Press Option+Enter to send a multi-line message'
147
- : 'Press Shift+Enter to send a multi-line message',
146
+ ? 'Presiona Option+Enter para enviar un mensaje multilínea'
147
+ : 'Presiona Shift+Enter para enviar un mensaje multilínea',
148
148
  cooldownSessions: 10,
149
149
  async isRelevant() {
150
150
  const config = getGlobalConfig();
@@ -156,8 +156,8 @@ const externalTips = [
156
156
  {
157
157
  id: 'shift-enter-setup',
158
158
  content: async () => env.terminal === 'Apple_Terminal'
159
- ? 'Run /terminal-setup to enable Option+Enter for new lines'
160
- : 'Run /terminal-setup to enable Shift+Enter for new lines',
159
+ ? 'Ejecuta /terminal-setup para habilitar Option+Enter para nuevas líneas'
160
+ : 'Ejecuta /terminal-setup para habilitar Shift+Enter para nuevas líneas',
161
161
  cooldownSessions: 10,
162
162
  async isRelevant() {
163
163
  if (!shouldOfferTerminalSetup()) {
@@ -171,7 +171,7 @@ const externalTips = [
171
171
  },
172
172
  {
173
173
  id: 'memory-command',
174
- content: async () => 'Use /memory to view and manage Claude memory',
174
+ content: async () => 'Usa /memory para ver y gestionar la memoria de Context',
175
175
  cooldownSessions: 15,
176
176
  async isRelevant() {
177
177
  const config = getGlobalConfig();
@@ -180,32 +180,32 @@ const externalTips = [
180
180
  },
181
181
  {
182
182
  id: 'theme-command',
183
- content: async () => 'Use /theme to change the color theme',
183
+ content: async () => 'Usa /theme para cambiar el tema de color',
184
184
  cooldownSessions: 20,
185
185
  isRelevant: async () => true,
186
186
  },
187
187
  {
188
188
  id: 'colorterm-truecolor',
189
- content: async () => 'Try setting environment variable COLORTERM=truecolor for richer colors',
189
+ content: async () => 'Prueba a configurar la variable de entorno COLORTERM=truecolor para obtener colores más vivos',
190
190
  cooldownSessions: 30,
191
191
  isRelevant: async () => !process.env.COLORTERM && chalk.level < 3,
192
192
  },
193
193
  {
194
194
  id: 'powershell-tool-env',
195
- content: async () => 'Set CLAUDE_CODE_USE_POWERSHELL_TOOL=1 to enable the PowerShell tool (preview)',
195
+ content: async () => 'Configura CLAUDE_CODE_USE_POWERSHELL_TOOL=1 para habilitar la herramienta PowerShell (vista previa)',
196
196
  cooldownSessions: 10,
197
197
  isRelevant: async () => getPlatform() === 'windows' &&
198
198
  process.env.CLAUDE_CODE_USE_POWERSHELL_TOOL === undefined,
199
199
  },
200
200
  {
201
201
  id: 'status-line',
202
- content: async () => 'Use /statusline to set up a custom status line that will display beneath the input box',
202
+ content: async () => 'Usa /statusline para configurar una línea de estado personalizada que se mostrará debajo del cuadro de entrada',
203
203
  cooldownSessions: 25,
204
204
  isRelevant: async () => getSettings_DEPRECATED().statusLine === undefined,
205
205
  },
206
206
  {
207
207
  id: 'prompt-queue',
208
- content: async () => 'Hit Enter to queue up additional messages while Claude is working.',
208
+ content: async () => 'Pulsa Enter para poner en cola mensajes adicionales mientras Context está trabajando.',
209
209
  cooldownSessions: 5,
210
210
  async isRelevant() {
211
211
  const config = getGlobalConfig();
@@ -214,19 +214,19 @@ const externalTips = [
214
214
  },
215
215
  {
216
216
  id: 'enter-to-steer-in-relatime',
217
- content: async () => 'Send messages to Claude while it works to steer Claude in real-time',
217
+ content: async () => 'Envía mensajes a Context mientras trabaja para guiarlo en tiempo real',
218
218
  cooldownSessions: 20,
219
219
  isRelevant: async () => true,
220
220
  },
221
221
  {
222
222
  id: 'todo-list',
223
- content: async () => 'Ask Claude to create a todo list when working on complex tasks to track progress and remain on track',
223
+ content: async () => 'Pide a Context que cree una lista de tareas cuando trabajes en tareas complejas para seguir el progreso',
224
224
  cooldownSessions: 20,
225
225
  isRelevant: async () => true,
226
226
  },
227
227
  {
228
228
  id: 'vscode-command-install',
229
- content: async () => `Open the Command Palette (Cmd+Shift+P) and run "Shell Command: Install '${env.terminal === 'vscode' ? 'code' : env.terminal}' command in PATH" to enable IDE integration`,
229
+ content: async () => `Abre la Paleta de Comandos (Cmd+Shift+P) y ejecuta "Shell Command: Install '${env.terminal === 'vscode' ? 'code' : env.terminal}' command in PATH" para habilitar la integración con el IDE`,
230
230
  cooldownSessions: 0,
231
231
  async isRelevant() {
232
232
  // Only show this tip if we're in a VS Code-style terminal
@@ -251,7 +251,7 @@ const externalTips = [
251
251
  },
252
252
  {
253
253
  id: 'ide-upsell-external-terminal',
254
- content: async () => 'Connect Claude to your IDE · /ide',
254
+ content: async () => 'Conecta Context a tu IDE · /ide',
255
255
  cooldownSessions: 4,
256
256
  async isRelevant() {
257
257
  if (isSupportedTerminal()) {
@@ -268,19 +268,19 @@ const externalTips = [
268
268
  },
269
269
  {
270
270
  id: 'install-github-app',
271
- content: async () => 'Run /install-github-app to tag @claude right from your Github issues and PRs',
271
+ content: async () => 'Ejecuta /install-github-app para etiquetar a @claude directamente desde tus issues y PRs de Github',
272
272
  cooldownSessions: 10,
273
273
  isRelevant: async () => !getGlobalConfig().githubActionSetupCount,
274
274
  },
275
275
  {
276
276
  id: 'install-slack-app',
277
- content: async () => 'Run /install-slack-app to use Claude in Slack',
277
+ content: async () => 'Ejecuta /install-slack-app para usar Context en Slack',
278
278
  cooldownSessions: 10,
279
279
  isRelevant: async () => !getGlobalConfig().slackAppInstallCount,
280
280
  },
281
281
  {
282
282
  id: 'permissions',
283
- content: async () => 'Use /permissions to pre-approve and pre-deny bash, edit, and MCP tools',
283
+ content: async () => 'Usa /permissions para pre-aprobar y pre-denegar herramientas de bash, edición y MCP',
284
284
  cooldownSessions: 10,
285
285
  async isRelevant() {
286
286
  const config = getGlobalConfig();
@@ -289,25 +289,25 @@ const externalTips = [
289
289
  },
290
290
  {
291
291
  id: 'drag-and-drop-images',
292
- content: async () => 'Did you know you can drag and drop image files into your terminal?',
292
+ content: async () => '¿Sabías que puedes arrastrar y soltar archivos de imagen en tu terminal?',
293
293
  cooldownSessions: 10,
294
294
  isRelevant: async () => !env.isSSH(),
295
295
  },
296
296
  {
297
297
  id: 'paste-images-mac',
298
- content: async () => 'Paste images into Context Code using control+v (not cmd+v!)',
298
+ content: async () => 'Pega imágenes en Context Code usando control+v (¡no cmd+v!)',
299
299
  cooldownSessions: 10,
300
300
  isRelevant: async () => getPlatform() === 'macos',
301
301
  },
302
302
  {
303
303
  id: 'double-esc',
304
- content: async () => 'Double-tap esc to rewind the conversation to a previous point in time',
304
+ content: async () => 'Pulsa dos veces esc para rebobinar la conversación a un punto anterior en el tiempo',
305
305
  cooldownSessions: 10,
306
306
  isRelevant: async () => !fileHistoryEnabled(),
307
307
  },
308
308
  {
309
309
  id: 'double-esc-code-restore',
310
- content: async () => 'Double-tap esc to rewind the code and/or conversation to a previous point in time',
310
+ content: async () => 'Pulsa dos veces esc para rebobinar el código y/o la conversación a un punto anterior en el tiempo',
311
311
  cooldownSessions: 10,
312
312
  isRelevant: async () => fileHistoryEnabled(),
313
313
  },
@@ -319,13 +319,13 @@ const externalTips = [
319
319
  },
320
320
  {
321
321
  id: 'rename-conversation',
322
- content: async () => 'Name your conversations with /rename to find them easily in /resume later',
322
+ content: async () => 'Nombra tus conversaciones con /rename para encontrarlas fácilmente en /resume más tarde',
323
323
  cooldownSessions: 15,
324
324
  isRelevant: async () => isCustomTitleEnabled() && getGlobalConfig().numStartups > 10,
325
325
  },
326
326
  {
327
327
  id: 'custom-commands',
328
- content: async () => 'Create skills by adding .md files to .context/skills/ in your project or ~/.context/skills/ for skills that work in any project',
328
+ content: async () => 'Crea habilidades añadiendo archivos .md a .context/skills/ en tu proyecto o ~/.context/skills/ para habilidades globales',
329
329
  cooldownSessions: 15,
330
330
  async isRelevant() {
331
331
  const config = getGlobalConfig();
@@ -335,20 +335,20 @@ const externalTips = [
335
335
  {
336
336
  id: 'shift-tab',
337
337
  content: async () => process.env.USER_TYPE === 'ant'
338
- ? `Hit ${getShortcutDisplay('chat:cycleMode', 'Chat', 'shift+tab')} to cycle between default mode and auto mode`
339
- : `Hit ${getShortcutDisplay('chat:cycleMode', 'Chat', 'shift+tab')} to cycle between default mode, auto-accept edit mode, and plan mode`,
338
+ ? `Pulsa ${getShortcutDisplay('chat:cycleMode', 'Chat', 'shift+tab')} para alternar entre el modo predeterminado y el modo automático`
339
+ : `Pulsa ${getShortcutDisplay('chat:cycleMode', 'Chat', 'shift+tab')} para alternar entre el modo predeterminado, el modo de edición con auto-aceptación y el modo de planificación`,
340
340
  cooldownSessions: 10,
341
341
  isRelevant: async () => true,
342
342
  },
343
343
  {
344
344
  id: 'image-paste',
345
- content: async () => `Use ${getShortcutDisplay('chat:imagePaste', 'Chat', 'ctrl+v')} to paste images from your clipboard`,
345
+ content: async () => `Usa ${getShortcutDisplay('chat:imagePaste', 'Chat', 'ctrl+v')} para pegar imágenes desde tu portapapeles`,
346
346
  cooldownSessions: 20,
347
347
  isRelevant: async () => true,
348
348
  },
349
349
  {
350
350
  id: 'custom-agents',
351
- content: async () => 'Use /agents to optimize specific tasks. Eg. Software Architect, Code Writer, Code Reviewer',
351
+ content: async () => 'Usa /agents para optimizar tareas específicas. Ej: Arquitecto de Software, Escritor de Código, Revisor de Código',
352
352
  cooldownSessions: 15,
353
353
  async isRelevant() {
354
354
  const config = getGlobalConfig();
@@ -357,7 +357,7 @@ const externalTips = [
357
357
  },
358
358
  {
359
359
  id: 'agent-flag',
360
- content: async () => 'Use --agent <agent_name> to directly start a conversation with a subagent',
360
+ content: async () => 'Usa --agent <nombre_agente> para iniciar directamente una conversación con un subagente',
361
361
  cooldownSessions: 15,
362
362
  async isRelevant() {
363
363
  const config = getGlobalConfig();
@@ -366,7 +366,7 @@ const externalTips = [
366
366
  },
367
367
  {
368
368
  id: 'desktop-app',
369
- content: async () => 'Run Context Code locally or remotely using the Claude desktop app: clau.de/desktop',
369
+ content: async () => 'Ejecuta Context Code local o remotamente usando la aplicación de escritorio: clau.de/desktop',
370
370
  cooldownSessions: 15,
371
371
  isRelevant: async () => getPlatform() !== 'linux',
372
372
  },
@@ -374,7 +374,7 @@ const externalTips = [
374
374
  id: 'desktop-shortcut',
375
375
  content: async (ctx) => {
376
376
  const blue = color('suggestion', ctx.theme);
377
- return `Continue your session in Context Code Desktop with ${blue('/desktop')}`;
377
+ return `Continúa tu sesión en Context Code Desktop con ${blue('/desktop')}`;
378
378
  },
379
379
  cooldownSessions: 15,
380
380
  isRelevant: async () => {
@@ -386,19 +386,19 @@ const externalTips = [
386
386
  },
387
387
  {
388
388
  id: 'web-app',
389
- content: async () => 'Run tasks in the cloud while you keep coding locally · clau.de/web',
389
+ content: async () => 'Ejecuta tareas en la nube mientras sigues programando localmente · clau.de/web',
390
390
  cooldownSessions: 15,
391
391
  isRelevant: async () => true,
392
392
  },
393
393
  {
394
394
  id: 'mobile-app',
395
- content: async () => '/mobile to use Context Code from the Claude app on your phone',
395
+ content: async () => '/mobile para usar Context Code desde la aplicación de Context en tu teléfono',
396
396
  cooldownSessions: 15,
397
397
  isRelevant: async () => true,
398
398
  },
399
399
  {
400
400
  id: 'opusplan-mode-reminder',
401
- content: async () => `Your default model setting is Opus Plan Mode. Press ${getShortcutDisplay('chat:cycleMode', 'Chat', 'shift+tab')} twice to activate Plan Mode and plan with Claude Opus.`,
401
+ content: async () => `Tu modelo predeterminado es Opus Plan Mode. Presiona ${getShortcutDisplay('chat:cycleMode', 'Chat', 'shift+tab')} dos veces para activar el Modo Plan y planificar con Context Opus.`,
402
402
  cooldownSessions: 2,
403
403
  async isRelevant() {
404
404
  if (process.env.USER_TYPE === 'ant')
@@ -417,7 +417,7 @@ const externalTips = [
417
417
  id: 'frontend-design-plugin',
418
418
  content: async (ctx) => {
419
419
  const blue = color('suggestion', ctx.theme);
420
- return `Working with HTML/CSS? Install the frontend-design plugin:\n${blue(`/plugin install frontend-design@${OFFICIAL_MARKETPLACE_NAME}`)}`;
420
+ return `¿Trabajando con HTML/CSS? Instala el plugin frontend-design:\n${blue(`/plugin install frontend-design@${OFFICIAL_MARKETPLACE_NAME}`)}`;
421
421
  },
422
422
  cooldownSessions: 3,
423
423
  isRelevant: async (context) => isMarketplacePluginRelevant('frontend-design', context, {
@@ -428,7 +428,7 @@ const externalTips = [
428
428
  id: 'vercel-plugin',
429
429
  content: async (ctx) => {
430
430
  const blue = color('suggestion', ctx.theme);
431
- return `Working with Vercel? Install the vercel plugin:\n${blue(`/plugin install vercel@${OFFICIAL_MARKETPLACE_NAME}`)}`;
431
+ return `¿Trabajando con Vercel? Instala el plugin vercel:\n${blue(`/plugin install vercel@${OFFICIAL_MARKETPLACE_NAME}`)}`;
432
432
  },
433
433
  cooldownSessions: 3,
434
434
  isRelevant: async (context) => isMarketplacePluginRelevant('vercel', context, {
@@ -443,8 +443,8 @@ const externalTips = [
443
443
  const cmd = blue('/effort high');
444
444
  const variant = getFeatureValue_CACHED_MAY_BE_STALE('tengu_tide_elm', 'off');
445
445
  return variant === 'copy_b'
446
- ? `Use ${cmd} for better one-shot answers. Claude thinks it through first.`
447
- : `Working on something tricky? ${cmd} gives better first answers`;
446
+ ? `Usa ${cmd} para obtener mejores respuestas directas. Context lo analiza profundamente primero.`
447
+ : `¿Trabajando en algo difícil? ${cmd} ofrece mejores respuestas iniciales`;
448
448
  },
449
449
  cooldownSessions: 3,
450
450
  isRelevant: async () => {
@@ -469,8 +469,8 @@ const externalTips = [
469
469
  const blue = color('suggestion', ctx.theme);
470
470
  const variant = getFeatureValue_CACHED_MAY_BE_STALE('tengu_tern_alloy', 'off');
471
471
  return variant === 'copy_b'
472
- ? `For big tasks, tell Claude to ${blue('use subagents')}. They work in parallel and keep your main thread clean.`
473
- : `Say ${blue('"fan out subagents"')} and Claude sends a team. Each one digs deep so nothing gets missed.`;
472
+ ? `Para tareas grandes, pide a Context que ${blue('use subagentes')}. Trabajan en paralelo y mantienen limpia la conversación principal.`
473
+ : `Di ${blue('"fan out subagents"')} y Context enviará un equipo. Cada uno profundiza para que no se escape nada.`;
474
474
  },
475
475
  cooldownSessions: 3,
476
476
  isRelevant: async () => {
@@ -485,8 +485,8 @@ const externalTips = [
485
485
  const blue = color('suggestion', ctx.theme);
486
486
  const variant = getFeatureValue_CACHED_MAY_BE_STALE('tengu_timber_lark', 'off');
487
487
  return variant === 'copy_b'
488
- ? `Use ${blue('/loop 5m check the deploy')} to run any prompt on a schedule. Set it and forget it.`
489
- : `${blue('/loop')} runs any prompt on a recurring schedule. Great for monitoring deploys, babysitting PRs, or polling status.`;
488
+ ? `Usa ${blue('/loop 5m check the deploy')} para ejecutar cualquier prompt de forma programada. Configúralo y olvídate.`
489
+ : `${blue('/loop')} ejecuta cualquier prompt de forma recurrente. Ideal para monitorear despliegues o estados de PR.`;
490
490
  },
491
491
  cooldownSessions: 3,
492
492
  isRelevant: async () => {
@@ -500,11 +500,11 @@ const externalTips = [
500
500
  {
501
501
  id: 'guest-passes',
502
502
  content: async (ctx) => {
503
- const claude = color('claude', ctx.theme);
503
+ const contextColor = color('claude', ctx.theme);
504
504
  const reward = getCachedReferrerReward();
505
505
  return reward
506
- ? `Share Context Code and earn ${claude(formatCreditAmount(reward))} of extra usage · ${claude('/passes')}`
507
- : `You have free guest passes to share · ${claude('/passes')}`;
506
+ ? `Comparte Context Code y gana ${contextColor(formatCreditAmount(reward))} de uso extra · ${contextColor('/passes')}`
507
+ : `Tienes pases de invitado gratuitos para compartir · ${contextColor('/passes')}`;
508
508
  },
509
509
  cooldownSessions: 3,
510
510
  isRelevant: async () => {
@@ -519,20 +519,19 @@ const externalTips = [
519
519
  {
520
520
  id: 'overage-credit',
521
521
  content: async (ctx) => {
522
- const claude = color('claude', ctx.theme);
522
+ const contextColor = color('claude', ctx.theme);
523
523
  const info = getCachedOverageCreditGrant();
524
524
  const amount = info ? formatGrantAmount(info) : null;
525
525
  if (!amount)
526
526
  return '';
527
- // Copy from "OC & Bulk Overages copy" doc (#5 CLI Rotating tip)
528
- return `${claude(`${amount} in extra usage, on us`)} · third-party apps · ${claude('/extra-usage')}`;
527
+ return `${contextColor(`${amount} en uso extra, por nuestra cuenta`)} · aplicaciones de terceros · ${contextColor('/extra-usage')}`;
529
528
  },
530
529
  cooldownSessions: 3,
531
530
  isRelevant: async () => shouldShowOverageCreditUpsell(),
532
531
  },
533
532
  {
534
533
  id: 'feedback-command',
535
- content: async () => 'Use /feedback to help us improve!',
534
+ content: async () => '¡Usa /feedback para ayudarnos a mejorar!',
536
535
  cooldownSessions: 15,
537
536
  async isRelevant() {
538
537
  if (process.env.USER_TYPE === 'ant') {
@@ -1503,6 +1503,11 @@ export function getMiniMaxAccessToken() {
1503
1503
  getStoredProviderApiKey('minimax', getResolvedProviderProfileId('minimax')) ||
1504
1504
  null);
1505
1505
  }
1506
+ export function getNvidiaAccessToken() {
1507
+ return (process.env.NVIDIA_API_KEY ||
1508
+ getStoredProviderApiKey('nvidia', getResolvedProviderProfileId('nvidia')) ||
1509
+ null);
1510
+ }
1506
1511
  export function getOllamaAccessToken() {
1507
1512
  return 'ollama';
1508
1513
  }
@@ -1525,6 +1530,8 @@ export function getOpenAICompatibleAccessToken(provider = getAPIProvider()) {
1525
1530
  return getZAIAccessToken();
1526
1531
  case 'minimax':
1527
1532
  return getMiniMaxAccessToken();
1533
+ case 'nvidia':
1534
+ return getNvidiaAccessToken();
1528
1535
  case 'ollama':
1529
1536
  return getOllamaAccessToken();
1530
1537
  case 'ollama-cloud':
@@ -8,6 +8,7 @@ const DEFAULT_PROVIDER_BASE_URLS = {
8
8
  'gemini-google': 'https://generativelanguage.googleapis.com/v1beta/openai',
9
9
  zai: 'https://api.z.ai/api/coding/paas/v4',
10
10
  minimax: 'https://api.minimax.io/anthropic',
11
+ nvidia: 'https://integrate.api.nvidia.com/v1',
11
12
  };
12
13
  function trimTrailingSlash(value) {
13
14
  return value.endsWith('/') ? value.slice(0, -1) : value;
@@ -65,7 +66,8 @@ export function normalizeProviderBaseUrl(provider, rawValue) {
65
66
  if (provider === 'zai' ||
66
67
  provider === 'minimax' ||
67
68
  provider === 'gemini-api' ||
68
- provider === 'gemini-google') {
69
+ provider === 'gemini-google' ||
70
+ provider === 'nvidia') {
69
71
  return trimTrailingSlash(normalized);
70
72
  }
71
73
  return ensureV1Suffix(normalized);
@@ -95,6 +95,18 @@ export const VISIBLE_PROVIDERS = [
95
95
  },
96
96
  implemented: true,
97
97
  },
98
+ {
99
+ id: 'nvidia',
100
+ label: 'NVIDIA API',
101
+ description: 'API key propia de NVIDIA NIM / build.nvidia.com. Luego elige un modelo en /model.',
102
+ setup: {
103
+ kind: 'api-key',
104
+ intro: 'Pega tu API key de NVIDIA.',
105
+ nextStep: 'Despues, elige un modelo compatible desde /model.',
106
+ actionLabel: 'Pegar API key',
107
+ },
108
+ implemented: true,
109
+ },
98
110
  {
99
111
  id: 'claude',
100
112
  label: 'Claude (Anthropic)',
@@ -95,6 +95,8 @@ function getProviderLabel(provider) {
95
95
  return 'Z.AI';
96
96
  case 'minimax':
97
97
  return 'MiniMax';
98
+ case 'nvidia':
99
+ return 'NVIDIA NIM';
98
100
  default:
99
101
  return 'OpenAI';
100
102
  }
@@ -132,6 +134,10 @@ function getProviderBaseUrl(provider) {
132
134
  return trimTrailingSlash(process.env.MINIMAX_BASE_URL ||
133
135
  getConfiguredProviderBaseUrl('minimax') ||
134
136
  'https://api.minimax.io/anthropic');
137
+ case 'nvidia':
138
+ return ensureV1Suffix(process.env.NVIDIA_BASE_URL ||
139
+ getConfiguredProviderBaseUrl('nvidia') ||
140
+ 'https://integrate.api.nvidia.com/v1');
135
141
  default:
136
142
  return ensureV1Suffix(process.env.OPENAI_API_BASE_URL ||
137
143
  process.env.OPENAI_BASE_URL ||
@@ -13,12 +13,12 @@ export function toProviderPreference(provider) {
13
13
  return 'ollama-cloud';
14
14
  if (provider === 'gemini-api')
15
15
  return 'gemini-api';
16
- if (provider === 'gemini-google')
17
- return 'gemini-google';
18
16
  if (provider === 'zai')
19
17
  return 'zai';
20
18
  if (provider === 'minimax')
21
19
  return 'minimax';
20
+ if (provider === 'nvidia')
21
+ return 'nvidia';
22
22
  return 'claude';
23
23
  }
24
24
  export function getStoredModelForProvider(provider) {
@@ -40,10 +40,10 @@ export function switchProviderPreference(params) {
40
40
  params.currentProvider === 'openrouter' ||
41
41
  params.currentProvider === 'ollama' ||
42
42
  params.currentProvider === 'ollama-cloud' ||
43
- params.currentProvider === 'gemini-api' ||
44
43
  params.currentProvider === 'gemini-google' ||
45
44
  params.currentProvider === 'zai' ||
46
- params.currentProvider === 'minimax'
45
+ params.currentProvider === 'minimax' ||
46
+ params.currentProvider === 'nvidia'
47
47
  ? params.currentProvider
48
48
  : toProviderPreference(params.currentProvider);
49
49
  if (isProfiledProvider(sourceProvider)) {
@@ -157,6 +157,18 @@ export function isMiniMaxProviderConfigured() {
157
157
  return Boolean(process.env.MINIMAX_API_KEY);
158
158
  }
159
159
  }
160
+ export function isNvidiaProviderConfigured() {
161
+ try {
162
+ const storage = getSecureStorage().read();
163
+ const scopedKeys = Object.entries(storage?.providerProfileApiKeys ?? {}).some(([key, value]) => key.startsWith('nvidia/') && typeof value === 'string' && value.trim());
164
+ return Boolean(process.env.NVIDIA_API_KEY ||
165
+ storage?.providerApiKeys?.nvidia ||
166
+ scopedKeys);
167
+ }
168
+ catch {
169
+ return Boolean(process.env.NVIDIA_API_KEY);
170
+ }
171
+ }
160
172
  export function isOpenAICompatibleProvider(provider) {
161
173
  return (provider === 'openai' ||
162
174
  provider === 'openrouter' ||
@@ -164,7 +176,8 @@ export function isOpenAICompatibleProvider(provider) {
164
176
  provider === 'ollama-cloud' ||
165
177
  provider === 'gemini-api' ||
166
178
  provider === 'gemini-google' ||
167
- provider === 'zai');
179
+ provider === 'zai' ||
180
+ provider === 'nvidia');
168
181
  }
169
182
  export function hasDualProviderSessions() {
170
183
  return ((isOpenAIProviderConfigured() ||
@@ -174,7 +187,8 @@ export function hasDualProviderSessions() {
174
187
  isGeminiApiProviderConfigured() ||
175
188
  isGeminiGoogleProviderConfigured() ||
176
189
  isZAIProviderConfigured() ||
177
- isMiniMaxProviderConfigured()) &&
190
+ isMiniMaxProviderConfigured() ||
191
+ isNvidiaProviderConfigured()) &&
178
192
  hasStoredClaudeAIOAuthToken());
179
193
  }
180
194
  export function getAPIProvider() {
@@ -211,6 +225,8 @@ export function getAPIProvider() {
211
225
  return 'zai';
212
226
  if (preference === 'minimax')
213
227
  return 'minimax';
228
+ if (preference === 'nvidia')
229
+ return 'nvidia';
214
230
  }
215
231
  catch {
216
232
  // Config no disponible en arranque temprano.
@@ -223,6 +239,7 @@ export function getAPIProvider() {
223
239
  const geminiGoogleConfigured = isGeminiGoogleProviderConfigured();
224
240
  const zaiConfigured = isZAIProviderConfigured();
225
241
  const minimaxConfigured = isMiniMaxProviderConfigured();
242
+ const nvidiaConfigured = isNvidiaProviderConfigured();
226
243
  if (openAIConfigured) {
227
244
  return 'openai';
228
245
  }
@@ -235,6 +252,9 @@ export function getAPIProvider() {
235
252
  if (minimaxConfigured) {
236
253
  return 'minimax';
237
254
  }
255
+ if (nvidiaConfigured) {
256
+ return 'nvidia';
257
+ }
238
258
  if (geminiApiConfigured) {
239
259
  return 'gemini-api';
240
260
  }
@@ -78,7 +78,8 @@ async function validateAgainstProviderCatalog(modelName, provider) {
78
78
  if (provider !== 'openrouter' &&
79
79
  provider !== 'ollama' &&
80
80
  provider !== 'zai' &&
81
- provider !== 'minimax') {
81
+ provider !== 'minimax' &&
82
+ provider !== 'nvidia') {
82
83
  return null;
83
84
  }
84
85
  const providerModels = await fetchProviderModels(provider);
@@ -162,7 +163,7 @@ function handleProviderAwareError(error, modelName, provider) {
162
163
  if (lowerMessage.includes('no credentials were found') ||
163
164
  status === 401 ||
164
165
  status === 403) {
165
- if (provider === 'openrouter' || provider === 'zai' || provider === 'minimax') {
166
+ if (provider === 'openrouter' || provider === 'zai' || provider === 'minimax' || provider === 'nvidia') {
166
167
  return {
167
168
  valid: false,
168
169
  error: `${providerLabel} credentials are missing or invalid. Use /login and configure ${providerLabel}.`,
@@ -251,6 +252,8 @@ function getProviderLabel(provider) {
251
252
  return 'Z.AI';
252
253
  case 'minimax':
253
254
  return 'MiniMax';
255
+ case 'nvidia':
256
+ return 'NVIDIA NIM';
254
257
  default:
255
258
  return 'Context';
256
259
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Release notes come from the upstream changelog in English. Keep a small,
3
+ * deterministic translation layer for the home feed and /release-notes so the
4
+ * Spanish UI does not regress when known upstream notes are shown.
5
+ */
6
+ export function translateReleaseNoteToSpanish(note) {
7
+ const trimmed = note.trim();
8
+ if (trimmed.startsWith('Fixed OAuth authentication failing with a 401 retry loop')) {
9
+ return 'Se corrigió un bucle de reintentos 401 en la autenticación OAuth cuando `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1` está configurado.';
10
+ }
11
+ if (trimmed.startsWith('Added `ANTHROPIC_BEDROCK_SERVICE_TIER` environment variable')) {
12
+ return 'Se agregó la variable de entorno `ANTHROPIC_BEDROCK_SERVICE_TIER` para seleccionar el tier de servicio de Bedrock (`default`, `priority`, etc.).';
13
+ }
14
+ if (trimmed.startsWith('Pasting a PR URL into the `/resume` search box now finds the session')) {
15
+ return 'Pegar una URL de PR en la búsqueda de `/resume` ahora encuentra la sesión que creó esa PR.';
16
+ }
17
+ return note;
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iaforged/context-code",
3
- "version": "1.0.89",
3
+ "version": "1.0.91",
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",