ucode-agent 1.32.0 → 1.33.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/package.json +5 -5
- package/src/core/provider.js +12 -0
- package/src/tools/index.js +5 -1
- package/src/tools/scaffold.js +57 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucode-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.0",
|
|
4
4
|
"description": "ucode - a terminal coding agent that reads, edits and runs your code, on NVIDIA and Cohere models.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "ucode.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"start": "node ucode.js",
|
|
18
18
|
"test": "node test/run.js",
|
|
19
|
-
"prepublishOnly": "node test/run.js"
|
|
19
|
+
"prepublishOnly": "node scripts/no-bundled-key.js && node test/run.js"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
@@ -26,9 +26,6 @@
|
|
|
26
26
|
"url": "https://github.com/sppideey/ucode-agent/issues"
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/sppideey/ucode-agent#readme",
|
|
29
|
-
"publishConfig": {
|
|
30
|
-
"access": "public"
|
|
31
|
-
},
|
|
32
29
|
"engines": {
|
|
33
30
|
"node": ">=22"
|
|
34
31
|
},
|
|
@@ -56,5 +53,8 @@
|
|
|
56
53
|
},
|
|
57
54
|
"devDependencies": {
|
|
58
55
|
"typescript": "^5.9.3"
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
59
59
|
}
|
|
60
60
|
}
|
package/src/core/provider.js
CHANGED
|
@@ -107,6 +107,18 @@ export const FALLBACKS = [
|
|
|
107
107
|
|
|
108
108
|
/** The next model to try after `id`, skipping any already tried this round. */
|
|
109
109
|
export function fallbackFor(id, tried = new Set()) {
|
|
110
|
+
// Off unless UCODE_FALLBACK=1. A build that starts on one model and finishes
|
|
111
|
+
// on another finishes to a different standard, and the swap lands exactly
|
|
112
|
+
// when the user is least placed to work out why the output changed —
|
|
113
|
+
// mid-build, behind a note that scrolls past. Which model to run is the one
|
|
114
|
+
// decision they made before starting; it is not one to take back for them.
|
|
115
|
+
//
|
|
116
|
+
// Every caller already reads "no fallback" as "wait, then try this one
|
|
117
|
+
// again": failover waits a minute and returns to the same model, the
|
|
118
|
+
// stuck-detector simply does not switch, and a worker retries its own. That
|
|
119
|
+
// is why this can be a single gate rather than four.
|
|
120
|
+
if (process.env.UCODE_FALLBACK !== '1') return null;
|
|
121
|
+
|
|
110
122
|
const start = Math.max(0, FALLBACKS.indexOf(id));
|
|
111
123
|
for (let i = 1; i <= FALLBACKS.length; i++) {
|
|
112
124
|
const next = FALLBACKS[(start + i) % FALLBACKS.length];
|
package/src/tools/index.js
CHANGED
|
@@ -85,7 +85,11 @@ export const tools = [
|
|
|
85
85
|
'Start a new app AND write it, in one call. Pass "files" with the whole app and this ' +
|
|
86
86
|
'is the only call the build needs: the starter lands, your files are written over it, ' +
|
|
87
87
|
'and the result comes back with everything. Two starters. "plain-html" (the default): ' +
|
|
88
|
-
'one index.html, one stylesheet, one ES module — nothing to install, nothing to build, ' +
|
|
88
|
+
'one index.html, one stylesheet, one ES module — nothing to install, nothing to build, ' +
|
|
89
|
+
'and the stylesheet is a design system already: a palette, a spacing scale, radii, ' +
|
|
90
|
+
'motion timings, focus rings and a breakpoint. Re-tint those tokens to suit the app ' +
|
|
91
|
+
'and compose every rule from them. Replacing it with raw pixel values is what an ' +
|
|
92
|
+
'undesigned page is made of. ' +
|
|
89
93
|
'opens straight in a browser, and its three files come back inside this result so there ' +
|
|
90
94
|
'is never a reason to read them. Use it for anything that is one page: a tasks app, a ' +
|
|
91
95
|
'toy, a game, a visualisation, a calculator, a timer. "next-shadcn": Next.js 16, ' +
|
package/src/tools/scaffold.js
CHANGED
|
@@ -108,6 +108,46 @@ async function copyTree(from, to, fill) {
|
|
|
108
108
|
* globals.css and layout.tsx. Apps stop looking like the same default blue.
|
|
109
109
|
* Returns the preset used, or null when the starter has none.
|
|
110
110
|
*/
|
|
111
|
+
/**
|
|
112
|
+
* The file in each starter that carries the design, not just some of the rules.
|
|
113
|
+
*
|
|
114
|
+
* next-shadcn has globals.css, which applyDesign re-tints. plain-html has one
|
|
115
|
+
* stylesheet and it is the whole design system: the palette, a spacing scale,
|
|
116
|
+
* radii, motion timings, focus rings, a reduced-motion rule and a breakpoint.
|
|
117
|
+
*/
|
|
118
|
+
const TOKEN_FILE = { 'plain-html': 'styles.css' };
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Put the starter's token block back when the app wrote over it without one.
|
|
122
|
+
*
|
|
123
|
+
* The stylesheet in plain-html is a design, not a placeholder — but `files`
|
|
124
|
+
* lands straight on top of the starter, so a model passing its own styles.css
|
|
125
|
+
* replaces the scale, the palette and the timings with whatever it typed. What
|
|
126
|
+
* comes back is hand-rolled CSS with no system behind it, and an app built on
|
|
127
|
+
* raw pixel values has uneven spacing everywhere for the rest of its life.
|
|
128
|
+
*
|
|
129
|
+
* A replacement that declares its own custom properties is left alone: that is
|
|
130
|
+
* a model doing the job properly, and second-guessing it would be worse. Only
|
|
131
|
+
* a stylesheet with no :root variables at all gets the starter's block put
|
|
132
|
+
* back above it, where every rule underneath can reach it.
|
|
133
|
+
*/
|
|
134
|
+
async function keepDesignTokens(appDir, template, written, starterCss) {
|
|
135
|
+
const rel = TOKEN_FILE[template];
|
|
136
|
+
if (!rel || !starterCss) return null;
|
|
137
|
+
|
|
138
|
+
const abs = path.join(appDir, rel);
|
|
139
|
+
if (!written.has(abs)) return null; // never overwritten
|
|
140
|
+
|
|
141
|
+
const now = await fs.readFile(abs, 'utf8').catch(() => null);
|
|
142
|
+
if (now === null || /:root\s*\{[^}]*--/.test(now)) return null;
|
|
143
|
+
|
|
144
|
+
const block = /:root\s*\{[\s\S]*?\n\}/.exec(starterCss);
|
|
145
|
+
if (!block) return null;
|
|
146
|
+
|
|
147
|
+
await fs.writeFile(abs, `${block[0]}\n\n${now}`, 'utf8');
|
|
148
|
+
return rel;
|
|
149
|
+
}
|
|
150
|
+
|
|
111
151
|
export async function applyDesign(appDir, design) {
|
|
112
152
|
const dir = path.join(appDir, 'presets');
|
|
113
153
|
const names = (await fs.readdir(dir).catch(() => [])).filter((f) => f.endsWith('.json'));
|
|
@@ -242,7 +282,13 @@ export async function createApp({ folder, name, description, template = 'plain-h
|
|
|
242
282
|
// Next.js serves static files from public/; a plain page has no such place
|
|
243
283
|
// and an empty folder in a three-file app is clutter.
|
|
244
284
|
if (template !== 'plain-html') await fs.mkdir(path.join(target.abs, 'public'), { recursive: true });
|
|
245
|
-
const look = await applyDesign(target.abs, design);
|
|
285
|
+
const look = await applyDesign(target.abs, design);
|
|
286
|
+
|
|
287
|
+
// Read before the app's own files land on top of it, so the tokens can be
|
|
288
|
+
// put back if the replacement arrives without any.
|
|
289
|
+
const starterCss = TOKEN_FILE[template]
|
|
290
|
+
? await fs.readFile(path.join(target.abs, TOKEN_FILE[template]), 'utf8').catch(() => null)
|
|
291
|
+
: null;
|
|
246
292
|
|
|
247
293
|
// The starter has been installed on this machine before: hard-link that
|
|
248
294
|
// tree in, which is seconds where npm is a minute. Otherwise install as
|
|
@@ -271,7 +317,8 @@ export async function createApp({ folder, name, description, template = 'plain-h
|
|
|
271
317
|
// one, and round trips are nearly all of the time a build takes.
|
|
272
318
|
const mine = given;
|
|
273
319
|
const wrote = mine.length ? await batchWrite({ files: mine }) : null;
|
|
274
|
-
const written = new Set(mine.map((f) => resolveIn(f.path, 'create_app', 'files').abs));
|
|
320
|
+
const written = new Set(mine.map((f) => resolveIn(f.path, 'create_app', 'files').abs));
|
|
321
|
+
const keptTokens = await keepDesignTokens(target.abs, template, written, starterCss);
|
|
275
322
|
|
|
276
323
|
// The starter's own files, in full, so there is never a reason to read them
|
|
277
324
|
// back — and only the ones this call did not already write over. A read is
|
|
@@ -287,7 +334,14 @@ export async function createApp({ folder, name, description, template = 'plain-h
|
|
|
287
334
|
const out = result(
|
|
288
335
|
`Created ${target.show} from the ${template} starter — ${copied.length} files, already known to build.\n` +
|
|
289
336
|
(look ? `Design: the ${look.name} preset (${look.summary}), font ${look.fonts?.sans ?? 'Geist'}.\n` : '') +
|
|
290
|
-
(wrote ? `\nYour ${mine.length} file${mine.length === 1 ? '' : 's'}:\n${wrote.content}\n` : '') +
|
|
337
|
+
(wrote ? `\nYour ${mine.length} file${mine.length === 1 ? '' : 's'}:\n${wrote.content}\n` : '') +
|
|
338
|
+
(keptTokens
|
|
339
|
+
? `\nYour ${keptTokens} arrived with no :root block, so the starter's was kept above it — ` +
|
|
340
|
+
'the palette, the spacing scale (--s1 to --s5), the radius and the motion timings. ' +
|
|
341
|
+
'Build the rest of the stylesheet out of those variables: spacing that comes from a ' +
|
|
342
|
+
'scale is the difference between a designed page and an arranged one. Re-tint the ' +
|
|
343
|
+
'values to suit this app; do not go back to raw pixels.\n'
|
|
344
|
+
: '') +
|
|
291
345
|
(linked
|
|
292
346
|
? `Its packages are already in place (${linked.toLocaleString()} files, linked from the starter cache) — ` +
|
|
293
347
|
'nothing to install: build and run straight away.\n'
|