ucode-agent 1.2.0 → 1.4.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 +73 -17
- package/package.json +3 -2
- package/skills/build-app/SKILL.md +4 -2
- package/skills/ui-ux/SKILL.md +5 -1
- package/src/core/context.js +151 -0
- package/src/core/loop.js +550 -40
- package/src/core/provider.js +58 -15
- package/src/core/updater.js +95 -0
- package/src/tools/browser.js +258 -0
- package/src/tools/files.js +173 -16
- package/src/tools/index.js +472 -394
- package/src/tools/shell.js +149 -1
- package/src/ui/plain.js +330 -325
- package/src/ui/screen.js +27 -7
- package/src/ui/theme.js +18 -0
package/src/ui/screen.js
CHANGED
|
@@ -39,7 +39,7 @@ import chalk from 'chalk';
|
|
|
39
39
|
import {
|
|
40
40
|
theme, blue, sky, deep, dim, edge, ADDED, REMOVED, BANNER, BANNER_WIDTH, SPINNER,
|
|
41
41
|
boxTop, boxBottom, boxRow, visLen, padVis, clip, wrapAnsi,
|
|
42
|
-
shortenPath, asLabel, ensureColour,
|
|
42
|
+
shortenPath, asLabel, ensureColour, planLine, bare,
|
|
43
43
|
} from './theme.js';
|
|
44
44
|
import { renderer, render, polish } from './markdown.js';
|
|
45
45
|
import { VERSION } from '../core/version.js';
|
|
@@ -57,7 +57,7 @@ export function isLabel(text) {
|
|
|
57
57
|
|
|
58
58
|
export const COMMANDS = [
|
|
59
59
|
'/help', '/model', '/models', '/session', '/sessions', '/resume',
|
|
60
|
-
'/new', '/skills', '/clear', '/search', '/copy', '/exit',
|
|
60
|
+
'/new', '/remember', '/skills', '/clear', '/search', '/copy', '/exit',
|
|
61
61
|
];
|
|
62
62
|
|
|
63
63
|
// ANSI ----------------------------------------------------------------------
|
|
@@ -290,6 +290,12 @@ export class Screen {
|
|
|
290
290
|
this.updateSpinner(label);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
/** The checklist, when the model updates it. One line, wrapped if it must. */
|
|
294
|
+
plan(items) {
|
|
295
|
+
const line = planLine(items);
|
|
296
|
+
if (line) this.push(line);
|
|
297
|
+
}
|
|
298
|
+
|
|
293
299
|
toolResult(summary) {
|
|
294
300
|
this.push(dim(` └ ${summary}`));
|
|
295
301
|
}
|
|
@@ -767,11 +773,15 @@ export class Screen {
|
|
|
767
773
|
* cannot be given up permanently, because under alternate scroll the mouse
|
|
768
774
|
* wheel arrives as arrow keys.
|
|
769
775
|
*/
|
|
770
|
-
pick(items, { active = 0, hint = 'enter to choose · esc to cancel' } = {}) {
|
|
776
|
+
pick(items, { active = 0, hint = 'enter to choose · esc to cancel', deletable = false } = {}) {
|
|
771
777
|
this.picker = {
|
|
772
778
|
items,
|
|
773
779
|
index: Math.min(Math.max(0, active), Math.max(0, items.length - 1)),
|
|
774
780
|
hint,
|
|
781
|
+
// With deletable, `d` twice on a row resolves { delete: index }. Twice,
|
|
782
|
+
// because a single stray keypress should never cost a conversation.
|
|
783
|
+
deletable,
|
|
784
|
+
armed: null,
|
|
775
785
|
};
|
|
776
786
|
this.render();
|
|
777
787
|
return new Promise((resolve) => { this.pickerResolve = resolve; });
|
|
@@ -793,7 +803,7 @@ export class Screen {
|
|
|
793
803
|
* about instead of a column of near-identical titles.
|
|
794
804
|
*/
|
|
795
805
|
pickerLines(height) {
|
|
796
|
-
const { items, index, hint } = this.picker;
|
|
806
|
+
const { items, index, hint, armed } = this.picker;
|
|
797
807
|
const room = Math.max(1, height - 2);
|
|
798
808
|
|
|
799
809
|
// Rows per item, so the window can be sized in rows rather than in items.
|
|
@@ -815,12 +825,15 @@ export class Screen {
|
|
|
815
825
|
for (let i = first; i <= last; i++) {
|
|
816
826
|
const item = items[i];
|
|
817
827
|
const body = typeof item === 'string' ? item : item.label;
|
|
818
|
-
|
|
828
|
+
if (i === armed) out.push(`${theme.warn('✗')} ${theme.warn(bare(body))}`);
|
|
829
|
+
else out.push(i === index ? `${blue('❯')} ${chalk.bold.white(body)}` : ` ${dim(body)}`);
|
|
819
830
|
if (typeof item !== 'string' && item.sub) out.push(` ${item.sub}`);
|
|
820
831
|
}
|
|
821
832
|
|
|
822
833
|
out.push('');
|
|
823
|
-
out.push(
|
|
834
|
+
out.push(armed !== null && armed !== undefined
|
|
835
|
+
? theme.warn(' press d again to delete this conversation · any other key keeps it')
|
|
836
|
+
: dim(` ${hint}`));
|
|
824
837
|
return out;
|
|
825
838
|
}
|
|
826
839
|
|
|
@@ -926,6 +939,13 @@ export class Screen {
|
|
|
926
939
|
// An open picker owns the keyboard until it closes.
|
|
927
940
|
if (this.picker) {
|
|
928
941
|
const last = this.picker.items.length - 1;
|
|
942
|
+
if (this.picker.deletable && (key === 'd' || key === 'D' || key === `${ESC}[3~`)) {
|
|
943
|
+
if (this.picker.armed === this.picker.index) { this.closePicker({ delete: this.picker.index }); return; }
|
|
944
|
+
this.picker.armed = this.picker.index;
|
|
945
|
+
this.render();
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
this.picker.armed = null; // any other key takes the delete back
|
|
929
949
|
if (key === `${ESC}[A`) { this.picker.index = Math.max(0, this.picker.index - 1); this.render(); return; }
|
|
930
950
|
if (key === `${ESC}[B`) { this.picker.index = Math.min(last, this.picker.index + 1); this.render(); return; }
|
|
931
951
|
if (key === '\r' || key === '\n') { this.closePicker(this.picker.index); return; }
|
|
@@ -1143,7 +1163,7 @@ export class Screen {
|
|
|
1143
1163
|
|
|
1144
1164
|
// The version, in the corner, and nothing else on the screen.
|
|
1145
1165
|
if (VERSION) {
|
|
1146
|
-
const tag = dim(`v${VERSION}`);
|
|
1166
|
+
const tag = dim(this.facts.update ? `v${VERSION} · v${this.facts.update} installed, starts next time` : `v${VERSION}`);
|
|
1147
1167
|
frame[this.rows - 1] = ' '.repeat(Math.max(0, g.cols - visLen(tag) - 2)) + tag;
|
|
1148
1168
|
}
|
|
1149
1169
|
|
package/src/ui/theme.js
CHANGED
|
@@ -254,3 +254,21 @@ export function asLabel(text) {
|
|
|
254
254
|
.replace(/\s+/g, ' ')
|
|
255
255
|
.replace(/(?<=[\w)\]"'`])[.。]+$/, '');
|
|
256
256
|
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The model's checklist, as one short line — done ticked, the current item
|
|
260
|
+
* marked, the rest dim — so progress is visible without taking over the screen.
|
|
261
|
+
*/
|
|
262
|
+
export function planLine(items) {
|
|
263
|
+
const list = (Array.isArray(items) ? items : []).slice(0, 6);
|
|
264
|
+
if (!list.length) return '';
|
|
265
|
+
const done = list.filter((i) => i?.done).length;
|
|
266
|
+
const current = list.findIndex((i) => !i?.done);
|
|
267
|
+
const parts = list.map((item, i) => {
|
|
268
|
+
const text = clip(String(item?.text ?? '').trim(), 30);
|
|
269
|
+
if (item?.done) return `${theme.ok('✓')} ${dim(text)}`;
|
|
270
|
+
if (i === current) return `${blue('▸')} ${chalk.white(text)}`;
|
|
271
|
+
return dim(`○ ${text}`);
|
|
272
|
+
});
|
|
273
|
+
return ` ${sky(`plan ${done}/${list.length}`)} ${parts.join(dim(' · '))}`;
|
|
274
|
+
}
|