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/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
- out.push(i === index ? `${blue('')} ${chalk.bold.white(body)}` : ` ${dim(body)}`);
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(dim(` ${hint}`));
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
+ }