ucode-agent 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -4
- package/package.json +5 -2
- package/skills/build-app/SKILL.md +19 -1
- package/skills/ui-ux/SKILL.md +2 -2
- package/src/core/doctor.js +122 -0
- package/src/core/loop.js +303 -12
- package/src/core/provider.js +93 -10
- package/src/core/stuck.js +269 -0
- package/src/tools/browser.js +121 -59
- package/src/tools/deploy.js +283 -0
- package/src/tools/files.js +91 -8
- package/src/tools/index.js +65 -8
- package/src/tools/scaffold.js +190 -0
- package/src/tools/shell.js +89 -1
- package/src/ui/activity.js +203 -0
- package/src/ui/plain.js +22 -3
- package/src/ui/screen.js +65 -19
- package/templates/next-shadcn/AGENTS.md +9 -0
- package/templates/next-shadcn/README.md +9 -0
- package/templates/next-shadcn/TEMPLATE.md +83 -0
- package/templates/next-shadcn/_gitignore +41 -0
- package/templates/next-shadcn/_package-lock.json +11465 -0
- package/templates/next-shadcn/components.json +25 -0
- package/templates/next-shadcn/eslint.config.mjs +18 -0
- package/templates/next-shadcn/next-env.d.ts +5 -0
- package/templates/next-shadcn/next.config.ts +8 -0
- package/templates/next-shadcn/package.json +39 -0
- package/templates/next-shadcn/postcss.config.mjs +7 -0
- package/templates/next-shadcn/presets/citrus.json +77 -0
- package/templates/next-shadcn/presets/graphite.json +77 -0
- package/templates/next-shadcn/presets/grove.json +77 -0
- package/templates/next-shadcn/presets/ocean.json +78 -0
- package/templates/next-shadcn/presets/sunset.json +77 -0
- package/templates/next-shadcn/presets/violet.json +77 -0
- package/templates/next-shadcn/src/app/favicon.ico +0 -0
- package/templates/next-shadcn/src/app/globals.css +155 -0
- package/templates/next-shadcn/src/app/layout.tsx +42 -0
- package/templates/next-shadcn/src/app/page.tsx +14 -0
- package/templates/next-shadcn/src/components/theme-provider.tsx +7 -0
- package/templates/next-shadcn/src/components/theme-toggle.tsx +27 -0
- package/templates/next-shadcn/src/components/ui/accordion.tsx +80 -0
- package/templates/next-shadcn/src/components/ui/alert-dialog.tsx +199 -0
- package/templates/next-shadcn/src/components/ui/avatar.tsx +111 -0
- package/templates/next-shadcn/src/components/ui/badge.tsx +48 -0
- package/templates/next-shadcn/src/components/ui/button.tsx +66 -0
- package/templates/next-shadcn/src/components/ui/calendar.tsx +222 -0
- package/templates/next-shadcn/src/components/ui/card.tsx +102 -0
- package/templates/next-shadcn/src/components/ui/checkbox.tsx +32 -0
- package/templates/next-shadcn/src/components/ui/collapsible.tsx +33 -0
- package/templates/next-shadcn/src/components/ui/command.tsx +195 -0
- package/templates/next-shadcn/src/components/ui/dialog.tsx +168 -0
- package/templates/next-shadcn/src/components/ui/dropdown-menu.tsx +268 -0
- package/templates/next-shadcn/src/components/ui/hover-card.tsx +43 -0
- package/templates/next-shadcn/src/components/ui/input-group.tsx +156 -0
- package/templates/next-shadcn/src/components/ui/input.tsx +18 -0
- package/templates/next-shadcn/src/components/ui/label.tsx +23 -0
- package/templates/next-shadcn/src/components/ui/popover.tsx +88 -0
- package/templates/next-shadcn/src/components/ui/progress.tsx +30 -0
- package/templates/next-shadcn/src/components/ui/radio-group.tsx +43 -0
- package/templates/next-shadcn/src/components/ui/scroll-area.tsx +54 -0
- package/templates/next-shadcn/src/components/ui/select.tsx +191 -0
- package/templates/next-shadcn/src/components/ui/separator.tsx +27 -0
- package/templates/next-shadcn/src/components/ui/sheet.tsx +147 -0
- package/templates/next-shadcn/src/components/ui/skeleton.tsx +13 -0
- package/templates/next-shadcn/src/components/ui/slider.tsx +58 -0
- package/templates/next-shadcn/src/components/ui/sonner.tsx +49 -0
- package/templates/next-shadcn/src/components/ui/switch.tsx +32 -0
- package/templates/next-shadcn/src/components/ui/table.tsx +115 -0
- package/templates/next-shadcn/src/components/ui/tabs.tsx +89 -0
- package/templates/next-shadcn/src/components/ui/textarea.tsx +17 -0
- package/templates/next-shadcn/src/components/ui/toggle-group.tsx +89 -0
- package/templates/next-shadcn/src/components/ui/toggle.tsx +46 -0
- package/templates/next-shadcn/src/components/ui/tooltip.tsx +56 -0
- package/templates/next-shadcn/src/lib/utils.ts +6 -0
- package/templates/next-shadcn/tsconfig.json +34 -0
- package/ucode.js +8 -1
package/src/ui/screen.js
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
boxTop, boxBottom, boxRow, visLen, padVis, clip, wrapAnsi,
|
|
42
42
|
shortenPath, asLabel, ensureColour, planLine, bare,
|
|
43
43
|
} from './theme.js';
|
|
44
|
+
import { FRAME_MS, fitActivity, shimmer, spinnerGlyph, formatDuration, doneLine, stepPaint } from './activity.js';
|
|
44
45
|
import { renderer, render, polish } from './markdown.js';
|
|
45
46
|
import { VERSION } from '../core/version.js';
|
|
46
47
|
|
|
@@ -58,6 +59,7 @@ export function isLabel(text) {
|
|
|
58
59
|
export const COMMANDS = [
|
|
59
60
|
'/help', '/model', '/models', '/session', '/sessions', '/resume',
|
|
60
61
|
'/new', '/remember', '/skills', '/clear', '/search', '/copy', '/exit',
|
|
62
|
+
'/stats', '/doctor', '/deploy',
|
|
61
63
|
];
|
|
62
64
|
|
|
63
65
|
// ANSI ----------------------------------------------------------------------
|
|
@@ -135,6 +137,8 @@ export class Screen {
|
|
|
135
137
|
this.onInterrupt = null;
|
|
136
138
|
this.onModeChange = null;
|
|
137
139
|
this.spinTimer = null;
|
|
140
|
+
this.activity = null; // the turn in flight: when it began, how many steps
|
|
141
|
+
this.tick = 0; // animation frames painted, for the spinner
|
|
138
142
|
this.pendingPrompt = null;
|
|
139
143
|
|
|
140
144
|
this.cols = output.columns || 80;
|
|
@@ -164,7 +168,9 @@ export class Screen {
|
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
stop() {
|
|
171
|
+
this.activity = null;
|
|
167
172
|
this.stopSpinner();
|
|
173
|
+
this.stopTimer();
|
|
168
174
|
this.output.off?.('resize', this.onResize);
|
|
169
175
|
this.input.setRawMode?.(false);
|
|
170
176
|
this.input.pause();
|
|
@@ -633,14 +639,23 @@ export class Screen {
|
|
|
633
639
|
let middle = '';
|
|
634
640
|
if (this.flashText) {
|
|
635
641
|
middle = dim(clip(this.flashText, between - 2));
|
|
636
|
-
} else if (this.status.busy) {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
642
|
+
} else if (this.status.busy || this.activity) {
|
|
643
|
+
// The whole turn, not just the current tool: the timer and step count
|
|
644
|
+
// keep going through the gaps between calls, so a long build never
|
|
645
|
+
// looks like it has stopped.
|
|
646
|
+
const now = Date.now();
|
|
647
|
+
const a = this.activity;
|
|
648
|
+
const since = a?.start ?? this.status.since ?? now;
|
|
649
|
+
const meta = [];
|
|
650
|
+
if (a?.steps) meta.push({ text: `step ${a.steps}`, paint: stepPaint(now - a.movedAt < 900) });
|
|
651
|
+
if (now - since >= 1000) meta.push({ text: formatDuration(now - since), keep: true });
|
|
652
|
+
middle = fitActivity({
|
|
653
|
+
glyph: spinnerGlyph(this.tick, now),
|
|
654
|
+
label: this.status.busy ? this.status.text : 'working',
|
|
655
|
+
meta,
|
|
656
|
+
hint: 'esc to stop',
|
|
657
|
+
paint: (s) => shimmer(s, now),
|
|
658
|
+
}, between - 3);
|
|
644
659
|
}
|
|
645
660
|
|
|
646
661
|
// The percentage is pinned to the right border whatever is in the middle,
|
|
@@ -700,13 +715,7 @@ export class Screen {
|
|
|
700
715
|
// `since` is what makes a long think legible: the label may not change for
|
|
701
716
|
// a minute, so the seconds beside it are the proof it is still alive.
|
|
702
717
|
this.status = { busy: true, text: asLabel(text), frame: 0, since: Date.now() };
|
|
703
|
-
|
|
704
|
-
this.spinTimer = setInterval(() => {
|
|
705
|
-
this.status.frame = (this.status.frame + 1) % SPINNER.length;
|
|
706
|
-
this.paintStatus();
|
|
707
|
-
}, 80);
|
|
708
|
-
this.spinTimer.unref?.();
|
|
709
|
-
}
|
|
718
|
+
this.startTimer();
|
|
710
719
|
this.paintStatus();
|
|
711
720
|
}
|
|
712
721
|
|
|
@@ -717,16 +726,53 @@ export class Screen {
|
|
|
717
726
|
}
|
|
718
727
|
|
|
719
728
|
stopSpinner() {
|
|
720
|
-
if (this.
|
|
721
|
-
clearInterval(this.spinTimer);
|
|
722
|
-
this.spinTimer = null;
|
|
723
|
-
}
|
|
729
|
+
if (!this.activity) this.stopTimer();
|
|
724
730
|
if (this.status.busy) {
|
|
725
731
|
this.status = { busy: false, text: '', frame: 0, since: 0 };
|
|
726
732
|
this.paintStatus();
|
|
727
733
|
}
|
|
728
734
|
}
|
|
729
735
|
|
|
736
|
+
// -- the turn in flight ----------------------------------------------------
|
|
737
|
+
|
|
738
|
+
/** A turn begins: the timer and step count run until turnEnd(). */
|
|
739
|
+
turnStart() {
|
|
740
|
+
this.activity = { start: Date.now(), steps: 0, movedAt: 0 };
|
|
741
|
+
this.startTimer();
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/** One more model step in this turn. */
|
|
745
|
+
step() {
|
|
746
|
+
if (!this.activity) return;
|
|
747
|
+
this.activity.steps++;
|
|
748
|
+
this.activity.movedAt = Date.now();
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** The turn is over: leave "✓ Done in 6m 12s · 25 steps" under the answer. */
|
|
752
|
+
turnEnd({ ok = true } = {}) {
|
|
753
|
+
const a = this.activity;
|
|
754
|
+
this.activity = null;
|
|
755
|
+
if (!this.status.busy) this.stopTimer();
|
|
756
|
+
if (a && ok && Date.now() - a.start >= 2000) this.push(` ${doneLine(Date.now() - a.start, a.steps)}`);
|
|
757
|
+
this.paintStatus();
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/** The animation clock: only the status row repaints, about twelve times a second. */
|
|
761
|
+
startTimer() {
|
|
762
|
+
if (this.spinTimer) return;
|
|
763
|
+
this.spinTimer = setInterval(() => {
|
|
764
|
+
this.tick++;
|
|
765
|
+
this.paintStatus();
|
|
766
|
+
}, FRAME_MS);
|
|
767
|
+
this.spinTimer.unref?.();
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
stopTimer() {
|
|
771
|
+
if (!this.spinTimer) return;
|
|
772
|
+
clearInterval(this.spinTimer);
|
|
773
|
+
this.spinTimer = null;
|
|
774
|
+
}
|
|
775
|
+
|
|
730
776
|
// -- input ---------------------------------------------------------------
|
|
731
777
|
|
|
732
778
|
nextLine() {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- BEGIN:nextjs-agent-rules -->
|
|
2
|
+
|
|
3
|
+
# This is NOT the Next.js you know
|
|
4
|
+
|
|
5
|
+
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
|
6
|
+
|
|
7
|
+
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
|
8
|
+
|
|
9
|
+
<!-- END:nextjs-agent-rules -->
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# What this project already has
|
|
2
|
+
|
|
3
|
+
It was created by ucode's `create_app` from a starter that is known to build.
|
|
4
|
+
Do not re-run create-next-app or `shadcn init` — everything below is in place.
|
|
5
|
+
|
|
6
|
+
## Stack
|
|
7
|
+
|
|
8
|
+
- Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS 4
|
|
9
|
+
- shadcn/ui on Radix (the standard shadcn you know — `asChild` works), lucide-react icons
|
|
10
|
+
- next-themes (light / dark / system), sonner toasts, date-fns, react-day-picker
|
|
11
|
+
|
|
12
|
+
Next 16 changed some APIs. `AGENTS.md` explains, and `node_modules/next/dist/docs/`
|
|
13
|
+
has the guides — check them before using an API you are unsure of.
|
|
14
|
+
|
|
15
|
+
## Already wired
|
|
16
|
+
|
|
17
|
+
- `src/app/layout.tsx` — font (Geist, registered as `--font-sans`), `ThemeProvider`,
|
|
18
|
+
`TooltipProvider`, and `<Toaster />`.
|
|
19
|
+
- `src/components/theme-toggle.tsx` — a light/dark button, ready to place.
|
|
20
|
+
- `src/app/globals.css` — the design tokens. The palette is a starting point:
|
|
21
|
+
re-tint `--primary`, `--accent` and the neutrals for this app's direction.
|
|
22
|
+
`--success` and `--warning` exist alongside `--destructive`
|
|
23
|
+
(`bg-success`, `text-warning`, ...).
|
|
24
|
+
- `src/app/page.tsx` — a placeholder. Replace it.
|
|
25
|
+
|
|
26
|
+
`layout.tsx`, `theme-provider.tsx`, `theme-toggle.tsx`, `src/lib/utils.ts` and
|
|
27
|
+
everything in `src/components/ui/` are finished and build. Use them; do not
|
|
28
|
+
rewrite them — a rewrite from memory brings back APIs that no longer exist.
|
|
29
|
+
|
|
30
|
+
## Components in `src/components/ui/`
|
|
31
|
+
|
|
32
|
+
accordion, alert-dialog, avatar, badge, button, calendar, card, checkbox, collapsible, command, dialog, dropdown-menu, hover-card, input-group, input, label, popover, progress, radio-group, scroll-area, select, separator, sheet, skeleton, slider, sonner, switch, table, tabs, textarea, toggle-group, toggle, tooltip
|
|
33
|
+
|
|
34
|
+
Anything else: `npx shadcn@latest add <name> -y` with cwd set to this folder.
|
|
35
|
+
|
|
36
|
+
## APIs that are easy to get wrong
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
// Toasts — sonner. A message first, options second. There is no toast({ title }).
|
|
40
|
+
import { toast } from "sonner";
|
|
41
|
+
toast.success("Saved");
|
|
42
|
+
toast("Task deleted", { description: "Buy milk", action: { label: "Undo", onClick: () => restore() } });
|
|
43
|
+
|
|
44
|
+
// Slider — value is an ARRAY, even for one thumb.
|
|
45
|
+
<Slider value={[tip]} onValueChange={([v]) => setTip(v)} min={0} max={30} step={1} />
|
|
46
|
+
|
|
47
|
+
// Select — value is a string.
|
|
48
|
+
<Select value={list} onValueChange={(v: string) => setList(v)}>
|
|
49
|
+
<SelectTrigger><SelectValue placeholder="Pick a list" /></SelectTrigger>
|
|
50
|
+
<SelectContent><SelectItem value="inbox">Inbox</SelectItem></SelectContent>
|
|
51
|
+
</Select>
|
|
52
|
+
|
|
53
|
+
// Date picker — Calendar inside a Popover.
|
|
54
|
+
<Popover>
|
|
55
|
+
<PopoverTrigger asChild><Button variant="outline">{date ? format(date, "PPP") : "Pick a date"}</Button></PopoverTrigger>
|
|
56
|
+
<PopoverContent className="w-auto p-0"><Calendar mode="single" selected={date} onSelect={setDate} /></PopoverContent>
|
|
57
|
+
</Popover>
|
|
58
|
+
|
|
59
|
+
// Toggle group — value is a string for type="single".
|
|
60
|
+
<ToggleGroup type="single" value={preset} onValueChange={(v) => v && setPreset(v)}>
|
|
61
|
+
<ToggleGroupItem value="15">15%</ToggleGroupItem>
|
|
62
|
+
</ToggleGroup>
|
|
63
|
+
|
|
64
|
+
// Anything using useState, events, localStorage or browser APIs needs "use client"
|
|
65
|
+
// as the first line of its file.
|
|
66
|
+
|
|
67
|
+
// localStorage — read it in useEffect, never in render or a useState initializer:
|
|
68
|
+
// the page is also rendered on the server, where localStorage does not exist.
|
|
69
|
+
const [bill, setBill] = useState("");
|
|
70
|
+
useEffect(() => { setBill(localStorage.getItem("bill") ?? ""); }, []);
|
|
71
|
+
useEffect(() => { localStorage.setItem("bill", bill); }, [bill]);
|
|
72
|
+
|
|
73
|
+
// next-themes — there is no "next-themes/dist/types". Import from "next-themes".
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Conventions
|
|
77
|
+
|
|
78
|
+
- Components are named exports — `export function BillInput()` — imported with
|
|
79
|
+
braces: `import { BillInput } from "@/components/calculator/bill-input"`.
|
|
80
|
+
Only `page.tsx` and `layout.tsx` use `export default`.
|
|
81
|
+
- One component per file, grouped by feature: `src/components/<feature>/`.
|
|
82
|
+
- Shared types in `src/lib/types.ts`, helpers in `src/lib/`.
|
|
83
|
+
- `cn()` from `@/lib/utils` to merge class names.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
/node_modules
|
|
5
|
+
/.pnp
|
|
6
|
+
.pnp.*
|
|
7
|
+
.yarn/*
|
|
8
|
+
!.yarn/patches
|
|
9
|
+
!.yarn/plugins
|
|
10
|
+
!.yarn/releases
|
|
11
|
+
!.yarn/versions
|
|
12
|
+
|
|
13
|
+
# testing
|
|
14
|
+
/coverage
|
|
15
|
+
|
|
16
|
+
# next.js
|
|
17
|
+
/.next/
|
|
18
|
+
/out/
|
|
19
|
+
|
|
20
|
+
# production
|
|
21
|
+
/build
|
|
22
|
+
|
|
23
|
+
# misc
|
|
24
|
+
.DS_Store
|
|
25
|
+
*.pem
|
|
26
|
+
|
|
27
|
+
# debug
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
yarn-debug.log*
|
|
30
|
+
yarn-error.log*
|
|
31
|
+
.pnpm-debug.log*
|
|
32
|
+
|
|
33
|
+
# env files (can opt-in for committing if needed)
|
|
34
|
+
.env*
|
|
35
|
+
|
|
36
|
+
# vercel
|
|
37
|
+
.vercel
|
|
38
|
+
|
|
39
|
+
# typescript
|
|
40
|
+
*.tsbuildinfo
|
|
41
|
+
next-env.d.ts
|