ucode-agent 1.44.0 → 1.45.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 +2 -1
- package/src/core/csscheck.js +77 -0
- package/src/core/jslogic.js +69 -0
- package/src/core/loop.js +71 -7
- package/src/tools/browser.js +546 -496
- package/src/tools/index.js +13 -10
- package/src/tools/scaffold.js +80 -65
- package/src/tools/shared.js +389 -343
- package/src/ui/plain.js +353 -353
- package/src/ui/screen.js +1699 -1692
- package/src/ui/theme.js +814 -776
package/src/tools/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createApp, TEMPLATE_NAMES, TEMPLATE_NOTES } from './scaffold.js';
|
|
|
16
16
|
import { deploy } from './deploy.js';
|
|
17
17
|
import { clip, READ_LINES } from './shared.js';
|
|
18
18
|
|
|
19
|
-
export { setRoot, setConfirm, getRoot } from './shared.js';
|
|
19
|
+
export { setRoot, setConfirm, setRequest, getRoot } from './shared.js';
|
|
20
20
|
|
|
21
21
|
const str = (description) => ({ type: 'string', description });
|
|
22
22
|
const int = (description) => ({ type: 'integer', description });
|
|
@@ -85,20 +85,20 @@ 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, ' +
|
|
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 ' +
|
|
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
92
|
'undesigned page is made of. ' +
|
|
93
93
|
'opens straight in a browser, and its three files come back inside this result so there ' +
|
|
94
94
|
'is never a reason to read them. Use it for anything that is one page: a tasks app, a ' +
|
|
95
95
|
'toy, a game, a visualisation, a calculator, a timer. "next-shadcn": Next.js 16, ' +
|
|
96
96
|
'TypeScript, Tailwind 4 and shadcn with 33 components — only when the app genuinely ' +
|
|
97
97
|
'needs routes, a database or many screens, because it costs an install and a build. ' +
|
|
98
|
-
'This is how every Next.js app begins - never run create-next-app or shadcn init. ' +
|
|
99
|
-
'In "next-shadcn" every path in "files" goes under src/: a page is a route only at ' +
|
|
100
|
-
'<app>/src/app/page.tsx or <app>/src/app/<segment>/page.tsx, an API handler only at ' +
|
|
101
|
-
'<app>/src/app/api/<name>/route.ts, and components at <app>/src/components/<feature>/. ' +
|
|
98
|
+
'This is how every Next.js app begins - never run create-next-app or shadcn init. ' +
|
|
99
|
+
'In "next-shadcn" every path in "files" goes under src/: a page is a route only at ' +
|
|
100
|
+
'<app>/src/app/page.tsx or <app>/src/app/<segment>/page.tsx, an API handler only at ' +
|
|
101
|
+
'<app>/src/app/api/<name>/route.ts, and components at <app>/src/components/<feature>/. ' +
|
|
102
102
|
'A page.tsx written anywhere else is an ordinary file the router never serves.',
|
|
103
103
|
parameters: {
|
|
104
104
|
type: 'object',
|
|
@@ -124,7 +124,10 @@ export const tools = [
|
|
|
124
124
|
},
|
|
125
125
|
template: {
|
|
126
126
|
type: 'string',
|
|
127
|
-
|
|
127
|
+
// The default goes first: a model picking from an enum reaches for
|
|
128
|
+
// the head of the list, and the head of this one used to be the
|
|
129
|
+
// starter that costs an install and a build.
|
|
130
|
+
enum: ['plain-html', 'next-shadcn'],
|
|
128
131
|
description:
|
|
129
132
|
'Which starter. "plain-html" (the default) for one page, a toy, a game, or any ' +
|
|
130
133
|
'app that does not need a server: no install, no build, nothing to wait for. ' +
|
package/src/tools/scaffold.js
CHANGED
|
@@ -14,7 +14,7 @@ import { promises as fs } from 'node:fs';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import { ToolFailure } from '../core/failure.js';
|
|
17
|
-
import { resolveIn, guard, result } from './shared.js';
|
|
17
|
+
import { resolveIn, guard, result, askedForPlainHtml } from './shared.js';
|
|
18
18
|
import { batchWrite } from './files.js';
|
|
19
19
|
import { packageJsonWritten, installIn } from './shell.js';
|
|
20
20
|
import { restore, populate } from './cache.js';
|
|
@@ -173,60 +173,60 @@ async function unprefixOwnFolder(appDir, written) {
|
|
|
173
173
|
return fixed;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
/**
|
|
177
|
-
* Files that legitimately sit at the root of a Next.js project.
|
|
178
|
-
*
|
|
179
|
-
* Everything else a model writes there is app code that has missed src/.
|
|
180
|
-
*/
|
|
181
|
-
const NEXT_ROOT = /^(?:package(?:-lock)?\.json|next\.config\.[mc]?[jt]s|tsconfig\.json|postcss\.config\.[mc]?js|eslint\.config\.[mc]?js|components\.json|README\.md|TEMPLATE\.md|\.gitignore|next-env\.d\.ts)$/i;
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Put Next.js files where Next.js looks for them.
|
|
185
|
-
*
|
|
186
|
-
* Routing is folder-based and unforgiving: a page is a route only at
|
|
187
|
-
* src/app/<segment>/page.tsx. A traced build wrote page.tsx at the project
|
|
188
|
-
* root and api/expenses/route.ts beside it — the build passed, every route
|
|
189
|
-
* 404ed, and nothing said why.
|
|
190
|
-
*
|
|
191
|
-
* The guide has the tree in it, and the guide cannot help: it comes back
|
|
192
|
-
* inside the create_app result, and the model chooses these paths in the call
|
|
193
|
-
* that produces that result. It is reading the map after it has parked. So the
|
|
194
|
-
* paths are corrected on the way in, and the result says what moved and why,
|
|
195
|
-
* which is the copy that arrives in time to matter for the next file.
|
|
196
|
-
*
|
|
197
|
-
* Only for next-shadcn, only for files under the app folder, and never for the
|
|
198
|
-
* config files that genuinely belong at the root.
|
|
199
|
-
*/
|
|
200
|
-
function placeForNext(files, folder) {
|
|
201
|
-
const moved = [];
|
|
202
|
-
|
|
203
|
-
const out = files.map((f) => {
|
|
204
|
-
const rel = String(f.path ?? '').split('\\').join('/');
|
|
205
|
-
const prefix = `${folder}/`;
|
|
206
|
-
if (!rel.startsWith(prefix)) return f;
|
|
207
|
-
|
|
208
|
-
const inside = rel.slice(prefix.length);
|
|
209
|
-
if (!inside || inside.startsWith('src/') || inside.startsWith('public/')) return f;
|
|
210
|
-
if (NEXT_ROOT.test(inside)) return f;
|
|
211
|
-
|
|
212
|
-
// app/... is the right tree written one level too high; everything else
|
|
213
|
-
// that is app code belongs under src/ as it stands.
|
|
214
|
-
const under = inside.startsWith('app/') ? `src/${inside}` : `src/app/${inside}`;
|
|
215
|
-
const isRoute = /(?:^|\/)(?:page|layout|loading|error|not-found)\.[jt]sx?$/.test(inside)
|
|
216
|
-
|| /(?:^|\/)route\.[jt]s$/.test(inside);
|
|
217
|
-
const isCode = /^(?:components|lib|hooks|styles|utils)\//.test(inside);
|
|
218
|
-
|
|
219
|
-
if (!isRoute && !isCode) return f;
|
|
220
|
-
const to = isCode ? `${prefix}src/${inside}` : `${prefix}${under}`;
|
|
221
|
-
if (to === rel) return f;
|
|
222
|
-
|
|
223
|
-
moved.push(`${inside} -> ${to.slice(prefix.length)}`);
|
|
224
|
-
return { ...f, path: to };
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
return { files: out, moved };
|
|
228
|
-
}
|
|
229
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Files that legitimately sit at the root of a Next.js project.
|
|
178
|
+
*
|
|
179
|
+
* Everything else a model writes there is app code that has missed src/.
|
|
180
|
+
*/
|
|
181
|
+
const NEXT_ROOT = /^(?:package(?:-lock)?\.json|next\.config\.[mc]?[jt]s|tsconfig\.json|postcss\.config\.[mc]?js|eslint\.config\.[mc]?js|components\.json|README\.md|TEMPLATE\.md|\.gitignore|next-env\.d\.ts)$/i;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Put Next.js files where Next.js looks for them.
|
|
185
|
+
*
|
|
186
|
+
* Routing is folder-based and unforgiving: a page is a route only at
|
|
187
|
+
* src/app/<segment>/page.tsx. A traced build wrote page.tsx at the project
|
|
188
|
+
* root and api/expenses/route.ts beside it — the build passed, every route
|
|
189
|
+
* 404ed, and nothing said why.
|
|
190
|
+
*
|
|
191
|
+
* The guide has the tree in it, and the guide cannot help: it comes back
|
|
192
|
+
* inside the create_app result, and the model chooses these paths in the call
|
|
193
|
+
* that produces that result. It is reading the map after it has parked. So the
|
|
194
|
+
* paths are corrected on the way in, and the result says what moved and why,
|
|
195
|
+
* which is the copy that arrives in time to matter for the next file.
|
|
196
|
+
*
|
|
197
|
+
* Only for next-shadcn, only for files under the app folder, and never for the
|
|
198
|
+
* config files that genuinely belong at the root.
|
|
199
|
+
*/
|
|
200
|
+
function placeForNext(files, folder) {
|
|
201
|
+
const moved = [];
|
|
202
|
+
|
|
203
|
+
const out = files.map((f) => {
|
|
204
|
+
const rel = String(f.path ?? '').split('\\').join('/');
|
|
205
|
+
const prefix = `${folder}/`;
|
|
206
|
+
if (!rel.startsWith(prefix)) return f;
|
|
207
|
+
|
|
208
|
+
const inside = rel.slice(prefix.length);
|
|
209
|
+
if (!inside || inside.startsWith('src/') || inside.startsWith('public/')) return f;
|
|
210
|
+
if (NEXT_ROOT.test(inside)) return f;
|
|
211
|
+
|
|
212
|
+
// app/... is the right tree written one level too high; everything else
|
|
213
|
+
// that is app code belongs under src/ as it stands.
|
|
214
|
+
const under = inside.startsWith('app/') ? `src/${inside}` : `src/app/${inside}`;
|
|
215
|
+
const isRoute = /(?:^|\/)(?:page|layout|loading|error|not-found)\.[jt]sx?$/.test(inside)
|
|
216
|
+
|| /(?:^|\/)route\.[jt]s$/.test(inside);
|
|
217
|
+
const isCode = /^(?:components|lib|hooks|styles|utils)\//.test(inside);
|
|
218
|
+
|
|
219
|
+
if (!isRoute && !isCode) return f;
|
|
220
|
+
const to = isCode ? `${prefix}src/${inside}` : `${prefix}${under}`;
|
|
221
|
+
if (to === rel) return f;
|
|
222
|
+
|
|
223
|
+
moved.push(`${inside} -> ${to.slice(prefix.length)}`);
|
|
224
|
+
return { ...f, path: to };
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return { files: out, moved };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
230
|
async function keepDesignTokens(appDir, template, written, starterCss) {
|
|
231
231
|
const rel = TOKEN_FILE[template];
|
|
232
232
|
if (!rel || !starterCss) return null;
|
|
@@ -340,6 +340,16 @@ export async function createApp({ folder, name, description, template = 'plain-h
|
|
|
340
340
|
});
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
// The user said no framework. A starter that costs an npm install and a
|
|
344
|
+
// build is then not a judgement call the model gets to make on their behalf
|
|
345
|
+
// — it is the one thing they ruled out, and the cost of getting it wrong is
|
|
346
|
+
// minutes of their time plus a rebuild from nothing.
|
|
347
|
+
let overrode = false;
|
|
348
|
+
if (template === 'next-shadcn' && askedForPlainHtml()) {
|
|
349
|
+
template = 'plain-html';
|
|
350
|
+
overrode = true;
|
|
351
|
+
}
|
|
352
|
+
|
|
343
353
|
const target = resolveIn(folder, 'create_app', 'folder');
|
|
344
354
|
const attempted = `creating an app in ${target.show}`;
|
|
345
355
|
if (target.show === '.') {
|
|
@@ -420,8 +430,8 @@ export async function createApp({ folder, name, description, template = 'plain-h
|
|
|
420
430
|
|
|
421
431
|
// The app's own files, written in this same call. Two round trips become
|
|
422
432
|
// one, and round trips are nearly all of the time a build takes.
|
|
423
|
-
// Next.js only: paths that have missed src/ are corrected before they land.
|
|
424
|
-
const placed = template === 'next-shadcn' ? placeForNext(given, path.basename(target.abs)) : { files: given, moved: [] };
|
|
433
|
+
// Next.js only: paths that have missed src/ are corrected before they land.
|
|
434
|
+
const placed = template === 'next-shadcn' ? placeForNext(given, path.basename(target.abs)) : { files: given, moved: [] };
|
|
425
435
|
const mine = placed.files;
|
|
426
436
|
const wrote = mine.length ? await batchWrite({ files: mine }) : null;
|
|
427
437
|
const written = new Set(mine.map((f) => resolveIn(f.path, 'create_app', 'files').abs));
|
|
@@ -440,16 +450,21 @@ export async function createApp({ folder, name, description, template = 'plain-h
|
|
|
440
450
|
}
|
|
441
451
|
|
|
442
452
|
const out = result(
|
|
443
|
-
|
|
453
|
+
(overrode
|
|
454
|
+
? 'You asked for next-shadcn, but the request said plain HTML and no framework, so ' +
|
|
455
|
+
'this is the plain-html starter instead: three files, no install, no build. Write ' +
|
|
456
|
+
'the app in them.\n'
|
|
457
|
+
: '') +
|
|
458
|
+
`Created ${target.show} from the ${template} starter — ${copied.length} files, already known to build.\n` +
|
|
444
459
|
(look ? `Design: the ${look.name} preset (${look.summary}), font ${look.fonts?.sans ?? 'Geist'}.\n` : '') +
|
|
445
460
|
(wrote ? `\nYour ${mine.length} file${mine.length === 1 ? '' : 's'}:\n${wrote.content}\n` : '') +
|
|
446
|
-
(placed.moved.length
|
|
447
|
-
? `\nMoved into place: ${placed.moved.join(', ')}. In Next.js a page is only a route at `
|
|
448
|
-
+ 'src/app/<segment>/page.tsx, an API handler only at src/app/api/<name>/route.ts, and '
|
|
449
|
-
+ 'components and libraries live under src/. A file written outside src/ builds fine and '
|
|
450
|
-
+ 'is never served, which is the hardest kind of wrong to notice. Write the rest there '
|
|
451
|
-
+ 'directly.\n'
|
|
452
|
-
: '') +
|
|
461
|
+
(placed.moved.length
|
|
462
|
+
? `\nMoved into place: ${placed.moved.join(', ')}. In Next.js a page is only a route at `
|
|
463
|
+
+ 'src/app/<segment>/page.tsx, an API handler only at src/app/api/<name>/route.ts, and '
|
|
464
|
+
+ 'components and libraries live under src/. A file written outside src/ builds fine and '
|
|
465
|
+
+ 'is never served, which is the hardest kind of wrong to notice. Write the rest there '
|
|
466
|
+
+ 'directly.\n'
|
|
467
|
+
: '') +
|
|
453
468
|
(relinked.length
|
|
454
469
|
? `\nFixed in ${relinked.join(', ')}: links that began with "${path.basename(target.abs)}/". ` +
|
|
455
470
|
'Paths in "files" are relative to the project root, but a link inside a page is ' +
|