ucode-agent 1.62.4 → 1.62.5
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 +1 -1
- package/src/core/loop.js +5 -32
- package/src/core/opener.js +44 -0
- package/src/core/scope.js +23 -0
- package/src/tools/browser.js +58 -0
package/README.md
CHANGED
|
@@ -1,464 +1,464 @@
|
|
|
1
|
-
# ucode
|
|
2
|
-
|
|
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 Google's
|
|
5
|
-
Gemini models, free with a key.
|
|
6
|
-
|
|
7
|
-
It opens on a quiet screen — the name, the place to type, and the version in the
|
|
8
|
-
corner:
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗
|
|
12
|
-
██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝
|
|
13
|
-
██║ ██║██║ ██║ ██║██║ ██║█████╗
|
|
14
|
-
██║ ██║██║ ██║ ██║██║ ██║██╔══╝
|
|
15
|
-
╚██████╔╝╚██████╗╚██████╔╝██████╔╝███████╗
|
|
16
|
-
╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
20
|
-
│ › Ask anything… │
|
|
21
|
-
│ │
|
|
22
|
-
│ BUILD North Mini Code 0% │
|
|
23
|
-
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
24
|
-
|
|
25
|
-
try build me a landing page for a coffee shop
|
|
26
|
-
explain what this project does and how it fits together
|
|
27
|
-
add a dark mode toggle that remembers the choice
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
v1.62.
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
A light crosses the wordmark once as it opens, and the three lines under the box
|
|
34
|
-
are there so an empty screen has something to say. Once you are talking, each
|
|
35
|
-
message you send is marked down its left edge in the same blue as the input, so
|
|
36
|
-
your own words are easy to find in a long session — and each step the agent
|
|
37
|
-
takes carries the shape of the work: a hollow diamond to look, a filled one to
|
|
38
|
-
change, an arrow to run.
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
▌ build a notes dashboard
|
|
42
|
-
|
|
43
|
-
◇ Read 3 files
|
|
44
|
-
◆ Writing index.html +148 -0
|
|
45
|
-
▸ Running npm run dev
|
|
46
|
-
|
|
47
|
-
The dashboard is at http://localhost:3000, and `npm run dev` brings it back up.
|
|
48
|
-
|
|
49
|
-
╭──────────────────────────────────────────────────────────────────────────────────╮
|
|
50
|
-
│ › now add a dark mode toggle │
|
|
51
|
-
│ │
|
|
52
|
-
│ BUILD North Mini Code 4% │
|
|
53
|
-
╰──────────────────────────────────────────────────────────────────────────────────╯
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
The status sits inside the input box because it describes the thing you are
|
|
57
|
-
typing into. Three facts, no more: the live mode, the answering model, and how
|
|
58
|
-
full the context window is. The percentage turns amber at 75%, which is where
|
|
59
|
-
older turns start being folded into a summary. While a turn is running the
|
|
60
|
-
middle of that row carries the spinner and the way out of it, and hands the
|
|
61
|
-
space straight back when it finishes.
|
|
62
|
-
|
|
63
|
-
## Install
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
npm i -g ucode-agent
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Then save your Google key (free at [aistudio.google.com/apikey](https://aistudio.google.com/apikey)):
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
ucode login YOUR_KEY
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
It is written to `~/.ucode/.env` as `GEMINI_API_KEY`. A `.env` in
|
|
76
|
-
the project you are working on wins over that one, and a real environment
|
|
77
|
-
variable wins over both.
|
|
78
|
-
|
|
79
|
-
Then, in any project:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
ucode
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Needs Node 22 or newer.
|
|
86
|
-
|
|
87
|
-
## The models
|
|
88
|
-
|
|
89
|
-
Served by Google (aistudio.google.com), free with a key.
|
|
90
|
-
|
|
91
|
-
| Model | For |
|
|
92
|
-
| --- | --- |
|
|
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 |
|
|
96
|
-
|
|
97
|
-
`/model` shows them and switches. `ucode -m gemini-3.5-flash` starts on one.
|
|
98
|
-
|
|
99
|
-
When Google is overloaded and Flash-Lite stops answering, ucode carries on with
|
|
100
|
-
3.5 Flash by itself and goes back to Flash-Lite a few minutes later.
|
|
101
|
-
|
|
102
|
-
## What it does
|
|
103
|
-
|
|
104
|
-
**Twenty-one tools.** `create_app`, `read_file`, `read_files`, `write_file`,
|
|
105
|
-
`batch_write`, `edit_file`, `multi_edit`, `edit_files`, `rename_symbol`,
|
|
106
|
-
`find_symbol`, `outline`, `type_of`, `add_block`, `list_dir`, `glob`, `grep`,
|
|
107
|
-
`run_command`, `run_commands`, `look_at_app`, `web_search`, `deploy`. Read-only
|
|
108
|
-
calls run in parallel, and start the moment the model finishes writing them —
|
|
109
|
-
while the rest of its reply is still arriving. Anything that writes runs on its
|
|
110
|
-
own, in order.
|
|
111
|
-
|
|
112
|
-
**It asks rather than guesses.** `type_of` opens a language service on the
|
|
113
|
-
TypeScript the project itself has installed and gives back the exact signature,
|
|
114
|
-
the JSDoc, and where a thing is defined — inferred types included. `find_symbol`
|
|
115
|
-
answers "where is this declared", which is the question `grep` is usually being
|
|
116
|
-
asked badly. `rename_symbol` renames by code shape, knowing where strings and
|
|
117
|
-
comments begin, because a find-and-replace that matched too much is the most
|
|
118
|
-
common broken edit.
|
|
119
|
-
|
|
120
|
-
**It checks itself as it goes.** After a change: an incremental type check that
|
|
121
|
-
answers in about a second rather than a cold minute, then the tests that reach
|
|
122
|
-
the files just changed, then anything the running dev server has complained
|
|
123
|
-
about since the last look. All three come back the way an error does, so the
|
|
124
|
-
model fixes them without being told.
|
|
125
|
-
|
|
126
|
-
**New apps are ready in seconds.** A starter installed once is kept and
|
|
127
|
-
hard-linked into the next app — the same files under another name, so it costs
|
|
128
|
-
no extra disk and skips the wait entirely.
|
|
129
|
-
|
|
130
|
-
**Apps start from a ready-made starter — and finish in the same call.**
|
|
131
|
-
`create_app` copies a starter that is already known to build, and takes the
|
|
132
|
-
app's files with it, so a one-page app is a single round trip: the starter
|
|
133
|
-
lands, the model's files are written over it, and the starter's own files come
|
|
134
|
-
back inside the result so there is nothing to read afterwards.
|
|
135
|
-
|
|
136
|
-
The default starter is `plain-html`: one page, one stylesheet, one module,
|
|
137
|
-
nothing to install and nothing to build. A tasks app, a game, a calculator or a
|
|
138
|
-
visualisation is finished before a framework would have finished installing.
|
|
139
|
-
`next-shadcn` is there for routes, a database or many screens — Next.js 16,
|
|
140
|
-
TypeScript, Tailwind 4, shadcn/ui with 25 components, light/dark, toasts and a
|
|
141
|
-
considered theme. Setting that up by hand is about four minutes
|
|
142
|
-
(`create-next-app` and the shadcn CLI measured at 116s and 130s) plus a dozen
|
|
143
|
-
round trips; the copy takes under a second, and its install runs in the
|
|
144
|
-
background while the model writes the first components.
|
|
145
|
-
|
|
146
|
-
**Built to be fast, and measured.** A traced build of a small Next.js app went
|
|
147
|
-
from 17 minutes and 116 model steps to about 6 minutes and 25 steps, by fixing
|
|
148
|
-
where the time actually went:
|
|
149
|
-
|
|
150
|
-
- Edits return the file as it now stands, so the model does not re-read it.
|
|
151
|
-
- Files written more than a few steps ago stop being re-sent in full; the
|
|
152
|
-
conversation stays small, so every step answers faster.
|
|
153
|
-
- A file-write whose JSON is malformed — a missing comma, an unescaped quote in
|
|
154
|
-
the code, raw line breaks — is repaired instead of thrown away with all its
|
|
155
|
-
output.
|
|
156
|
-
- An edit whose `old_string` is slightly off — a middle line remembered wrong,
|
|
157
|
-
escapes written out, a blank line at either end, a different indent — still
|
|
158
|
-
lands, through the fallback matchers from opencode's edit tool. Found in two
|
|
159
|
-
places is still refused, and so is a match far bigger than what was asked
|
|
160
|
-
for. `replace_all` does a rename in one call.
|
|
161
|
-
- A missing file comes back with the lookalikes beside it ("did you mean
|
|
162
|
-
App.jsx?"), and a tool named with the wrong case runs as the tool it means,
|
|
163
|
-
instead of each costing a round trip to learn a spelling.
|
|
164
|
-
- Every write is parsed on the spot, so a syntax error comes back in the same
|
|
165
|
-
step rather than a minute later from a failed build.
|
|
166
|
-
- A failed build that is missing a component or package says exactly which
|
|
167
|
-
command fixes it.
|
|
168
|
-
- The starter is the shadcn models already know (Radix), so the code they write
|
|
169
|
-
compiles the first time.
|
|
170
|
-
- A new app goes out without the tools it has nothing to point at — no symbol
|
|
171
|
-
lookup, no rename, no type query in an empty folder — and an instruction pack
|
|
172
|
-
that loads itself sends its short form, with the full one a `load_skill`
|
|
173
|
-
away. Both are re-read by the provider on every step, so what is not in the
|
|
174
|
-
request is time off every one of them.
|
|
175
|
-
- Ready-made blocks for a page with no build step as well as for React: a list you
|
|
176
|
-
can add to, tick off, rename and remove, a filter row, a localStorage store, a
|
|
177
|
-
dialog, toasts, a theme toggle. Typing is the slowest part of a build, and each
|
|
178
|
-
block is a hundred lines nobody has to type.
|
|
179
|
-
- A nested argument written the wrong way — a JSON string, a { path: contents } map
|
|
180
|
-
— is read rather than refused. Each refusal was a round trip spent being told
|
|
181
|
-
something that could simply be parsed.
|
|
182
|
-
- A tool that was not offered is refused rather than quietly run, so withholding one
|
|
183
|
-
from a new project, or from plan mode, means what it says.
|
|
184
|
-
- The closing message is cut to eight lines. A build that ends with the request
|
|
185
|
-
read back and every feature ticked off is a status report nobody asked for,
|
|
186
|
-
and it is the last thing left on screen.
|
|
187
|
-
|
|
188
|
-
`UCODE_TRACE=1` writes every model call and tool, with its duration, to
|
|
189
|
-
`~/.ucode/trace.jsonl`.
|
|
190
|
-
|
|
191
|
-
Measured on "build me a simple todo app" — same prompt, same model, two traced
|
|
192
|
-
runs: **27 model calls, 8 failed tool calls, no finished app** before this round of
|
|
193
|
-
work; **14 model calls, no failures, a working app in under two minutes** after it.
|
|
194
|
-
|
|
195
|
-
**Deploy in one line.** Say "deploy it", or type `/deploy [folder]`, and the app
|
|
196
|
-
goes live on Vercel. ucode picks a short project name that fits the app and is
|
|
197
|
-
free (`food-iq`, else `food-iq-app`…), copies the app's `.env` keys to Vercel as
|
|
198
|
-
encrypted variables, refuses code with a secret written into it (and says how to
|
|
199
|
-
move it to a server route), and gives you the link. Deploying again updates the
|
|
200
|
-
same link. Needs a token from vercel.com/account/tokens in `~/.ucode/.env` as
|
|
201
|
-
`VERCEL_TOKEN=...`.
|
|
202
|
-
|
|
203
|
-
**A look for every app.** `create_app` takes a design preset — ocean, grove,
|
|
204
|
-
sunset, graphite, violet or citrus — each a full light and dark palette with its
|
|
205
|
-
own font, so apps stop looking like the same default blue.
|
|
206
|
-
|
|
207
|
-
**Every turn can be taken back.** `/undo` puts back every file the last turn
|
|
208
|
-
changed — a rewritten file returns byte for byte, a file that did not exist
|
|
209
|
-
before is removed again. Each write keeps the original the first time that turn
|
|
210
|
-
touches it, so what comes back is the state before the turn rather than before
|
|
211
|
-
the last of six edits to the same file. An agent that writes to your disk on
|
|
212
|
-
its own should be able to take it back, whether or not the project has git.
|
|
213
|
-
|
|
214
|
-
**It notices when it is going round in circles.** The same failing edit, an edit
|
|
215
|
-
that changes nothing, or a build failing on the same errors three times gets a
|
|
216
|
-
firm, specific note; if that does not work, the turn moves to another model.
|
|
217
|
-
|
|
218
|
-
**It never dies at the daily limit.** When the free daily limit runs out mid-build,
|
|
219
|
-
ucode counts down to the reset and carries on by itself.
|
|
220
|
-
|
|
221
|
-
**You can see it working.** The status row shows the current step with a light
|
|
222
|
-
sweeping across it, the step count and the time, and each answer ends with
|
|
223
|
-
`✓ Done in 6m 12s · 25 steps`. When a dev server comes up, the app opens in your
|
|
224
|
-
browser (`UCODE_OPEN=0` turns that off).
|
|
225
|
-
|
|
226
|
-
**`/stats` and `ucode doctor`.** `/stats` shows the session's time, steps, tokens,
|
|
227
|
-
files and builds. `ucode doctor` (or `/doctor`) checks Node, npm, git, the API
|
|
228
|
-
key and today's free requests left, the browser, the Vercel token and the
|
|
229
|
-
version, with the fix for anything wrong.
|
|
230
|
-
|
|
231
|
-
**Parallel workers.** When a build splits into parts that touch different files
|
|
232
|
-
— the API route, the upload component, the results view — the model hands them
|
|
233
|
-
to up to three workers that build at the same time, each line in the transcript
|
|
234
|
-
tagged with the worker's name. File writes take turns so two never collide.
|
|
235
|
-
|
|
236
|
-
**Installs that start early.** The moment a `package.json` with dependencies is
|
|
237
|
-
written, its install starts in the background while the rest of the app is
|
|
238
|
-
still being written. An install the model asks for later waits for that one
|
|
239
|
-
instead of running twice, and anything run in that folder waits for it too.
|
|
240
|
-
|
|
241
|
-
**It opens what it built and uses it.** Every app build ends with a look — not
|
|
242
|
-
when the model remembers to ask for one, but as part of the same pass that
|
|
243
|
-
type-checks. It opens the app in a real browser (the Edge or Chrome already on
|
|
244
|
-
your machine, so there is nothing extra to download) at 375px and 1440px, and
|
|
245
|
-
serves the folder itself when there is no dev server to point at, which is how
|
|
246
|
-
a three-file app gets checked at all.
|
|
247
|
-
|
|
248
|
-
Then it uses the app. It types into the first field, presses Enter, and clicks
|
|
249
|
-
the button that submits — and if the page gains no elements, changes no text
|
|
250
|
-
and stores nothing, that is reported as the thing to fix before anything else.
|
|
251
|
-
A page that renders and has no working behaviour passes a type check, a syntax
|
|
252
|
-
check and a screenshot; the only way to find out is to press something.
|
|
253
|
-
|
|
254
|
-
It also reports console errors, failed requests, content that spills off a
|
|
255
|
-
phone screen, broken images and unlabeled controls, saves screenshots to
|
|
256
|
-
`.ucode/screenshots`, and has Nemotron Nano Omni review them the way a designer
|
|
257
|
-
would. The model fixes what it finds before calling the app done. Both widths load at once, and the designer review — the slow part — runs
|
|
258
|
-
on the first look at an app in each request and is skipped, not waited on, when
|
|
259
|
-
the vision model is busy. The look after the fixes re-runs only the fast checks:
|
|
260
|
-
a few seconds.
|
|
261
|
-
|
|
262
|
-
**Errors fixed before you see them.** When the model says it is done, ucode
|
|
263
|
-
type-checks every file it changed — `tsc --noEmit` for TypeScript projects,
|
|
264
|
-
a syntax check for JavaScript and Python — and hands any errors back to fix,
|
|
265
|
-
up to three rounds.
|
|
266
|
-
|
|
267
|
-
**A plan you can see.** For longer jobs the model keeps a short checklist, with
|
|
268
|
-
a bar across the top for how far along it is and one row per step, so the one in
|
|
269
|
-
progress is findable without reading the rest:
|
|
270
|
-
|
|
271
|
-
```
|
|
272
|
-
━━━━────── 2/5
|
|
273
|
-
✓ Scaffold
|
|
274
|
-
✓ Upload
|
|
275
|
-
▸ Score dial
|
|
276
|
-
○ Findings
|
|
277
|
-
○ Polish
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
**It knows the project before it asks.** Each turn starts with a map of every
|
|
281
|
-
file and the names each code file exports, so the model goes straight to the
|
|
282
|
-
right file instead of searching for it.
|
|
283
|
-
|
|
284
|
-
**Project memory.** `UCODE.md` in a project — and `~/.ucode/UCODE.md` for how you
|
|
285
|
-
like to work everywhere — is read at the start of every turn. `/remember <note>`
|
|
286
|
-
adds a line to it.
|
|
287
|
-
|
|
288
|
-
**Edits that never guess.** `edit_file` matches exactly once or it fails, and
|
|
289
|
-
when it fails it says *why*. It tolerates what does not matter — tabs against
|
|
290
|
-
spaces, a different indent depth, Windows line endings — and re-indents the
|
|
291
|
-
replacement to fit the file, but a match found twice is still refused.
|
|
292
|
-
`edit_files` changes several files in one call, and writes none of them if any
|
|
293
|
-
edit fails.
|
|
294
|
-
|
|
295
|
-
**Diffs with real line numbers.** Removed lines are numbered where they were,
|
|
296
|
-
added lines where they now are. Numbers you can jump to, not decoration.
|
|
297
|
-
|
|
298
|
-
**Live commentary.** `● Listing src`, `● Running npm test` — what it is doing,
|
|
299
|
-
as it does it, named after the file or command rather than the tool.
|
|
300
|
-
|
|
301
|
-
**Two modes.** Build edits and runs. Plan reads and researches with the writing
|
|
302
|
-
tools withheld, which is stronger than asking a model nicely. `ctrl+b` swaps
|
|
303
|
-
them; the chip inside the input box says which is live.
|
|
304
|
-
|
|
305
|
-
**Sessions.** Everything is on disk under `~/.ucode/sessions`, saved after every
|
|
306
|
-
step. `/resume` lists them with what each one was actually about, the ones from
|
|
307
|
-
this folder first. Press `d` twice on one to delete it — the list stays open, so
|
|
308
|
-
clearing out several is quick — or `/session delete 2,5`.
|
|
309
|
-
|
|
310
|
-
**It updates itself.** Each launch checks npm in the background and, if there is
|
|
311
|
-
a newer version, installs it while you work. The next launch is the new one.
|
|
312
|
-
Set `UCODE_NO_UPDATE=1` to turn that off.
|
|
313
|
-
|
|
314
|
-
**A context window that folds rather than forgets.** Past 75% the oldest turns
|
|
315
|
-
are summarised instead of dropped, never cutting between a tool call and its
|
|
316
|
-
result. The full history stays on disk regardless.
|
|
317
|
-
|
|
318
|
-
**Screenshots.** Mention a `.png` in your message and it gets attached.
|
|
319
|
-
|
|
320
|
-
## Skills
|
|
321
|
-
|
|
322
|
-
A skill is a folder with a `SKILL.md`: frontmatter, then instructions. Only the
|
|
323
|
-
names and one-line descriptions go into the system prompt — a body is pulled in
|
|
324
|
-
when it is wanted, so the prompt stays the same size however many you add.
|
|
325
|
-
|
|
326
|
-
| Skill | For |
|
|
327
|
-
| --- | --- |
|
|
328
|
-
| `ui-ux` | interfaces: direction, tokens, layout, states, motion, accessibility |
|
|
329
|
-
| `build-app` | going from nothing to something running, and proving it runs |
|
|
330
|
-
| `debug` | finding the real cause instead of the first plausible one |
|
|
331
|
-
| `code-review` | reviewing a change the way a careful colleague would |
|
|
332
|
-
| `write-tests` | tests that fail for the right reason |
|
|
333
|
-
| `ai-features` | model-backed features: prompts with rules, validated JSON, images, failure handling |
|
|
334
|
-
| `security` | secrets, auth, ownership checks, injection, XSS, CSRF, SSRF, uploads |
|
|
335
|
-
| `performance` | measure first, find the real bottleneck, prove the win with numbers |
|
|
336
|
-
| `refactor` | change the shape of code without changing what it does |
|
|
337
|
-
|
|
338
|
-
**Every skill loads itself** when the request calls for it — an app pulls in
|
|
339
|
-
`ui-ux` and `build-app`, "it crashes" pulls in `debug`, an AI feature pulls in
|
|
340
|
-
`ai-features`, an API key pulls in `security` — so the whole skill is in
|
|
341
|
-
context before the model takes its first step. Waiting for the model to decide it needs design guidance means
|
|
342
|
-
finding out it did not after the app is built.
|
|
343
|
-
|
|
344
|
-
Add your own in `.ucode/skills/<name>/SKILL.md` inside a project. A project
|
|
345
|
-
skill shadows a built-in of the same name. Give it an `auto:` line and it loads
|
|
346
|
-
itself too:
|
|
347
|
-
|
|
348
|
-
```markdown
|
|
349
|
-
---
|
|
350
|
-
name: house-style
|
|
351
|
-
description: How we write services here.
|
|
352
|
-
auto: endpoint, handler, migration
|
|
353
|
-
---
|
|
354
|
-
|
|
355
|
-
Everything after the frontmatter is the instruction.
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
## Commands
|
|
359
|
-
|
|
360
|
-
| | |
|
|
361
|
-
| --- | --- |
|
|
362
|
-
| `/help` | the list |
|
|
363
|
-
| `/model` | show the models and switch — `/models` does the same |
|
|
364
|
-
| `/resume` | pick up an earlier conversation — `/session`, `/sessions` too |
|
|
365
|
-
| `/session delete 2,5` | delete saved conversations by number (or `d d` in the list) |
|
|
366
|
-
| `/new` | save this one and start fresh |
|
|
367
|
-
| `/remember <note>` | add a standing note to this project's `UCODE.md` |
|
|
368
|
-
| `/undo` | put back every file the last turn changed |
|
|
369
|
-
| `/look [url]` | open the running app and report what is on the page |
|
|
370
|
-
| `/deploy [folder]` | put the app online and get its link |
|
|
371
|
-
| `/stats` | time, steps and tokens this session |
|
|
372
|
-
| `/doctor` | check that everything ucode needs is working |
|
|
373
|
-
| `/skills` | what it knows how to do, and what is loaded |
|
|
374
|
-
| `/search <query>` | look something up on the web |
|
|
375
|
-
| `/copy` | last reply to the clipboard |
|
|
376
|
-
| `/clear` | clear the screen, keep the conversation |
|
|
377
|
-
| `/exit` | save and quit |
|
|
378
|
-
|
|
379
|
-
`ctrl+b` plan/build · `esc` stops a running turn · `ctrl+d` quits ·
|
|
380
|
-
`↑ ↓` scroll the conversation, or walk history once you are typing ·
|
|
381
|
-
`tab` completes a command
|
|
382
|
-
|
|
383
|
-
## Options
|
|
384
|
-
|
|
385
|
-
```
|
|
386
|
-
ucode [options]
|
|
387
|
-
|
|
388
|
-
-m, --model <id> which model to use
|
|
389
|
-
-C, --cwd <dir> work in another directory
|
|
390
|
-
--plan start in plan mode
|
|
391
|
-
--debug print stack traces when something breaks
|
|
392
|
-
-v, --version print the version
|
|
393
|
-
-h, --help the above
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
## Configuration
|
|
397
|
-
|
|
398
|
-
| | |
|
|
399
|
-
| --- | --- |
|
|
400
|
-
| `~/.ucode/.env` | `UCODE_API_KEY`, and `TAVILY_API_KEY` for web search |
|
|
401
|
-
| `~/.ucode/sessions/` | one JSON per conversation |
|
|
402
|
-
| `.ucode/skills/` | skills belonging to a project |
|
|
403
|
-
| `UCODE.md` | project memory, read every turn |
|
|
404
|
-
| `~/.ucode/UCODE.md` | your own standing instructions, for every project |
|
|
405
|
-
|
|
406
|
-
Environment overrides: `UCODE_MODEL`, `UCODE_WORKER_MODEL` (a faster model for
|
|
407
|
-
parallel workers), `UCODE_WORKER_STEPS`, `UCODE_MAX_CONTEXT_TOKENS`,
|
|
408
|
-
`UCODE_MAX_STEPS`, `UCODE_MAX_TOOL_OUTPUT`, `UCODE_REQUEST_TIMEOUT_MS`,
|
|
409
|
-
`UCODE_STALL_MS` (how long a silent reply is waited on before asking again, 60s),
|
|
410
|
-
`UCODE_BASE_URL`, `UCODE_NO_UPDATE`.
|
|
411
|
-
|
|
412
|
-
Web search needs a Tavily key — free, 1000 searches a month, no card. Without
|
|
413
|
-
one, ucode answers from what it knows and says that it could not check.
|
|
414
|
-
|
|
415
|
-
## How it is put together
|
|
416
|
-
|
|
417
|
-
```
|
|
418
|
-
ucode.js the command: arguments in, Agent out
|
|
419
|
-
src/core/loop.js the agent loop, the system prompt, the slash commands
|
|
420
|
-
src/core/provider.js the only file that knows which provider answers
|
|
421
|
-
src/core/history.js sessions on disk
|
|
422
|
-
src/core/window.js folding a long conversation to fit
|
|
423
|
-
src/core/skills.js loading skills, and deciding which load themselves
|
|
424
|
-
src/core/context.js the project map and project memory
|
|
425
|
-
src/core/failure.js one error shape: what, why, what next
|
|
426
|
-
src/tools/ the twenty-one tools, plus their shared plumbing
|
|
427
|
-
src/ui/screen.js the full-screen interface
|
|
428
|
-
src/ui/plain.js the same interface for when there is no terminal
|
|
429
|
-
src/ui/theme.js colour, boxes, and the string maths behind both
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
Everything above `provider.js` speaks one small provider-neutral message
|
|
433
|
-
format. Moving to another host means rewriting that one file.
|
|
434
|
-
|
|
435
|
-
Every failure carries three things — what was attempted, what failed, and what
|
|
436
|
-
to do next — so no screen ever has to fall back on a stack trace. Tool failures
|
|
437
|
-
are handed to the model as text instead, which is why they read like
|
|
438
|
-
instructions.
|
|
439
|
-
|
|
440
|
-
## Development
|
|
441
|
-
|
|
442
|
-
```bash
|
|
443
|
-
git clone https://github.com/sppideey/ucode-agent
|
|
444
|
-
cd ucode-agent
|
|
445
|
-
npm install
|
|
446
|
-
npm link # puts `ucode` on PATH, pointing at this checkout
|
|
447
|
-
npm test
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
`npm link` matters while developing: it symlinks the global command to your
|
|
451
|
-
working copy, so an edit is live on the next launch. Running
|
|
452
|
-
`npm i -g ucode-agent` replaces that with a frozen copy from the registry and
|
|
453
|
-
your edits stop taking effect.
|
|
454
|
-
|
|
455
|
-
The tests need no network and no framework — `node test/run.js` runs them all.
|
|
456
|
-
|
|
457
|
-
## Licence
|
|
458
|
-
|
|
459
|
-
MIT — see [LICENSE](LICENSE). Made with ❤️ by om dixit.
|
|
460
|
-
|
|
461
|
-
The edit matchers, the summary template, the context-overflow and transient-error
|
|
462
|
-
patterns and tool-name repair are adapted from
|
|
463
|
-
[opencode](https://opencode.ai) (MIT); see
|
|
464
|
-
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
|
1
|
+
# ucode
|
|
2
|
+
|
|
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 Google's
|
|
5
|
+
Gemini models, free with a key.
|
|
6
|
+
|
|
7
|
+
It opens on a quiet screen — the name, the place to type, and the version in the
|
|
8
|
+
corner:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗
|
|
12
|
+
██║ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝
|
|
13
|
+
██║ ██║██║ ██║ ██║██║ ██║█████╗
|
|
14
|
+
██║ ██║██║ ██║ ██║██║ ██║██╔══╝
|
|
15
|
+
╚██████╔╝╚██████╗╚██████╔╝██████╔╝███████╗
|
|
16
|
+
╚═════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
20
|
+
│ › Ask anything… │
|
|
21
|
+
│ │
|
|
22
|
+
│ BUILD North Mini Code 0% │
|
|
23
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
24
|
+
|
|
25
|
+
try build me a landing page for a coffee shop
|
|
26
|
+
explain what this project does and how it fits together
|
|
27
|
+
add a dark mode toggle that remembers the choice
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
v1.62.5
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
A light crosses the wordmark once as it opens, and the three lines under the box
|
|
34
|
+
are there so an empty screen has something to say. Once you are talking, each
|
|
35
|
+
message you send is marked down its left edge in the same blue as the input, so
|
|
36
|
+
your own words are easy to find in a long session — and each step the agent
|
|
37
|
+
takes carries the shape of the work: a hollow diamond to look, a filled one to
|
|
38
|
+
change, an arrow to run.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
▌ build a notes dashboard
|
|
42
|
+
|
|
43
|
+
◇ Read 3 files
|
|
44
|
+
◆ Writing index.html +148 -0
|
|
45
|
+
▸ Running npm run dev
|
|
46
|
+
|
|
47
|
+
The dashboard is at http://localhost:3000, and `npm run dev` brings it back up.
|
|
48
|
+
|
|
49
|
+
╭──────────────────────────────────────────────────────────────────────────────────╮
|
|
50
|
+
│ › now add a dark mode toggle │
|
|
51
|
+
│ │
|
|
52
|
+
│ BUILD North Mini Code 4% │
|
|
53
|
+
╰──────────────────────────────────────────────────────────────────────────────────╯
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The status sits inside the input box because it describes the thing you are
|
|
57
|
+
typing into. Three facts, no more: the live mode, the answering model, and how
|
|
58
|
+
full the context window is. The percentage turns amber at 75%, which is where
|
|
59
|
+
older turns start being folded into a summary. While a turn is running the
|
|
60
|
+
middle of that row carries the spinner and the way out of it, and hands the
|
|
61
|
+
space straight back when it finishes.
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm i -g ucode-agent
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then save your Google key (free at [aistudio.google.com/apikey](https://aistudio.google.com/apikey)):
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
ucode login YOUR_KEY
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
It is written to `~/.ucode/.env` as `GEMINI_API_KEY`. A `.env` in
|
|
76
|
+
the project you are working on wins over that one, and a real environment
|
|
77
|
+
variable wins over both.
|
|
78
|
+
|
|
79
|
+
Then, in any project:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
ucode
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Needs Node 22 or newer.
|
|
86
|
+
|
|
87
|
+
## The models
|
|
88
|
+
|
|
89
|
+
Served by Google (aistudio.google.com), free with a key.
|
|
90
|
+
|
|
91
|
+
| Model | For |
|
|
92
|
+
| --- | --- |
|
|
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 |
|
|
96
|
+
|
|
97
|
+
`/model` shows them and switches. `ucode -m gemini-3.5-flash` starts on one.
|
|
98
|
+
|
|
99
|
+
When Google is overloaded and Flash-Lite stops answering, ucode carries on with
|
|
100
|
+
3.5 Flash by itself and goes back to Flash-Lite a few minutes later.
|
|
101
|
+
|
|
102
|
+
## What it does
|
|
103
|
+
|
|
104
|
+
**Twenty-one tools.** `create_app`, `read_file`, `read_files`, `write_file`,
|
|
105
|
+
`batch_write`, `edit_file`, `multi_edit`, `edit_files`, `rename_symbol`,
|
|
106
|
+
`find_symbol`, `outline`, `type_of`, `add_block`, `list_dir`, `glob`, `grep`,
|
|
107
|
+
`run_command`, `run_commands`, `look_at_app`, `web_search`, `deploy`. Read-only
|
|
108
|
+
calls run in parallel, and start the moment the model finishes writing them —
|
|
109
|
+
while the rest of its reply is still arriving. Anything that writes runs on its
|
|
110
|
+
own, in order.
|
|
111
|
+
|
|
112
|
+
**It asks rather than guesses.** `type_of` opens a language service on the
|
|
113
|
+
TypeScript the project itself has installed and gives back the exact signature,
|
|
114
|
+
the JSDoc, and where a thing is defined — inferred types included. `find_symbol`
|
|
115
|
+
answers "where is this declared", which is the question `grep` is usually being
|
|
116
|
+
asked badly. `rename_symbol` renames by code shape, knowing where strings and
|
|
117
|
+
comments begin, because a find-and-replace that matched too much is the most
|
|
118
|
+
common broken edit.
|
|
119
|
+
|
|
120
|
+
**It checks itself as it goes.** After a change: an incremental type check that
|
|
121
|
+
answers in about a second rather than a cold minute, then the tests that reach
|
|
122
|
+
the files just changed, then anything the running dev server has complained
|
|
123
|
+
about since the last look. All three come back the way an error does, so the
|
|
124
|
+
model fixes them without being told.
|
|
125
|
+
|
|
126
|
+
**New apps are ready in seconds.** A starter installed once is kept and
|
|
127
|
+
hard-linked into the next app — the same files under another name, so it costs
|
|
128
|
+
no extra disk and skips the wait entirely.
|
|
129
|
+
|
|
130
|
+
**Apps start from a ready-made starter — and finish in the same call.**
|
|
131
|
+
`create_app` copies a starter that is already known to build, and takes the
|
|
132
|
+
app's files with it, so a one-page app is a single round trip: the starter
|
|
133
|
+
lands, the model's files are written over it, and the starter's own files come
|
|
134
|
+
back inside the result so there is nothing to read afterwards.
|
|
135
|
+
|
|
136
|
+
The default starter is `plain-html`: one page, one stylesheet, one module,
|
|
137
|
+
nothing to install and nothing to build. A tasks app, a game, a calculator or a
|
|
138
|
+
visualisation is finished before a framework would have finished installing.
|
|
139
|
+
`next-shadcn` is there for routes, a database or many screens — Next.js 16,
|
|
140
|
+
TypeScript, Tailwind 4, shadcn/ui with 25 components, light/dark, toasts and a
|
|
141
|
+
considered theme. Setting that up by hand is about four minutes
|
|
142
|
+
(`create-next-app` and the shadcn CLI measured at 116s and 130s) plus a dozen
|
|
143
|
+
round trips; the copy takes under a second, and its install runs in the
|
|
144
|
+
background while the model writes the first components.
|
|
145
|
+
|
|
146
|
+
**Built to be fast, and measured.** A traced build of a small Next.js app went
|
|
147
|
+
from 17 minutes and 116 model steps to about 6 minutes and 25 steps, by fixing
|
|
148
|
+
where the time actually went:
|
|
149
|
+
|
|
150
|
+
- Edits return the file as it now stands, so the model does not re-read it.
|
|
151
|
+
- Files written more than a few steps ago stop being re-sent in full; the
|
|
152
|
+
conversation stays small, so every step answers faster.
|
|
153
|
+
- A file-write whose JSON is malformed — a missing comma, an unescaped quote in
|
|
154
|
+
the code, raw line breaks — is repaired instead of thrown away with all its
|
|
155
|
+
output.
|
|
156
|
+
- An edit whose `old_string` is slightly off — a middle line remembered wrong,
|
|
157
|
+
escapes written out, a blank line at either end, a different indent — still
|
|
158
|
+
lands, through the fallback matchers from opencode's edit tool. Found in two
|
|
159
|
+
places is still refused, and so is a match far bigger than what was asked
|
|
160
|
+
for. `replace_all` does a rename in one call.
|
|
161
|
+
- A missing file comes back with the lookalikes beside it ("did you mean
|
|
162
|
+
App.jsx?"), and a tool named with the wrong case runs as the tool it means,
|
|
163
|
+
instead of each costing a round trip to learn a spelling.
|
|
164
|
+
- Every write is parsed on the spot, so a syntax error comes back in the same
|
|
165
|
+
step rather than a minute later from a failed build.
|
|
166
|
+
- A failed build that is missing a component or package says exactly which
|
|
167
|
+
command fixes it.
|
|
168
|
+
- The starter is the shadcn models already know (Radix), so the code they write
|
|
169
|
+
compiles the first time.
|
|
170
|
+
- A new app goes out without the tools it has nothing to point at — no symbol
|
|
171
|
+
lookup, no rename, no type query in an empty folder — and an instruction pack
|
|
172
|
+
that loads itself sends its short form, with the full one a `load_skill`
|
|
173
|
+
away. Both are re-read by the provider on every step, so what is not in the
|
|
174
|
+
request is time off every one of them.
|
|
175
|
+
- Ready-made blocks for a page with no build step as well as for React: a list you
|
|
176
|
+
can add to, tick off, rename and remove, a filter row, a localStorage store, a
|
|
177
|
+
dialog, toasts, a theme toggle. Typing is the slowest part of a build, and each
|
|
178
|
+
block is a hundred lines nobody has to type.
|
|
179
|
+
- A nested argument written the wrong way — a JSON string, a { path: contents } map
|
|
180
|
+
— is read rather than refused. Each refusal was a round trip spent being told
|
|
181
|
+
something that could simply be parsed.
|
|
182
|
+
- A tool that was not offered is refused rather than quietly run, so withholding one
|
|
183
|
+
from a new project, or from plan mode, means what it says.
|
|
184
|
+
- The closing message is cut to eight lines. A build that ends with the request
|
|
185
|
+
read back and every feature ticked off is a status report nobody asked for,
|
|
186
|
+
and it is the last thing left on screen.
|
|
187
|
+
|
|
188
|
+
`UCODE_TRACE=1` writes every model call and tool, with its duration, to
|
|
189
|
+
`~/.ucode/trace.jsonl`.
|
|
190
|
+
|
|
191
|
+
Measured on "build me a simple todo app" — same prompt, same model, two traced
|
|
192
|
+
runs: **27 model calls, 8 failed tool calls, no finished app** before this round of
|
|
193
|
+
work; **14 model calls, no failures, a working app in under two minutes** after it.
|
|
194
|
+
|
|
195
|
+
**Deploy in one line.** Say "deploy it", or type `/deploy [folder]`, and the app
|
|
196
|
+
goes live on Vercel. ucode picks a short project name that fits the app and is
|
|
197
|
+
free (`food-iq`, else `food-iq-app`…), copies the app's `.env` keys to Vercel as
|
|
198
|
+
encrypted variables, refuses code with a secret written into it (and says how to
|
|
199
|
+
move it to a server route), and gives you the link. Deploying again updates the
|
|
200
|
+
same link. Needs a token from vercel.com/account/tokens in `~/.ucode/.env` as
|
|
201
|
+
`VERCEL_TOKEN=...`.
|
|
202
|
+
|
|
203
|
+
**A look for every app.** `create_app` takes a design preset — ocean, grove,
|
|
204
|
+
sunset, graphite, violet or citrus — each a full light and dark palette with its
|
|
205
|
+
own font, so apps stop looking like the same default blue.
|
|
206
|
+
|
|
207
|
+
**Every turn can be taken back.** `/undo` puts back every file the last turn
|
|
208
|
+
changed — a rewritten file returns byte for byte, a file that did not exist
|
|
209
|
+
before is removed again. Each write keeps the original the first time that turn
|
|
210
|
+
touches it, so what comes back is the state before the turn rather than before
|
|
211
|
+
the last of six edits to the same file. An agent that writes to your disk on
|
|
212
|
+
its own should be able to take it back, whether or not the project has git.
|
|
213
|
+
|
|
214
|
+
**It notices when it is going round in circles.** The same failing edit, an edit
|
|
215
|
+
that changes nothing, or a build failing on the same errors three times gets a
|
|
216
|
+
firm, specific note; if that does not work, the turn moves to another model.
|
|
217
|
+
|
|
218
|
+
**It never dies at the daily limit.** When the free daily limit runs out mid-build,
|
|
219
|
+
ucode counts down to the reset and carries on by itself.
|
|
220
|
+
|
|
221
|
+
**You can see it working.** The status row shows the current step with a light
|
|
222
|
+
sweeping across it, the step count and the time, and each answer ends with
|
|
223
|
+
`✓ Done in 6m 12s · 25 steps`. When a dev server comes up, the app opens in your
|
|
224
|
+
browser (`UCODE_OPEN=0` turns that off).
|
|
225
|
+
|
|
226
|
+
**`/stats` and `ucode doctor`.** `/stats` shows the session's time, steps, tokens,
|
|
227
|
+
files and builds. `ucode doctor` (or `/doctor`) checks Node, npm, git, the API
|
|
228
|
+
key and today's free requests left, the browser, the Vercel token and the
|
|
229
|
+
version, with the fix for anything wrong.
|
|
230
|
+
|
|
231
|
+
**Parallel workers.** When a build splits into parts that touch different files
|
|
232
|
+
— the API route, the upload component, the results view — the model hands them
|
|
233
|
+
to up to three workers that build at the same time, each line in the transcript
|
|
234
|
+
tagged with the worker's name. File writes take turns so two never collide.
|
|
235
|
+
|
|
236
|
+
**Installs that start early.** The moment a `package.json` with dependencies is
|
|
237
|
+
written, its install starts in the background while the rest of the app is
|
|
238
|
+
still being written. An install the model asks for later waits for that one
|
|
239
|
+
instead of running twice, and anything run in that folder waits for it too.
|
|
240
|
+
|
|
241
|
+
**It opens what it built and uses it.** Every app build ends with a look — not
|
|
242
|
+
when the model remembers to ask for one, but as part of the same pass that
|
|
243
|
+
type-checks. It opens the app in a real browser (the Edge or Chrome already on
|
|
244
|
+
your machine, so there is nothing extra to download) at 375px and 1440px, and
|
|
245
|
+
serves the folder itself when there is no dev server to point at, which is how
|
|
246
|
+
a three-file app gets checked at all.
|
|
247
|
+
|
|
248
|
+
Then it uses the app. It types into the first field, presses Enter, and clicks
|
|
249
|
+
the button that submits — and if the page gains no elements, changes no text
|
|
250
|
+
and stores nothing, that is reported as the thing to fix before anything else.
|
|
251
|
+
A page that renders and has no working behaviour passes a type check, a syntax
|
|
252
|
+
check and a screenshot; the only way to find out is to press something.
|
|
253
|
+
|
|
254
|
+
It also reports console errors, failed requests, content that spills off a
|
|
255
|
+
phone screen, broken images and unlabeled controls, saves screenshots to
|
|
256
|
+
`.ucode/screenshots`, and has Nemotron Nano Omni review them the way a designer
|
|
257
|
+
would. The model fixes what it finds before calling the app done. Both widths load at once, and the designer review — the slow part — runs
|
|
258
|
+
on the first look at an app in each request and is skipped, not waited on, when
|
|
259
|
+
the vision model is busy. The look after the fixes re-runs only the fast checks:
|
|
260
|
+
a few seconds.
|
|
261
|
+
|
|
262
|
+
**Errors fixed before you see them.** When the model says it is done, ucode
|
|
263
|
+
type-checks every file it changed — `tsc --noEmit` for TypeScript projects,
|
|
264
|
+
a syntax check for JavaScript and Python — and hands any errors back to fix,
|
|
265
|
+
up to three rounds.
|
|
266
|
+
|
|
267
|
+
**A plan you can see.** For longer jobs the model keeps a short checklist, with
|
|
268
|
+
a bar across the top for how far along it is and one row per step, so the one in
|
|
269
|
+
progress is findable without reading the rest:
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
━━━━────── 2/5
|
|
273
|
+
✓ Scaffold
|
|
274
|
+
✓ Upload
|
|
275
|
+
▸ Score dial
|
|
276
|
+
○ Findings
|
|
277
|
+
○ Polish
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**It knows the project before it asks.** Each turn starts with a map of every
|
|
281
|
+
file and the names each code file exports, so the model goes straight to the
|
|
282
|
+
right file instead of searching for it.
|
|
283
|
+
|
|
284
|
+
**Project memory.** `UCODE.md` in a project — and `~/.ucode/UCODE.md` for how you
|
|
285
|
+
like to work everywhere — is read at the start of every turn. `/remember <note>`
|
|
286
|
+
adds a line to it.
|
|
287
|
+
|
|
288
|
+
**Edits that never guess.** `edit_file` matches exactly once or it fails, and
|
|
289
|
+
when it fails it says *why*. It tolerates what does not matter — tabs against
|
|
290
|
+
spaces, a different indent depth, Windows line endings — and re-indents the
|
|
291
|
+
replacement to fit the file, but a match found twice is still refused.
|
|
292
|
+
`edit_files` changes several files in one call, and writes none of them if any
|
|
293
|
+
edit fails.
|
|
294
|
+
|
|
295
|
+
**Diffs with real line numbers.** Removed lines are numbered where they were,
|
|
296
|
+
added lines where they now are. Numbers you can jump to, not decoration.
|
|
297
|
+
|
|
298
|
+
**Live commentary.** `● Listing src`, `● Running npm test` — what it is doing,
|
|
299
|
+
as it does it, named after the file or command rather than the tool.
|
|
300
|
+
|
|
301
|
+
**Two modes.** Build edits and runs. Plan reads and researches with the writing
|
|
302
|
+
tools withheld, which is stronger than asking a model nicely. `ctrl+b` swaps
|
|
303
|
+
them; the chip inside the input box says which is live.
|
|
304
|
+
|
|
305
|
+
**Sessions.** Everything is on disk under `~/.ucode/sessions`, saved after every
|
|
306
|
+
step. `/resume` lists them with what each one was actually about, the ones from
|
|
307
|
+
this folder first. Press `d` twice on one to delete it — the list stays open, so
|
|
308
|
+
clearing out several is quick — or `/session delete 2,5`.
|
|
309
|
+
|
|
310
|
+
**It updates itself.** Each launch checks npm in the background and, if there is
|
|
311
|
+
a newer version, installs it while you work. The next launch is the new one.
|
|
312
|
+
Set `UCODE_NO_UPDATE=1` to turn that off.
|
|
313
|
+
|
|
314
|
+
**A context window that folds rather than forgets.** Past 75% the oldest turns
|
|
315
|
+
are summarised instead of dropped, never cutting between a tool call and its
|
|
316
|
+
result. The full history stays on disk regardless.
|
|
317
|
+
|
|
318
|
+
**Screenshots.** Mention a `.png` in your message and it gets attached.
|
|
319
|
+
|
|
320
|
+
## Skills
|
|
321
|
+
|
|
322
|
+
A skill is a folder with a `SKILL.md`: frontmatter, then instructions. Only the
|
|
323
|
+
names and one-line descriptions go into the system prompt — a body is pulled in
|
|
324
|
+
when it is wanted, so the prompt stays the same size however many you add.
|
|
325
|
+
|
|
326
|
+
| Skill | For |
|
|
327
|
+
| --- | --- |
|
|
328
|
+
| `ui-ux` | interfaces: direction, tokens, layout, states, motion, accessibility |
|
|
329
|
+
| `build-app` | going from nothing to something running, and proving it runs |
|
|
330
|
+
| `debug` | finding the real cause instead of the first plausible one |
|
|
331
|
+
| `code-review` | reviewing a change the way a careful colleague would |
|
|
332
|
+
| `write-tests` | tests that fail for the right reason |
|
|
333
|
+
| `ai-features` | model-backed features: prompts with rules, validated JSON, images, failure handling |
|
|
334
|
+
| `security` | secrets, auth, ownership checks, injection, XSS, CSRF, SSRF, uploads |
|
|
335
|
+
| `performance` | measure first, find the real bottleneck, prove the win with numbers |
|
|
336
|
+
| `refactor` | change the shape of code without changing what it does |
|
|
337
|
+
|
|
338
|
+
**Every skill loads itself** when the request calls for it — an app pulls in
|
|
339
|
+
`ui-ux` and `build-app`, "it crashes" pulls in `debug`, an AI feature pulls in
|
|
340
|
+
`ai-features`, an API key pulls in `security` — so the whole skill is in
|
|
341
|
+
context before the model takes its first step. Waiting for the model to decide it needs design guidance means
|
|
342
|
+
finding out it did not after the app is built.
|
|
343
|
+
|
|
344
|
+
Add your own in `.ucode/skills/<name>/SKILL.md` inside a project. A project
|
|
345
|
+
skill shadows a built-in of the same name. Give it an `auto:` line and it loads
|
|
346
|
+
itself too:
|
|
347
|
+
|
|
348
|
+
```markdown
|
|
349
|
+
---
|
|
350
|
+
name: house-style
|
|
351
|
+
description: How we write services here.
|
|
352
|
+
auto: endpoint, handler, migration
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
Everything after the frontmatter is the instruction.
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Commands
|
|
359
|
+
|
|
360
|
+
| | |
|
|
361
|
+
| --- | --- |
|
|
362
|
+
| `/help` | the list |
|
|
363
|
+
| `/model` | show the models and switch — `/models` does the same |
|
|
364
|
+
| `/resume` | pick up an earlier conversation — `/session`, `/sessions` too |
|
|
365
|
+
| `/session delete 2,5` | delete saved conversations by number (or `d d` in the list) |
|
|
366
|
+
| `/new` | save this one and start fresh |
|
|
367
|
+
| `/remember <note>` | add a standing note to this project's `UCODE.md` |
|
|
368
|
+
| `/undo` | put back every file the last turn changed |
|
|
369
|
+
| `/look [url]` | open the running app and report what is on the page |
|
|
370
|
+
| `/deploy [folder]` | put the app online and get its link |
|
|
371
|
+
| `/stats` | time, steps and tokens this session |
|
|
372
|
+
| `/doctor` | check that everything ucode needs is working |
|
|
373
|
+
| `/skills` | what it knows how to do, and what is loaded |
|
|
374
|
+
| `/search <query>` | look something up on the web |
|
|
375
|
+
| `/copy` | last reply to the clipboard |
|
|
376
|
+
| `/clear` | clear the screen, keep the conversation |
|
|
377
|
+
| `/exit` | save and quit |
|
|
378
|
+
|
|
379
|
+
`ctrl+b` plan/build · `esc` stops a running turn · `ctrl+d` quits ·
|
|
380
|
+
`↑ ↓` scroll the conversation, or walk history once you are typing ·
|
|
381
|
+
`tab` completes a command
|
|
382
|
+
|
|
383
|
+
## Options
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
ucode [options]
|
|
387
|
+
|
|
388
|
+
-m, --model <id> which model to use
|
|
389
|
+
-C, --cwd <dir> work in another directory
|
|
390
|
+
--plan start in plan mode
|
|
391
|
+
--debug print stack traces when something breaks
|
|
392
|
+
-v, --version print the version
|
|
393
|
+
-h, --help the above
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Configuration
|
|
397
|
+
|
|
398
|
+
| | |
|
|
399
|
+
| --- | --- |
|
|
400
|
+
| `~/.ucode/.env` | `UCODE_API_KEY`, and `TAVILY_API_KEY` for web search |
|
|
401
|
+
| `~/.ucode/sessions/` | one JSON per conversation |
|
|
402
|
+
| `.ucode/skills/` | skills belonging to a project |
|
|
403
|
+
| `UCODE.md` | project memory, read every turn |
|
|
404
|
+
| `~/.ucode/UCODE.md` | your own standing instructions, for every project |
|
|
405
|
+
|
|
406
|
+
Environment overrides: `UCODE_MODEL`, `UCODE_WORKER_MODEL` (a faster model for
|
|
407
|
+
parallel workers), `UCODE_WORKER_STEPS`, `UCODE_MAX_CONTEXT_TOKENS`,
|
|
408
|
+
`UCODE_MAX_STEPS`, `UCODE_MAX_TOOL_OUTPUT`, `UCODE_REQUEST_TIMEOUT_MS`,
|
|
409
|
+
`UCODE_STALL_MS` (how long a silent reply is waited on before asking again, 60s),
|
|
410
|
+
`UCODE_BASE_URL`, `UCODE_NO_UPDATE`.
|
|
411
|
+
|
|
412
|
+
Web search needs a Tavily key — free, 1000 searches a month, no card. Without
|
|
413
|
+
one, ucode answers from what it knows and says that it could not check.
|
|
414
|
+
|
|
415
|
+
## How it is put together
|
|
416
|
+
|
|
417
|
+
```
|
|
418
|
+
ucode.js the command: arguments in, Agent out
|
|
419
|
+
src/core/loop.js the agent loop, the system prompt, the slash commands
|
|
420
|
+
src/core/provider.js the only file that knows which provider answers
|
|
421
|
+
src/core/history.js sessions on disk
|
|
422
|
+
src/core/window.js folding a long conversation to fit
|
|
423
|
+
src/core/skills.js loading skills, and deciding which load themselves
|
|
424
|
+
src/core/context.js the project map and project memory
|
|
425
|
+
src/core/failure.js one error shape: what, why, what next
|
|
426
|
+
src/tools/ the twenty-one tools, plus their shared plumbing
|
|
427
|
+
src/ui/screen.js the full-screen interface
|
|
428
|
+
src/ui/plain.js the same interface for when there is no terminal
|
|
429
|
+
src/ui/theme.js colour, boxes, and the string maths behind both
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Everything above `provider.js` speaks one small provider-neutral message
|
|
433
|
+
format. Moving to another host means rewriting that one file.
|
|
434
|
+
|
|
435
|
+
Every failure carries three things — what was attempted, what failed, and what
|
|
436
|
+
to do next — so no screen ever has to fall back on a stack trace. Tool failures
|
|
437
|
+
are handed to the model as text instead, which is why they read like
|
|
438
|
+
instructions.
|
|
439
|
+
|
|
440
|
+
## Development
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
git clone https://github.com/sppideey/ucode-agent
|
|
444
|
+
cd ucode-agent
|
|
445
|
+
npm install
|
|
446
|
+
npm link # puts `ucode` on PATH, pointing at this checkout
|
|
447
|
+
npm test
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
`npm link` matters while developing: it symlinks the global command to your
|
|
451
|
+
working copy, so an edit is live on the next launch. Running
|
|
452
|
+
`npm i -g ucode-agent` replaces that with a frozen copy from the registry and
|
|
453
|
+
your edits stop taking effect.
|
|
454
|
+
|
|
455
|
+
The tests need no network and no framework — `node test/run.js` runs them all.
|
|
456
|
+
|
|
457
|
+
## Licence
|
|
458
|
+
|
|
459
|
+
MIT — see [LICENSE](LICENSE). Made with ❤️ by om dixit.
|
|
460
|
+
|
|
461
|
+
The edit matchers, the summary template, the context-overflow and transient-error
|
|
462
|
+
patterns and tool-name repair are adapted from
|
|
463
|
+
[opencode](https://opencode.ai) (MIT); see
|
|
464
|
+
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
package/package.json
CHANGED
package/src/core/loop.js
CHANGED
|
@@ -51,6 +51,8 @@ import { StuckWatch, eventFor, describeHit } from './stuck.js';
|
|
|
51
51
|
import { serversReadySince } from '../tools/shell.js';
|
|
52
52
|
import { formatDuration } from '../ui/activity.js';
|
|
53
53
|
import { MAX_FILE_OUTPUT } from '../tools/shared.js';
|
|
54
|
+
import { openInBrowser } from './opener.js';
|
|
55
|
+
import { withScope } from './scope.js';
|
|
54
56
|
import { runDoctor } from './doctor.js';
|
|
55
57
|
import { JS_LOGIC } from './jslogic.js';
|
|
56
58
|
import { deploy } from '../tools/deploy.js';
|
|
@@ -110,19 +112,6 @@ const SILENT = new Set(['update_plan']);
|
|
|
110
112
|
/** How many rounds of "the type check found errors, fix them" one turn may take. */
|
|
111
113
|
const MAX_FIX_ROUNDS = 3;
|
|
112
114
|
|
|
113
|
-
/**
|
|
114
|
-
* Rides on a new app's request, the last thing the model reads. In the system
|
|
115
|
-
* prompt and the skill alone, Flash-Lite still built "make me a tasks app" as a
|
|
116
|
-
* tasks app plus pomodoro timer, kanban board and analytics — twice running.
|
|
117
|
-
*/
|
|
118
|
-
const SCOPE_NOTE =
|
|
119
|
-
'(From ucode: if this asks for a new app, build it complete and well made, but as the one app asked ' +
|
|
120
|
-
'for - no extra views or tools such as timers, calendars, kanban boards, analytics, stats or an ' +
|
|
121
|
-
'editor for making your own, unless the request names them.)';
|
|
122
|
-
|
|
123
|
-
/** A request that may be for a new app. Loose on purpose: the note it adds says "if". */
|
|
124
|
-
const BUILD_ASK = /\b(?:make|build|create|design|code|write|generate|develop)\b|\bi\s+(?:want|need)\b|\b(?:app|game|website|site|page|tracker|calculator|quiz)\b/i;
|
|
125
|
-
|
|
126
115
|
/** A file up to this many lines is sent whole on its first read, whatever slice was asked for. */
|
|
127
116
|
const WHOLE_READ_LINES = 1500;
|
|
128
117
|
|
|
@@ -1262,7 +1251,7 @@ export class Agent {
|
|
|
1262
1251
|
// Nothing to look up in an empty folder, so those tools do not go out with
|
|
1263
1252
|
// the request. Decided per turn: the moment there is code, they are back.
|
|
1264
1253
|
this.fresh = !hasCode(this.map);
|
|
1265
|
-
|
|
1254
|
+
request.content = withScope(input, { fresh: this.fresh });
|
|
1266
1255
|
this.wantsWeb = WANTS_WEB.test(input);
|
|
1267
1256
|
await this.persist();
|
|
1268
1257
|
|
|
@@ -1795,26 +1784,10 @@ export class Agent {
|
|
|
1795
1784
|
if (this.openInBrowser(server.url)) this.ui.note(`Opened ${server.url} in your browser`);
|
|
1796
1785
|
}
|
|
1797
1786
|
|
|
1798
|
-
/**
|
|
1799
|
-
* Show a URL or a local page in the desktop browser. False when it did not.
|
|
1800
|
-
*
|
|
1801
|
-
* A file goes to explorer on Windows, not `cmd /c start`: the folder name
|
|
1802
|
-
* comes from the model, and an & in it would be a second command to cmd.
|
|
1803
|
-
*/
|
|
1787
|
+
/** Show a URL or a page in the project in the desktop browser (see opener.js). UCODE_OPEN=0 turns it off. */
|
|
1804
1788
|
openInBrowser(target) {
|
|
1805
1789
|
if (!this.full || process.env.UCODE_OPEN === '0') return false;
|
|
1806
|
-
|
|
1807
|
-
// Only pages inside the project: a UNC path would have explorer reach out to another machine.
|
|
1808
|
-
const rel = web ? '' : path.relative(this.cwd, target);
|
|
1809
|
-
if (!web && (!rel || rel.startsWith('..') || path.isAbsolute(rel))) return false;
|
|
1810
|
-
const [cmd, args] = process.platform === 'win32'
|
|
1811
|
-
? (web ? ['cmd', ['/c', 'start', '', target]] : ['explorer.exe', [target]])
|
|
1812
|
-
: [process.platform === 'darwin' ? 'open' : 'xdg-open', [target]];
|
|
1813
|
-
try {
|
|
1814
|
-
// A missing opener (no xdg-open) fails later, as an event; unheard, it would crash ucode.
|
|
1815
|
-
spawn(cmd, args, { stdio: 'ignore', detached: true, windowsHide: true }).on('error', () => {}).unref();
|
|
1816
|
-
return true;
|
|
1817
|
-
} catch { return false; /* no browser to open — the link is in the answer */ }
|
|
1790
|
+
return openInBrowser(target, { root: this.cwd });
|
|
1818
1791
|
}
|
|
1819
1792
|
|
|
1820
1793
|
cmdStats() {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opener.js — showing a finished page or a running server in the user's own
|
|
3
|
+
* browser.
|
|
4
|
+
*
|
|
5
|
+
* Kept apart from the loop because it is the one place ucode starts a program
|
|
6
|
+
* on a path the model chose, and that deserves to be read on its own.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
|
|
12
|
+
/** Is this a web address rather than a file on disk? */
|
|
13
|
+
const isWeb = (target) => /^https?:\/\//.test(target);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Only pages inside the project are opened. A folder name comes from the
|
|
17
|
+
* model, and a UNC path (\\host\share) would have explorer reach out to
|
|
18
|
+
* another machine.
|
|
19
|
+
*/
|
|
20
|
+
export function insideRoot(target, root) {
|
|
21
|
+
const rel = path.relative(root, target);
|
|
22
|
+
return Boolean(rel) && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Open a URL or a page inside `root`. Returns whether an opener was started.
|
|
27
|
+
*
|
|
28
|
+
* A file goes to explorer on Windows, not `cmd /c start`: an & in a folder
|
|
29
|
+
* name would be a second command to cmd.
|
|
30
|
+
*/
|
|
31
|
+
export function openInBrowser(target, { root }) {
|
|
32
|
+
const web = isWeb(target);
|
|
33
|
+
if (!web && !insideRoot(target, root)) return false;
|
|
34
|
+
const [cmd, args] = process.platform === 'win32'
|
|
35
|
+
? (web ? ['cmd', ['/c', 'start', '', target]] : ['explorer.exe', [target]])
|
|
36
|
+
: [process.platform === 'darwin' ? 'open' : 'xdg-open', [target]];
|
|
37
|
+
try {
|
|
38
|
+
// A missing opener (no xdg-open) fails later, as an event; unheard, it would crash ucode.
|
|
39
|
+
spawn(cmd, args, { stdio: 'ignore', detached: true, windowsHide: true }).on('error', () => {}).unref();
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false; // no browser to open — the link is in the answer
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scope.js — keeping a new app to what was asked for.
|
|
3
|
+
*
|
|
4
|
+
* In the system prompt and the build-app skill alone, Flash-Lite still built
|
|
5
|
+
* "make me a tasks app" as a tasks app plus a pomodoro timer, a kanban board
|
|
6
|
+
* and analytics — twice running — and the extra views were where the builds
|
|
7
|
+
* broke. So the rule also rides on the request itself, the last thing the
|
|
8
|
+
* model reads before it starts.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const SCOPE_NOTE =
|
|
12
|
+
'(From ucode: if this asks for a new app, build it complete and well made, but as the one app asked ' +
|
|
13
|
+
'for - no extra views or tools such as timers, calendars, kanban boards, analytics, stats or an ' +
|
|
14
|
+
'editor for making your own, unless the request names them.)';
|
|
15
|
+
|
|
16
|
+
/** A request that may be for a new app. Loose on purpose: the note it adds says "if". */
|
|
17
|
+
export const BUILD_ASK =
|
|
18
|
+
/\b(?:make|build|create|design|code|write|generate|develop)\b|\bi\s+(?:want|need)\b|\b(?:app|game|website|site|page|tracker|calculator|quiz)\b/i;
|
|
19
|
+
|
|
20
|
+
/** The request as the model sees it: one in an empty folder, or shaped like a build, carries the note. */
|
|
21
|
+
export function withScope(input, { fresh = false } = {}) {
|
|
22
|
+
return fresh || BUILD_ASK.test(input) ? `${input}\n\n${SCOPE_NOTE}` : input;
|
|
23
|
+
}
|
package/src/tools/browser.js
CHANGED
|
@@ -232,6 +232,63 @@ const NOT_ADD = /search|filter|find|sort|query/i;
|
|
|
232
232
|
const LIST_HINT = /task|todo|to-do|item|note|add|new|entry|expense|habit|title|what needs|remind/i;
|
|
233
233
|
const ADD_BUTTON = /^\s*(?:\+|add|new|create|save|submit|post|done)\b|\b(?:add|create|save)\s/i;
|
|
234
234
|
|
|
235
|
+
/** A value a form will take for a field of this type, or null to leave the field alone. */
|
|
236
|
+
function sampleFor(type, today) {
|
|
237
|
+
switch (type) {
|
|
238
|
+
case 'number': return '42';
|
|
239
|
+
case 'date': return today;
|
|
240
|
+
case 'time': return '12:00';
|
|
241
|
+
case 'datetime-local': return `${today}T12:00`;
|
|
242
|
+
case 'month': return today.slice(0, 7);
|
|
243
|
+
case 'email': return 'check@example.com';
|
|
244
|
+
case 'url': return 'https://example.com';
|
|
245
|
+
case 'tel': return '5550100';
|
|
246
|
+
case 'text': case 'textarea': case 'search': case '': return PROBE_TEXT;
|
|
247
|
+
default: return null; // checkboxes, radios, files, colours: nothing to guess
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Fill in the rest of the form the probe text went into, the way a person
|
|
253
|
+
* would before pressing Add.
|
|
254
|
+
*
|
|
255
|
+
* Traced: a budget tracker's form also needs an amount. Only the description
|
|
256
|
+
* was typed, the browser refused the submit, and a working app was reported as
|
|
257
|
+
* "ADDING DOES NOT WORK" — the model then spent its fix rounds on code that
|
|
258
|
+
* worked. Filled: required fields, empty number fields (amounts are often
|
|
259
|
+
* checked in script, not marked required) and dropdowns left on a blank choice.
|
|
260
|
+
*/
|
|
261
|
+
async function fillTheRest(page, field) {
|
|
262
|
+
const marked = await field.evaluate((e) => {
|
|
263
|
+
if (!e.form) return false;
|
|
264
|
+
e.form.setAttribute('data-ucode-probe', '');
|
|
265
|
+
return true;
|
|
266
|
+
}).catch(() => false);
|
|
267
|
+
if (!marked) return;
|
|
268
|
+
|
|
269
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
270
|
+
const all = page.locator('form[data-ucode-probe] :is(input, select, textarea)');
|
|
271
|
+
const n = Math.min(await all.count().catch(() => 0), 20);
|
|
272
|
+
for (let i = 0; i < n; i++) {
|
|
273
|
+
const el = all.nth(i);
|
|
274
|
+
const info = await el.evaluate((e) => ({
|
|
275
|
+
tag: e.tagName.toLowerCase(),
|
|
276
|
+
type: (e.getAttribute('type') || (e.tagName === 'TEXTAREA' ? 'textarea' : '')).toLowerCase(),
|
|
277
|
+
empty: !e.value,
|
|
278
|
+
required: e.required,
|
|
279
|
+
})).catch(() => null);
|
|
280
|
+
if (!info?.empty || !(await el.isVisible().catch(() => false))) continue;
|
|
281
|
+
if (info.tag === 'select') {
|
|
282
|
+
await el.selectOption({ index: 1 }, { timeout: 2_000 }).catch(() => {});
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (!info.required && info.type !== 'number') continue;
|
|
286
|
+
const value = sampleFor(info.type, today);
|
|
287
|
+
if (value !== null) await el.fill(value, { timeout: 2_000 }).catch(() => {});
|
|
288
|
+
}
|
|
289
|
+
await page.locator('form[data-ucode-probe]').evaluate((f) => f.removeAttribute('data-ucode-probe')).catch(() => {});
|
|
290
|
+
}
|
|
291
|
+
|
|
235
292
|
async function tryAdd(page) {
|
|
236
293
|
const shows = () => page.evaluate((t) => document.body.innerText.includes(t), PROBE_TEXT).catch(() => false);
|
|
237
294
|
const settle = () => page.waitForTimeout(400);
|
|
@@ -279,6 +336,7 @@ async function tryAdd(page) {
|
|
|
279
336
|
}
|
|
280
337
|
const ok = await field.el.fill(PROBE_TEXT, { timeout: 2_000 }).then(() => true).catch(() => false);
|
|
281
338
|
if (!ok) return tried.length ? { tried, added: false, listApp } : null;
|
|
339
|
+
await fillTheRest(page, field.el);
|
|
282
340
|
await field.el.press('Enter', { timeout: 2_000 }).catch(() => {});
|
|
283
341
|
await settle();
|
|
284
342
|
tried.push(`typed "${PROBE_TEXT}" into ${field.hint ? `"${field.hint.slice(0, 30)}"` : 'the field'} and pressed Enter`);
|