ucode-agent 1.59.0 → 1.60.0

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.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # ucode
2
2
 
3
3
  A coding agent that lives in your terminal. It reads your code, edits it, runs
4
- your commands, and keeps every conversation on disk. It runs on models served
5
- by NVIDIA — DeepSeek, Kimi, GLM and Nemotron — with a free key.
4
+ your commands, and keeps every conversation on disk. It runs on Google's
5
+ Gemini models, free with a key.
6
6
 
7
7
  It opens on a quiet screen — the name, the place to type, and the version in the
8
8
  corner:
@@ -66,13 +66,13 @@ space straight back when it finishes.
66
66
  npm i -g ucode-agent
67
67
  ```
68
68
 
69
- Then save your NVIDIA key (free at [build.nvidia.com](https://build.nvidia.com)):
69
+ Then save your Google key (free at [aistudio.google.com/apikey](https://aistudio.google.com/apikey)):
70
70
 
71
71
  ```bash
72
- ucode login nvapi-...
72
+ ucode login YOUR_KEY
73
73
  ```
74
74
 
75
- It is written to `~/.ucode/.env` as `NVIDIA_API_KEY`. A `.env` in
75
+ It is written to `~/.ucode/.env` as `GEMINI_API_KEY`. A `.env` in
76
76
  the project you are working on wins over that one, and a real environment
77
77
  variable wins over both.
78
78
 
@@ -86,16 +86,15 @@ Needs Node 22 or newer.
86
86
 
87
87
  ## The models
88
88
 
89
- Served by NVIDIA (build.nvidia.com), with a free key.
89
+ Served by Google (aistudio.google.com), free with a key.
90
90
 
91
91
  | Model | For |
92
92
  | --- | --- |
93
- | **DeepSeek V4.1 Flash** ★ | the default — fast, and reliable with tools |
94
- | Kimi K3 | strong agentic coder — can be slow when NVIDIA is busy |
95
- | GLM 5.3 | strong coder — can be slow when NVIDIA is busy |
96
- | Nemotron 3 Super | NVIDIA all-rounder |
93
+ | **Gemini 3.5 Flash-Lite** ★ | the default — fast, reliable with tools, 500 free requests a day |
94
+ | Gemini 3.5 Flash | smarter, but only about 20 free requests a day |
95
+ | Gemini 3.1 Flash-Lite | older and lighter, 500 free requests a day |
97
96
 
98
- `/model` shows them and switches. `ucode -m moonshotai/kimi-k3` starts on one.
97
+ `/model` shows them and switches. `ucode -m gemini-3.5-flash` starts on one.
99
98
 
100
99
  ## What it does
101
100
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ucode-agent",
3
- "version": "1.59.0",
4
- "description": "ucode - a terminal coding agent that reads, edits and runs your code, on models served by NVIDIA: DeepSeek, Kimi, GLM and Nemotron.",
3
+ "version": "1.60.0",
4
+ "description": "ucode - a terminal coding agent that reads, edits and runs your code, on Google Gemini models.",
5
5
  "type": "module",
6
6
  "main": "ucode.js",
7
7
  "bin": {
@@ -12,7 +12,7 @@ import os from 'node:os';
12
12
  import path from 'node:path';
13
13
  import { theme, dim, blue } from '../ui/theme.js';
14
14
  import { VERSION } from './version.js';
15
- import { DEFAULT_MODEL, ENV_FILE, BASE_URL, nvidiaKey } from './provider.js';
15
+ import { DEFAULT_MODEL, ENV_FILE, BASE_URL, providerKey } from './provider.js';
16
16
  import { newer } from './updater.js';
17
17
 
18
18
  const DEADLINE = 6000;
@@ -24,8 +24,8 @@ function version(cmd) {
24
24
  }
25
25
 
26
26
  async function checkKey() {
27
- const key = nvidiaKey();
28
- if (!key) return { ok: false, name: 'API key', detail: 'not set', fix: `Add NVIDIA_API_KEY=nvapi-... to ${ENV_FILE} (free at build.nvidia.com)` };
27
+ const key = providerKey();
28
+ if (!key) return { ok: false, name: 'API key', detail: 'not set', fix: `Add GEMINI_API_KEY=... to ${ENV_FILE} (free at aistudio.google.com/apikey)` };
29
29
  try {
30
30
  const r = await fetch(`${BASE_URL}/chat/completions`, {
31
31
  method: 'POST',
package/src/core/login.js CHANGED
@@ -14,11 +14,11 @@ import { promises as fs } from 'node:fs';
14
14
  import path from 'node:path';
15
15
  import { ENV_FILE } from './provider.js';
16
16
 
17
- const NAME = 'NVIDIA_API_KEY';
17
+ const NAME = 'GEMINI_API_KEY';
18
18
 
19
- /** A plausible NVIDIA key, so a typo is caught here and not mid-answer. */
19
+ /** A plausible Google key (AIza... or the newer AQ.... form), so a typo is caught here. */
20
20
  export function looksLikeKey(key) {
21
- return typeof key === 'string' && /^nvapi-[A-Za-z0-9_-]{20,}$/.test(key.trim());
21
+ return typeof key === 'string' && /^(?:AIza[\w-]{30,}|AQ\.[\w.-]{30,})$/.test(key.trim());
22
22
  }
23
23
 
24
24
  /** Put `key` in the machine-wide env file, keeping whatever else is in it. */
@@ -44,10 +44,10 @@ export async function saveKey(key) {
44
44
  const trimmed = String(key ?? '').trim();
45
45
 
46
46
  if (!trimmed) {
47
- return ` Usage: ucode login <key>\n\n Get one free at https://build.nvidia.com\n It is saved to ${ENV_FILE} and used by every project on this machine.`;
47
+ return ` Usage: ucode login <key>\n\n Get one free at https://aistudio.google.com/apikey\n It is saved to ${ENV_FILE} and used by every project on this machine.`;
48
48
  }
49
49
  if (!looksLikeKey(trimmed)) {
50
- return ` That does not look like an NVIDIA key — they start with "nvapi-".\n Get one at https://build.nvidia.com`;
50
+ return ` That does not look like a Google key — they start with "AIza" or "AQ.".\n Get one at https://aistudio.google.com/apikey`;
51
51
  }
52
52
 
53
53
  const existing = await fs.readFile(ENV_FILE, 'utf8').catch(() => '');
package/src/core/loop.js CHANGED
@@ -786,6 +786,8 @@ function systemPrompt({ cwd, skills, mode, check, map, memory }) {
786
786
  ' finish. ucode opens it in the browser for the user as soon as it is ready, so a',
787
787
  ' build that ends with the server stopped ends with nothing to look at. A one-page',
788
788
  ' app with no server needs none of this: the file is the app. Write all of it in',
789
+ ' (HTML, CSS AND the JavaScript that makes every button work: add, complete, delete,',
790
+ ' filter, save to localStorage. A page whose buttons do nothing is not an app)',
789
791
  ' create_app\'s files, then stop: ucode opens the page itself and hands it to the',
790
792
  ' user the moment it works. Do not start a server, curl it or re-read it to check.',
791
793
  '- Nothing you run has a keyboard. Pass the non-interactive flag to anything that',
@@ -32,8 +32,8 @@ const PACKAGE_ROOT = join(HERE, '..', '..');
32
32
  export const UCODE_HOME = join(homedir(), '.ucode');
33
33
  export const ENV_FILE = join(UCODE_HOME, '.env');
34
34
 
35
- export const BASE_URL = 'https://integrate.api.nvidia.com/v1';
36
- export const PROVIDER = 'NVIDIA';
35
+ export const BASE_URL = 'https://generativelanguage.googleapis.com/v1beta/openai';
36
+ export const PROVIDER = 'Google';
37
37
 
38
38
  // First definition wins — dotenv never overwrites a variable that already
39
39
  // exists — so the order here is the precedence order:
@@ -44,36 +44,31 @@ dotenv.config({ path: ENV_FILE, quiet: true });
44
44
  dotenv.config({ path: join(PACKAGE_ROOT, '.env'), quiet: true });
45
45
 
46
46
  /**
47
- * The whole model list, served by NVIDIA (build.nvidia.com).
47
+ * The whole model list, served by Google (aistudio.google.com).
48
48
  *
49
49
  * `name` is what the status bar shows. `note` is what the picker shows.
50
50
  */
51
51
  export const MODELS = {
52
- 'deepseek-ai/deepseek-v4.1-flash': {
53
- name: 'DeepSeek V4.1 Flash',
54
- context: 128_000,
52
+ 'gemini-3.5-flash-lite': {
53
+ name: 'Gemini 3.5 Flash-Lite',
54
+ context: 1_000_000,
55
55
  star: true,
56
- note: 'the default — fast, and reliable with tools',
56
+ note: 'the default — fast, reliable with tools, 500 free requests a day',
57
57
  },
58
- 'moonshotai/kimi-k3': {
59
- name: 'Kimi K3',
60
- context: 128_000,
61
- note: 'strong agentic coder — can be slow when NVIDIA is busy',
58
+ 'gemini-3.5-flash': {
59
+ name: 'Gemini 3.5 Flash',
60
+ context: 1_000_000,
61
+ note: 'smarter, but only about 20 free requests a day',
62
62
  },
63
- 'z-ai/glm-5.3': {
64
- name: 'GLM 5.3',
65
- context: 128_000,
66
- note: 'strong coder — can be slow when NVIDIA is busy',
67
- },
68
- 'nvidia/nemotron-3-super-120b-a12b': {
69
- name: 'Nemotron 3 Super',
70
- context: 128_000,
71
- note: 'NVIDIA all-rounder',
63
+ 'gemini-3.1-flash-lite': {
64
+ name: 'Gemini 3.1 Flash-Lite',
65
+ context: 1_000_000,
66
+ note: 'older and lighter, 500 free requests a day',
72
67
  },
73
68
  };
74
69
 
75
70
  /** The model a session starts on. */
76
- export const DEFAULT_MODEL = 'deepseek-ai/deepseek-v4.1-flash';
71
+ export const DEFAULT_MODEL = 'gemini-3.5-flash-lite';
77
72
 
78
73
  /**
79
74
  * Where to go when a model is busy, in order of preference. Each is served by
@@ -82,10 +77,9 @@ export const DEFAULT_MODEL = 'deepseek-ai/deepseek-v4.1-flash';
82
77
  * the first "too many requests".
83
78
  */
84
79
  export const FALLBACKS = [
85
- 'deepseek-ai/deepseek-v4.1-flash',
86
- 'nvidia/nemotron-3-super-120b-a12b',
87
- 'moonshotai/kimi-k3',
88
- 'z-ai/glm-5.3',
80
+ 'gemini-3.5-flash-lite',
81
+ 'gemini-3.1-flash-lite',
82
+ 'gemini-3.5-flash',
89
83
  ];
90
84
 
91
85
  /** The next model to try after `id`, skipping any already tried this round. */
@@ -214,27 +208,24 @@ export function estimateConversation(messages) {
214
208
  * package is readable by anyone who runs `npm pack ucode-agent`, and no amount
215
209
  * of first-run convenience is worth handing out a live credential.
216
210
  */
217
- /** The NVIDIA key: NVIDIA_API_KEY, or a UCODE_API_KEY that is one (nvapi-...). */
218
- export function nvidiaKey() {
219
- const direct = (process.env.NVIDIA_API_KEY || '').trim();
220
- if (direct) return direct;
221
- const shared = (process.env.UCODE_API_KEY || '').trim();
222
- return shared.startsWith('nvapi-') ? shared : '';
211
+ /** The Google key: GEMINI_API_KEY, or GOOGLE_API_KEY. */
212
+ export function providerKey() {
213
+ return (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || '').trim();
223
214
  }
224
215
 
225
216
  function apiKey() {
226
217
  // UCODE_API_KEY is the documented name. The provider's own variable name is
227
218
  // still read, so a key set up for another tool keeps working here.
228
- const key = nvidiaKey();
219
+ const key = providerKey();
229
220
  if (!key) {
230
221
  throw new Failure({
231
222
  kind: 'no_api_key',
232
223
  attempted: 'connecting to the model',
233
- failed: 'No NVIDIA API key is set - NVIDIA_API_KEY is missing from the environment and from every .env file.',
224
+ failed: 'No Google API key is set - GEMINI_API_KEY is missing from the environment and from every .env file.',
234
225
  fix:
235
- `Put NVIDIA_API_KEY=nvapi-... in ${ENV_FILE} — that applies to every ` +
226
+ `Put GEMINI_API_KEY=your-key in ${ENV_FILE} — that applies to every ` +
236
227
  'project on this machine — or in a .env file beside your code. ' +
237
- 'Free keys: https://build.nvidia.com',
228
+ 'Free keys: https://aistudio.google.com/apikey',
238
229
  });
239
230
  }
240
231
  return key;
@@ -337,6 +328,9 @@ function wireMessages(messages) {
337
328
  id: c.id,
338
329
  type: 'function',
339
330
  function: { name: c.name, arguments: JSON.stringify(c.args ?? {}) },
331
+ // Gemini signs each tool call with its reasoning, and refuses the
332
+ // next step if the signature does not come back with the call.
333
+ ...(c.extra ? { extra_content: c.extra } : {}),
340
334
  }));
341
335
  }
342
336
  out.push(wire);
@@ -827,7 +821,7 @@ async function streamed(request, opts, id) {
827
821
  for (const [index, slot] of partial) {
828
822
  if (index < call.index && !handed.has(index)) {
829
823
  handed.add(index);
830
- opts.onToolCall(readCall({ id: slot.id || `call_${index}`, name: slot.name, raw: slot.args, names }));
824
+ opts.onToolCall(readCall({ id: slot.id || `call_${index}`, name: slot.name, raw: slot.args, names, extra: slot.extra }));
831
825
  }
832
826
  }
833
827
  highest = call.index;
@@ -835,6 +829,7 @@ async function streamed(request, opts, id) {
835
829
 
836
830
  const slot = partial.get(call.index) ?? { id: '', name: '', args: '' };
837
831
  if (call.id) slot.id = call.id;
832
+ if (call.extra_content) slot.extra = call.extra_content;
838
833
  if (call.function?.name) slot.name += call.function.name;
839
834
  if (call.function?.arguments) slot.args += call.function.arguments;
840
835
  partial.set(call.index, slot);
@@ -860,7 +855,7 @@ async function streamed(request, opts, id) {
860
855
 
861
856
  const toolCalls = [];
862
857
  for (const [index, slot] of partial) {
863
- toolCalls.push(readCall({ id: slot.id || `call_${index}`, name: slot.name, raw: slot.args, cutOff: finishReason === 'length', names }));
858
+ toolCalls.push(readCall({ id: slot.id || `call_${index}`, name: slot.name, raw: slot.args, cutOff: finishReason === 'length', names, extra: slot.extra }));
864
859
  }
865
860
 
866
861
  return {
@@ -1038,8 +1033,9 @@ export function fixName(name, names) {
1038
1033
  * back to the model, which usually fixes its own JSON on the next step —
1039
1034
  * cheaper than failing the whole turn over a stray comma.
1040
1035
  */
1041
- export function readCall({ id, name, raw, cutOff = false, names }) {
1036
+ export function readCall({ id, name, raw, cutOff = false, names, extra }) {
1042
1037
  const call = { id, name: fixName(name, names), args: {} };
1038
+ if (extra) call.extra = extra;
1043
1039
  name = call.name;
1044
1040
  const text = String(raw ?? '').trim();
1045
1041
  if (!text) return call;
@@ -1100,7 +1096,7 @@ function normalize(data, id, names) {
1100
1096
  const message = choice?.message ?? {};
1101
1097
 
1102
1098
  const toolCalls = (message.tool_calls ?? []).map((c) =>
1103
- readCall({ id: c.id, name: c.function?.name, raw: c.function?.arguments, cutOff: choice?.finish_reason === 'length', names })
1099
+ readCall({ id: c.id, name: c.function?.name, raw: c.function?.arguments, cutOff: choice?.finish_reason === 'length', names, extra: c.extra_content })
1104
1100
  );
1105
1101
 
1106
1102
  const u = data?.usage ?? {};
@@ -20,7 +20,7 @@ import { ToolFailure } from '../core/failure.js';
20
20
  import { ask } from '../core/provider.js';
21
21
  import { getRoot, result } from './shared.js';
22
22
 
23
- const VISION_MODEL = 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning';
23
+ const VISION_MODEL = 'gemini-3.5-flash-lite';
24
24
  const WIDTHS = [
25
25
  { name: 'phone', width: 375, height: 812 },
26
26
  { name: 'desktop', width: 1440, height: 900 },
package/ucode.js CHANGED
@@ -64,8 +64,8 @@ async function usage() {
64
64
  ' doctor check that everything ucode needs is working\n' +
65
65
  ' login <key> save your key for every folder on this machine\n\n' +
66
66
  ` ${sky('Models')}\n${models}\n\n` +
67
- ` Needs NVIDIA_API_KEY in the environment or in ${ENV_FILE}\n` +
68
- ' Free keys: https://build.nvidia.com\n\n'
67
+ ` Needs GEMINI_API_KEY in the environment or in ${ENV_FILE}\n` +
68
+ ' Free keys: https://aistudio.google.com/apikey\n\n'
69
69
  );
70
70
  }
71
71