ucode-agent 1.62.1 → 1.62.3
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 +464 -464
- package/package.json +63 -64
- package/skills/build-app/DIGEST.md +12 -0
- package/skills/build-app/SKILL.md +12 -0
- package/src/core/loop.js +31 -10
- package/src/core/provider.js +12 -13
- package/src/ui/plain.js +358 -358
- package/src/ui/screen.js +1752 -1735
- package/src/ui/theme.js +15 -9
- package/templates/next-shadcn/_package-lock.json +2025 -488
- package/templates/next-shadcn/package.json +6 -6
package/package.json
CHANGED
|
@@ -1,64 +1,63 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ucode-agent",
|
|
3
|
-
"version": "1.62.
|
|
4
|
-
"description": "ucode - a terminal coding agent that reads, edits and runs your code, on Google Gemini models.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "ucode.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"ucode": "ucode.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"ucode.js",
|
|
12
|
-
"src/",
|
|
13
|
-
"skills/",
|
|
14
|
-
"templates/",
|
|
15
|
-
"THIRD_PARTY_NOTICES.md"
|
|
16
|
-
],
|
|
17
|
-
"scripts": {
|
|
18
|
-
"start": "node ucode.js",
|
|
19
|
-
"test": "node test/run.js",
|
|
20
|
-
"prepublishOnly": "node scripts/no-bundled-key.js && node test/run.js",
|
|
21
|
-
"hooks": "node scripts/install-hooks.js"
|
|
22
|
-
},
|
|
23
|
-
"repository": {
|
|
24
|
-
"type": "git",
|
|
25
|
-
"url": "git+https://github.com/sppideey/ucode-agent.git"
|
|
26
|
-
},
|
|
27
|
-
"bugs": {
|
|
28
|
-
"url": "https://github.com/sppideey/ucode-agent/issues"
|
|
29
|
-
},
|
|
30
|
-
"homepage": "https://github.com/sppideey/ucode-agent#readme",
|
|
31
|
-
"engines": {
|
|
32
|
-
"node": ">=22"
|
|
33
|
-
},
|
|
34
|
-
"keywords": [
|
|
35
|
-
"agent",
|
|
36
|
-
"cli",
|
|
37
|
-
"terminal",
|
|
38
|
-
"coding-agent",
|
|
39
|
-
"llm",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"marked": "^
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "ucode-agent",
|
|
3
|
+
"version": "1.62.3",
|
|
4
|
+
"description": "ucode - a terminal coding agent that reads, edits and runs your code, on Google Gemini models.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "ucode.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ucode": "ucode.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"ucode.js",
|
|
12
|
+
"src/",
|
|
13
|
+
"skills/",
|
|
14
|
+
"templates/",
|
|
15
|
+
"THIRD_PARTY_NOTICES.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "node ucode.js",
|
|
19
|
+
"test": "node test/run.js",
|
|
20
|
+
"prepublishOnly": "node scripts/no-bundled-key.js && node test/run.js",
|
|
21
|
+
"hooks": "node scripts/install-hooks.js"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/sppideey/ucode-agent.git"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/sppideey/ucode-agent/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/sppideey/ucode-agent#readme",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"agent",
|
|
36
|
+
"cli",
|
|
37
|
+
"terminal",
|
|
38
|
+
"coding-agent",
|
|
39
|
+
"llm",
|
|
40
|
+
"gemini",
|
|
41
|
+
"google-gemini",
|
|
42
|
+
"ai"
|
|
43
|
+
],
|
|
44
|
+
"author": "om dixit",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@babel/parser": "^7.29.9",
|
|
48
|
+
"chalk": "^6.0.0",
|
|
49
|
+
"dotenv": "^18.0.3",
|
|
50
|
+
"jsonrepair": "^3.15.0",
|
|
51
|
+
"marked": "^15.0.12",
|
|
52
|
+
"marked-terminal": "^7.3.0",
|
|
53
|
+
"openai": "^7.23.0",
|
|
54
|
+
"playwright-core": "^1.63.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^26.6.2",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -108,6 +108,18 @@ first time; then the server route with the real integration; then the core
|
|
|
108
108
|
loop UI wired to it; then every state — empty, loading, success, error,
|
|
109
109
|
invalid input; then polish: motion, responsive, copy, title and metadata.
|
|
110
110
|
|
|
111
|
+
## 5a. It has to look finished
|
|
112
|
+
|
|
113
|
+
People judge the app in the first second it is on screen, before they click
|
|
114
|
+
anything. Before handing it over, check:
|
|
115
|
+
|
|
116
|
+
- A real title and one line saying what it is for — never "Welcome to…".
|
|
117
|
+
- The main action is visible without scrolling and is the most prominent button.
|
|
118
|
+
- Every button and input has hover and focus states; nothing is left browser-default.
|
|
119
|
+
- An empty list says what to do next ("No tasks yet — add one above"), never blank.
|
|
120
|
+
- It fits a 375px phone with no sideways scroll; tap targets are at least 44px.
|
|
121
|
+
- The number that matters (a score, a total, a timer) is big — several times body text.
|
|
122
|
+
|
|
111
123
|
## 5b. Tests, where there is a runner
|
|
112
124
|
|
|
113
125
|
`next-shadcn` ships vitest and one passing test, so `npm test` works from
|
|
@@ -195,6 +195,18 @@ const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
|
195
195
|
|
|
196
196
|
Use `batch_write` for the skeleton — one call, every file.
|
|
197
197
|
|
|
198
|
+
## 5a. It has to look finished
|
|
199
|
+
|
|
200
|
+
People judge the app in the first second it is on screen, before they click
|
|
201
|
+
anything. Before handing it over, check:
|
|
202
|
+
|
|
203
|
+
- A real title and one line saying what it is for — never "Welcome to…".
|
|
204
|
+
- The main action is visible without scrolling and is the most prominent button.
|
|
205
|
+
- Every button and input has hover and focus states; nothing is left browser-default.
|
|
206
|
+
- An empty list says what to do next ("No tasks yet — add one above"), never blank.
|
|
207
|
+
- It fits a 375px phone with no sideways scroll; tap targets are at least 44px.
|
|
208
|
+
- The number that matters (a score, a total, a timer) is big — several times body text.
|
|
209
|
+
|
|
198
210
|
## 6. Prove it works
|
|
199
211
|
|
|
200
212
|
- `npm run build` — it type-checks and lints; a build that fails is not done.
|
package/src/core/loop.js
CHANGED
|
@@ -1246,7 +1246,8 @@ export class Agent {
|
|
|
1246
1246
|
this.abort = new AbortController();
|
|
1247
1247
|
this.endedSilently = false;
|
|
1248
1248
|
|
|
1249
|
-
|
|
1249
|
+
// Kept on the agent too: the hand-over says how long the build took.
|
|
1250
|
+
const turnStarted = (this.turnStarted = Date.now());
|
|
1250
1251
|
let finished = false;
|
|
1251
1252
|
this.ui.turnStart?.();
|
|
1252
1253
|
try {
|
|
@@ -1592,8 +1593,7 @@ export class Agent {
|
|
|
1592
1593
|
`${modelName()} produced invalid tool arguments ${argRetries} times running ` +
|
|
1593
1594
|
'and could not correct itself.',
|
|
1594
1595
|
fix:
|
|
1595
|
-
'Say what you want more concretely, or /model to another one
|
|
1596
|
-
'Code and Nemotron 3.5 Lightning are both steadier with tool arguments.',
|
|
1596
|
+
'Say what you want more concretely, or /model to another one.',
|
|
1597
1597
|
});
|
|
1598
1598
|
}
|
|
1599
1599
|
} else {
|
|
@@ -1750,13 +1750,29 @@ export class Agent {
|
|
|
1750
1750
|
const server = serversReadySince(since).at(-1);
|
|
1751
1751
|
if (!server || (this.opened ??= new Set()).has(server.url)) return;
|
|
1752
1752
|
this.opened.add(server.url);
|
|
1753
|
+
if (this.openInBrowser(server.url)) this.ui.note(`Opened ${server.url} in your browser`);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
/**
|
|
1757
|
+
* Show a URL or a local page in the desktop browser. False when it did not.
|
|
1758
|
+
*
|
|
1759
|
+
* A file goes to explorer on Windows, not `cmd /c start`: the folder name
|
|
1760
|
+
* comes from the model, and an & in it would be a second command to cmd.
|
|
1761
|
+
*/
|
|
1762
|
+
openInBrowser(target) {
|
|
1763
|
+
if (!this.full || process.env.UCODE_OPEN === '0') return false;
|
|
1764
|
+
const web = /^https?:\/\//.test(target);
|
|
1765
|
+
// Only pages inside the project: a UNC path would have explorer reach out to another machine.
|
|
1766
|
+
const rel = web ? '' : path.relative(this.cwd, target);
|
|
1767
|
+
if (!web && (!rel || rel.startsWith('..') || path.isAbsolute(rel))) return false;
|
|
1753
1768
|
const [cmd, args] = process.platform === 'win32'
|
|
1754
|
-
? ['cmd', ['/c', 'start', '',
|
|
1755
|
-
: [process.platform === 'darwin' ? 'open' : 'xdg-open', [
|
|
1769
|
+
? (web ? ['cmd', ['/c', 'start', '', target]] : ['explorer.exe', [target]])
|
|
1770
|
+
: [process.platform === 'darwin' ? 'open' : 'xdg-open', [target]];
|
|
1756
1771
|
try {
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1772
|
+
// A missing opener (no xdg-open) fails later, as an event; unheard, it would crash ucode.
|
|
1773
|
+
spawn(cmd, args, { stdio: 'ignore', detached: true, windowsHide: true }).on('error', () => {}).unref();
|
|
1774
|
+
return true;
|
|
1775
|
+
} catch { return false; /* no browser to open — the link is in the answer */ }
|
|
1760
1776
|
}
|
|
1761
1777
|
|
|
1762
1778
|
cmdStats() {
|
|
@@ -2597,11 +2613,16 @@ export class Agent {
|
|
|
2597
2613
|
const made = calls.find((c) => c.name === 'create_app' && c.args?.name)?.args.name ?? this.appName;
|
|
2598
2614
|
const name = String(made ?? path.basename(app)).trim();
|
|
2599
2615
|
this.appName = name;
|
|
2600
|
-
const
|
|
2601
|
-
const
|
|
2616
|
+
const page = path.join(app, 'index.html');
|
|
2617
|
+
const link = pathToFileURL(page).href;
|
|
2618
|
+
// "Quiz is done in 42s": how long the whole build took, from the request.
|
|
2619
|
+
const took = this.turnStarted ? ` in ${formatDuration(Date.now() - this.turnStarted)}` : '';
|
|
2620
|
+
const text = `${name} is done${took}. Open it here: ${link}\n\n(or open \`${show}/index.html\` in your browser)`;
|
|
2602
2621
|
this.push({ role: 'assistant', content: text });
|
|
2603
2622
|
this.ui.assistant(text, { closing: true });
|
|
2604
2623
|
await this.persist();
|
|
2624
|
+
// The app pops up on its own; UCODE_OPEN=0 keeps it to the link.
|
|
2625
|
+
this.openInBrowser(page);
|
|
2605
2626
|
return { done: true };
|
|
2606
2627
|
}
|
|
2607
2628
|
|
package/src/core/provider.js
CHANGED
|
@@ -500,9 +500,8 @@ export function explain(err, id) {
|
|
|
500
500
|
: `Too many requests for ${modelName(id)} just now${wait ? ` — clear in ${wait}` : ''}.`,
|
|
501
501
|
fix: daily
|
|
502
502
|
? `It resets ${resetAt ? `at ${resetAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` : 'once a day'}. ` +
|
|
503
|
-
'
|
|
504
|
-
: 'ucode waits these out on its own
|
|
505
|
-
'clears in seconds; /model moves to a quieter one.',
|
|
503
|
+
'Nothing to do until then — the key is fine.'
|
|
504
|
+
: 'ucode waits these out on its own, usually for a few seconds. /model moves to another one.',
|
|
506
505
|
detail: { retryAfter, daily, resetAt: resetAt?.getTime() ?? null },
|
|
507
506
|
cause: err,
|
|
508
507
|
});
|
|
@@ -596,10 +595,7 @@ export function explain(err, id) {
|
|
|
596
595
|
kind: 'timeout',
|
|
597
596
|
attempted,
|
|
598
597
|
failed: `${modelName(id)} sent nothing back in time — the provider dropped the request.`,
|
|
599
|
-
fix:
|
|
600
|
-
'Free endpoints stall under load, and the largest reasoning models are the ' +
|
|
601
|
-
'first to. ucode already retried. If it keeps happening, /model to Nemotron ' +
|
|
602
|
-
'3.5 Lightning or North Mini Code, which answer sooner.',
|
|
598
|
+
fix: 'ucode already retried. Google is busy right now — send again, or /model to another one.',
|
|
603
599
|
detail: { status },
|
|
604
600
|
cause: err,
|
|
605
601
|
});
|
|
@@ -609,7 +605,7 @@ export function explain(err, id) {
|
|
|
609
605
|
return new Failure({
|
|
610
606
|
kind: 'server',
|
|
611
607
|
attempted,
|
|
612
|
-
failed: `
|
|
608
|
+
failed: `Google's servers are busy right now (HTTP ${status}). That is their side, not yours.`,
|
|
613
609
|
fix: 'Wait a few seconds and send again. If it persists, /model to another one.',
|
|
614
610
|
cause: err,
|
|
615
611
|
});
|
|
@@ -627,11 +623,14 @@ export function explain(err, id) {
|
|
|
627
623
|
return new Failure({
|
|
628
624
|
kind: 'network',
|
|
629
625
|
attempted,
|
|
630
|
-
|
|
626
|
+
// No DNS answer: the machine is offline, and saying so
|
|
627
|
+
// beats a raw "getaddrinfo ENOTFOUND" on a demo screen.
|
|
628
|
+
failed: /ENOTFOUND|EAI_AGAIN|getaddrinfo/i.test(raw)
|
|
629
|
+
? 'ucode could not reach Google — this computer looks offline.'
|
|
630
|
+
: `The connection to Google dropped: ${raw}`,
|
|
631
631
|
fix:
|
|
632
|
-
'ucode retries this by itself. If it keeps happening, check
|
|
633
|
-
'VPN and any
|
|
634
|
-
'a long think on a busy free endpoint is the usual cause.',
|
|
632
|
+
'ucode retries this by itself. If it keeps happening, check the Wi-Fi, ' +
|
|
633
|
+
'VPN and any proxy (HTTPS_PROXY), then send again.',
|
|
635
634
|
cause: err,
|
|
636
635
|
});
|
|
637
636
|
}
|
|
@@ -785,7 +784,7 @@ async function streamed(request, opts, id) {
|
|
|
785
784
|
kind: 'timeout',
|
|
786
785
|
attempted: `asking ${modelName(id)} for a reply`,
|
|
787
786
|
failed: `${modelName(id)} went silent for ${Math.round(limit() / 1000)}s, so ucode stopped waiting.`,
|
|
788
|
-
fix: 'ucode asks again by itself. If it keeps freezing, /model to
|
|
787
|
+
fix: 'ucode asks again by itself. If it keeps freezing, /model to another one.',
|
|
789
788
|
detail: { stalled: true, handed: handed.size },
|
|
790
789
|
cause,
|
|
791
790
|
});
|