termux-dev 1.9.8 → 1.9.9

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 CHANGED
@@ -22,6 +22,10 @@ Built specifically for **Android Termux**, **Windows**, **macOS**, and **Linux**
22
22
 
23
23
  <br/><br/>
24
24
 
25
+ <a href="assets/demo.mp4"><img src="assets/demo.gif" alt="devx demo: plan, build, test, preview with a QR code" width="880" /></a>
26
+
27
+ <br/>
28
+
25
29
  <img src="assets/preview.svg" alt="The devx terminal interface" width="880" />
26
30
 
27
31
  </div>
package/assets/banner.svg CHANGED
@@ -16,7 +16,7 @@
16
16
  <rect width="1000" height="280" fill="url(#bg)"/>
17
17
  </g>
18
18
  <rect x="1.5" y="1.5" width="997" height="277" rx="18" fill="none" stroke="#23252c" stroke-width="1.5"/>
19
- <text x="500.0" y="70" text-anchor="middle" font-family="'Inter',system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" letter-spacing="5" fill="#7d8896">THE TERMINAL-NATIVE AI CODING AGENT<tspan dx="16" fill="#2fe3ee" font-weight="700">v1.9.8</tspan></text>
19
+ <text x="500.0" y="70" text-anchor="middle" font-family="'Inter',system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" letter-spacing="5" fill="#7d8896">THE TERMINAL-NATIVE AI CODING AGENT<tspan dx="16" fill="#2fe3ee" font-weight="700">v1.9.9</tspan></text>
20
20
  <g>
21
21
  <text x="500.0" y="178" text-anchor="middle" font-family="'JetBrains Mono','Fira Code','SF Mono',ui-monospace,Menlo,Consolas,'DejaVu Sans Mono',monospace" font-size="112" font-weight="800" letter-spacing="14" fill="#2fe3ee">DEVX</text>
22
22
  </g>
@@ -32,7 +32,7 @@
32
32
  <rect x="595.2" y="574.0" width="243.2" height="19" fill="#000000"/>
33
33
  <text x="44.8" y="62.0" class="t" fill="#b794f6">▌</text>
34
34
  <text x="61.6" y="62.0" class="t b" fill="#f4f7fb">devx</text>
35
- <text x="112.0" y="62.0" class="t" fill="#5f6b7a">v1.9.8</text>
35
+ <text x="112.0" y="62.0" class="t" fill="#5f6b7a">v1.9.9</text>
36
36
  <text x="61.6" y="106.0" class="t" fill="#5f6b7a">model</text>
37
37
  <text x="128.8" y="106.0" class="t" fill="#e5eaf1">deepseek-v4.1-flash · think high</text>
38
38
  <text x="406.0" y="106.0" class="t" fill="#5f6b7a">/model</text>
@@ -3,7 +3,7 @@ import os from 'os';
3
3
  import path from 'path';
4
4
  import { extendedKeysOff, extendedKeysOn, isNewlineKey, normalizeKey, splitKeys } from './keys.js';
5
5
  import { buildPromptFrame, palette } from './ui.js';
6
- import { displayWidth, truncateWidth } from './width.js';
6
+ import { columnAfter, displayWidth, truncateWidth } from './width.js';
7
7
  import { t } from '../core/i18n.js';
8
8
  /** The down arrow in every form a terminal may send it: `ESC [ B`, `ESC O B` (application mode), `ESC [ 1 ; mods B` (with modifier fields). */
9
9
  const DOWN_ARROW = /^\x1b(?:\[|O)(?:1;[\d:]+)?B$/;
@@ -153,6 +153,8 @@ export class TurnComposer {
153
153
  state = { text: '', cursor: 0 };
154
154
  queue = [];
155
155
  lines = [];
156
+ /** The column the output cursor is in: the frame is drawn under it and the cursor comes back to this column. */
157
+ col = 0;
156
158
  original = null;
157
159
  timer = null;
158
160
  /** Called on ctrl+p (thinking) and ctrl+o (tool output). */
@@ -207,6 +209,7 @@ export class TurnComposer {
207
209
  this.active = true;
208
210
  this.suspended = false;
209
211
  this.shown = false;
212
+ this.col = 0;
210
213
  this.original = this.out.write.bind(this.out);
211
214
  this.out.write = ((chunk, encoding, cb) => this.through(chunk, encoding, cb));
212
215
  this.write(extendedKeysOn() + this.drawSeq());
@@ -298,17 +301,27 @@ export class TurnComposer {
298
301
  /** The write that replaces the terminal's own for the length of the turn. */
299
302
  through(chunk, encoding, cb) {
300
303
  const raw = this.original;
301
- if (!this.active || this.suspended)
304
+ const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
305
+ if (!this.active || this.suspended) {
306
+ this.advanceColumn(text);
302
307
  return raw(chunk, encoding, cb);
308
+ }
309
+ // The old frame is wiped from the column the cursor is in now; the new one comes back to the column the text ends in.
310
+ const erase = this.eraseSeq();
311
+ this.advanceColumn(text);
303
312
  if (typeof chunk === 'string') {
304
313
  // The callback form `write(chunk, cb)` has the callback in the second place.
305
- return raw(this.eraseSeq() + chunk + this.drawSeq(), encoding, cb);
314
+ return raw(erase + chunk + this.drawSeq(), encoding, cb);
306
315
  }
307
- raw(this.eraseSeq());
316
+ raw(erase);
308
317
  const result = raw(chunk, encoding, cb);
309
318
  raw(this.drawSeq());
310
319
  return result;
311
320
  }
321
+ /** Where the cursor is after this text (also while a card has the screen, so that the frame comes back to the right column). */
322
+ advanceColumn(text) {
323
+ this.col = columnAfter(this.col, text, this.out.columns ?? 80);
324
+ }
312
325
  write(text) {
313
326
  if (text)
314
327
  (this.original ?? this.out.write.bind(this.out))(text);
@@ -339,14 +352,25 @@ export class TurnComposer {
339
352
  oneRow(line) {
340
353
  return line.replace(/[\r\n\x00-\x08\x0b-\x1a\x1c-\x1f]+/g, ' ');
341
354
  }
342
- /** Wipes the frame: everything under the cursor row. The cursor stays on its text. */
355
+ /** Puts the cursor back in the column of the output, on the row it is on. */
356
+ backToColumn() {
357
+ const cols = Math.max(1, this.out.columns ?? 80);
358
+ return `\x1b[${Math.min(this.col, cols - 1) + 1}G`;
359
+ }
360
+ /**
361
+ * Wipes the frame: everything under the cursor row. The cursor stays on its text.
362
+ *
363
+ * Only relative moves are used, no saved cursor (ESC 7 / ESC 8): the saved position is a screen row,
364
+ * and when the window changes size in the middle of a write (the keyboard of a phone comes and goes)
365
+ * some terminals do not move it with the text, and the cursor comes back to the wrong row.
366
+ */
343
367
  eraseSeq() {
344
368
  if (!this.shown)
345
369
  return '';
346
370
  this.shown = false;
347
371
  // The erase starts from plain attributes: a style left on by the text above (an underline, a
348
372
  // background) would be painted into every row it wipes (stripes across the screen).
349
- return '\x1b7\x1b[0m\x1b[1B\r\x1b[J\x1b8';
373
+ return '\x1b[0m\x1b[1B\r\x1b[J\x1b[1A' + this.backToColumn();
350
374
  }
351
375
  /** Draws the frame under the cursor row and puts the cursor back where the text goes on. */
352
376
  drawSeq() {
@@ -358,9 +382,9 @@ export class TurnComposer {
358
382
  // The real cursor only marks where the output goes on. Whatever showed it in the meantime
359
383
  // (an approval card, a spinner that stopped) must not leave it blinking under the output.
360
384
  // ESC D moves down (scrolling at the bottom edge) and keeps the column; then back up.
361
- let seq = '\x1b[?25l' + '\x1bD'.repeat(n) + `\x1b[${n}A` + '\x1b7';
385
+ let seq = '\x1b[?25l' + '\x1bD'.repeat(n) + `\x1b[${n}A`;
362
386
  for (const line of lines)
363
387
  seq += `\x1b[0m\x1b[1B\r\x1b[2K${line}\x1b[0m`;
364
- return seq + '\x1b8';
388
+ return seq + `\x1b[${n}A` + this.backToColumn();
365
389
  }
366
390
  }
package/dist/cli/width.js CHANGED
@@ -289,3 +289,35 @@ export function rowsToTop(lineWidths, cursorRow, cursorCol, cols) {
289
289
  rows += Math.max(1, Math.ceil(lineWidths[i] / width));
290
290
  return rows + Math.floor(Math.max(0, cursorCol) / width);
291
291
  }
292
+ /**
293
+ * The column (0-based, in cells) the cursor is in after `text` is written starting at column `col`
294
+ * of a terminal `cols` wide. Escape sequences take no room; a line break and a carriage return go
295
+ * to column 0; a character that does not fit wraps to the next row. A cursor that has just filled
296
+ * the last column is reported as `cols` (the terminal holds it there until the next character).
297
+ */
298
+ export function columnAfter(col, text, cols) {
299
+ const width = Math.max(1, cols);
300
+ let at = Math.max(0, Math.min(col, width));
301
+ const plain = text.replace(ANSI, '').replace(/\x1b[0-9A-Za-z=>]/g, '');
302
+ for (const ch of plain) {
303
+ if (ch === '\n' || ch === '\r') {
304
+ at = 0;
305
+ continue;
306
+ }
307
+ if (ch === '\b') {
308
+ at = Math.max(0, at - 1);
309
+ continue;
310
+ }
311
+ if (ch === '\t') {
312
+ at = Math.min(width - 1, (Math.floor(at / 8) + 1) * 8);
313
+ continue;
314
+ }
315
+ const w = codePointWidth(ch.codePointAt(0));
316
+ if (w === 0)
317
+ continue;
318
+ if (at + w > width)
319
+ at = 0;
320
+ at += w;
321
+ }
322
+ return at;
323
+ }
@@ -1 +1 @@
1
- export const DEVX_VERSION = '1.9.8';
1
+ export const DEVX_VERSION = '1.9.9';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termux-dev",
3
- "version": "1.9.8",
3
+ "version": "1.9.9",
4
4
  "description": "Ultra-fast, terminal-native AI coding assistant and agent built for Android Termux, Windows, macOS, and Linux.",
5
5
  "main": "dist/cli/index.js",
6
6
  "type": "module",