ucode-agent 1.59.1 → 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 +10 -11
- package/package.json +2 -2
- package/src/core/doctor.js +3 -3
- package/src/core/login.js +5 -5
- package/src/core/provider.js +36 -40
- package/src/tools/browser.js +1 -1
- package/ucode.js +2 -2
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
|
|
5
|
-
|
|
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
|
|
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
|
|
72
|
+
ucode login YOUR_KEY
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
It is written to `~/.ucode/.env` as `
|
|
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
|
|
89
|
+
Served by Google (aistudio.google.com), free with a key.
|
|
90
90
|
|
|
91
91
|
| Model | For |
|
|
92
92
|
| --- | --- |
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
| **Nemotron 3 Super** ★ | the default — answers fastest on NVIDIA |
|
|
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
|
|
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.
|
|
4
|
-
"description": "ucode - a terminal coding agent that reads, edits and runs your code, on
|
|
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": {
|
package/src/core/doctor.js
CHANGED
|
@@ -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,
|
|
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 =
|
|
28
|
-
if (!key) return { ok: false, name: 'API key', detail: 'not set', fix: `Add
|
|
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 = '
|
|
17
|
+
const NAME = 'GEMINI_API_KEY';
|
|
18
18
|
|
|
19
|
-
/** A plausible
|
|
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' && /^
|
|
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://
|
|
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
|
|
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/provider.js
CHANGED
|
@@ -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://
|
|
36
|
-
export const PROVIDER = '
|
|
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
|
|
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
|
-
'
|
|
53
|
-
name: '
|
|
54
|
-
context:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
'moonshotai/kimi-k3': {
|
|
58
|
-
name: 'Kimi K3',
|
|
59
|
-
context: 128_000,
|
|
60
|
-
note: 'strong agentic coder — can be slow when NVIDIA is busy',
|
|
52
|
+
'gemini-3.5-flash-lite': {
|
|
53
|
+
name: 'Gemini 3.5 Flash-Lite',
|
|
54
|
+
context: 1_000_000,
|
|
55
|
+
star: true,
|
|
56
|
+
note: 'the default — fast, reliable with tools, 500 free requests a day',
|
|
61
57
|
},
|
|
62
|
-
'
|
|
63
|
-
name: '
|
|
64
|
-
context:
|
|
65
|
-
note: '
|
|
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',
|
|
66
62
|
},
|
|
67
|
-
'
|
|
68
|
-
name: '
|
|
69
|
-
context:
|
|
70
|
-
|
|
71
|
-
note: 'the default — answers fastest on NVIDIA',
|
|
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 = '
|
|
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 = 'nvidia/nemotron-3-super-120b-a12b';
|
|
|
82
77
|
* the first "too many requests".
|
|
83
78
|
*/
|
|
84
79
|
export const FALLBACKS = [
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'
|
|
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
|
|
218
|
-
export function
|
|
219
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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://
|
|
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 ?? {};
|
package/src/tools/browser.js
CHANGED
|
@@ -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 = '
|
|
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
|
|
68
|
-
' Free keys: https://
|
|
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
|
|