termux-dev 1.4.3 → 1.4.5

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/assets/banner.svg CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  <!-- Tagline & Badge -->
27
27
  <text x="425" y="142" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="600" fill="#8b949e" text-anchor="middle" letter-spacing="3">
28
- THE TERMINAL-NATIVE AI CODING AGENT <tspan fill="#00f2fe" font-weight="bold">v1.4.3</tspan>
28
+ THE TERMINAL-NATIVE AI CODING AGENT <tspan fill="#00f2fe" font-weight="bold">v1.4.5</tspan>
29
29
  </text>
30
30
 
31
31
  <!-- Top Accent Line -->
Binary file
@@ -18,7 +18,7 @@ async function pingUrl(url) {
18
18
  const req = https.get(url, {
19
19
  timeout: 3000,
20
20
  headers: {
21
- 'User-Agent': 'devx-doctor/1.4.3'
21
+ 'User-Agent': 'devx-doctor/1.4.5'
22
22
  }
23
23
  }, (res) => {
24
24
  res.resume(); // consume response data to free up memory and release socket
@@ -35,7 +35,7 @@ export async function runHeadlessMode(userPrompt, config, options = {}) {
35
35
  });
36
36
  try {
37
37
  if (!isQuiet && !isJson) {
38
- console.log(pc.bold(pc.cyan(`⚡ devx v1.4.3 (headless) | ${planMode ? 'PLAN' : 'AGENT'} | ${config.model}`)));
38
+ console.log(pc.bold(pc.cyan(`⚡ devx v1.4.5 (headless) | ${planMode ? 'PLAN' : 'AGENT'} | ${config.model}`)));
39
39
  console.log(pc.dim(`Task: ${userPrompt}\n`));
40
40
  }
41
41
  for await (const event of agent.run(abortController.signal)) {
package/dist/cli/index.js CHANGED
@@ -484,7 +484,7 @@ function drawLogo() {
484
484
  theme.colorFn(' █▀▀▄ █▀▀▀ █ █ █ █'),
485
485
  theme.colorFn(' █ █ █▀▀▀ ▀▄▀ ▀▄▀ '),
486
486
  theme.colorFn(' █▄▄▀ █▄▄▄ ▀ ▀ ▀ '),
487
- ' ' + theme.boldFn('v1.4.3'),
487
+ ' ' + theme.boldFn('v1.4.5'),
488
488
  ''
489
489
  ];
490
490
  for (const line of logo) {
@@ -499,7 +499,7 @@ function drawLogo() {
499
499
  indent + theme.colorFn('▀▀▀█▀▀▀ █▀▀▀ █▀▀█ █▄ ▄█ █ █ ▀▄ ▄▀ █▀▀▄ █▀▀▀ █ █'),
500
500
  indent + theme.colorFn(' █ █▀▀▀ █▄▄▀ █ █ █ █ █ █ ▀▀ █ █ █▀▀▀ █ █'),
501
501
  indent + theme.colorFn(' █ █▄▄▄ █ ▀▄ █ █ ▀▄▄▀ ▄▀ ▀▄ █▄▄▀ █▄▄▄ ▀▄▀ '),
502
- indent + theme.boldFn('v1.4.3'),
502
+ indent + theme.boldFn('v1.4.5'),
503
503
  ''
504
504
  ];
505
505
  for (const line of logo) {
@@ -645,7 +645,7 @@ async function handleSettings(config) {
645
645
  const maxIterLabel = maxIter >= 9999 ? 'Unlimited' : `${maxIter} steps`;
646
646
  const currentTh = getCurrentTheme();
647
647
  const choice = await select({
648
- message: `${pc.bold('⚙️ Settings')} ${pc.dim(`(devx v1.4.3 • theme: ${currentTh.name})`)}`,
648
+ message: `${pc.bold('⚙️ Settings')} ${pc.dim(`(devx v1.4.5 • theme: ${currentTh.name})`)}`,
649
649
  choices: [
650
650
  {
651
651
  name: `🎨 Color Theme: ${currentTh.emoji} ${currentTh.name}`,
@@ -686,7 +686,7 @@ async function handleSettings(config) {
686
686
  description: 'Limit how many tool steps (file edits, terminal commands) agent can do per request'
687
687
  },
688
688
  {
689
- name: `${currentTh.colorFn('✨ About devx')} ${pc.dim('(v1.4.3 by ApvCode)')}`,
689
+ name: `${currentTh.colorFn('✨ About devx')} ${pc.dim('(v1.4.5 by ApvCode)')}`,
690
690
  value: 'about',
691
691
  description: 'Terminal-Native AI Coding Agent created by ApvCode (https://github.com/apvcode/Termux-Dev)'
692
692
  },
@@ -702,7 +702,7 @@ async function handleSettings(config) {
702
702
  continue;
703
703
  }
704
704
  if (choice === 'about') {
705
- p.note(`⚡ devx v1.4.3 — Terminal-Native AI Coding Agent\n` +
705
+ p.note(`⚡ devx v1.4.5 — Terminal-Native AI Coding Agent\n` +
706
706
  `🎨 Theme: ${currentTh.emoji} ${currentTh.name}\n` +
707
707
  `👤 Author: ApvCode (https://github.com/apvcode)\n` +
708
708
  `🌟 Repository: https://github.com/apvcode/Termux-Dev\n` +
@@ -771,7 +771,7 @@ export async function main() {
771
771
  program
772
772
  .name('devx')
773
773
  .description('Terminal-native AI coding assistant and vibe-coding agent')
774
- .version('1.4.3')
774
+ .version('1.4.5')
775
775
  .option('-p, --prompt <task>', 'Run one-shot task non-interactively (headless mode)')
776
776
  .option('-y, --yolo', 'Automatically approve all tool executions without confirmation')
777
777
  .option('-m, --model <model>', 'Specify AI model to use for this execution')
@@ -202,13 +202,11 @@ export function askPrompt(opts = {}) {
202
202
  const availWidth = innerBoxWidth - 2;
203
203
  let row = '';
204
204
  if (availWidth < 20) {
205
- // Very narrow mobile screen: show command name only
206
205
  const labelStr = item.label.length > availWidth ? item.label.slice(0, availWidth - 1) + '…' : item.label.padEnd(availWidth);
207
206
  const plain = pointer + labelStr;
208
207
  row = isSelected ? theme.badgeFn(plain) : (pointer + theme.boldFn(labelStr));
209
208
  }
210
209
  else {
211
- // Show command name and truncated description
212
210
  const labelMax = Math.min(15, Math.floor(availWidth * 0.45));
213
211
  const labelStr = item.label.length > labelMax ? item.label.slice(0, labelMax - 1) + '…' : item.label.padEnd(labelMax);
214
212
  const descMax = availWidth - labelMax - 1;
@@ -221,7 +219,6 @@ export function askPrompt(opts = {}) {
221
219
  row = `${pointer}${theme.boldFn(labelStr)} ${pc.gray(descStr)}`;
222
220
  }
223
221
  }
224
- // Safety guarantee: exact visible width must match innerBoxWidth
225
222
  const currentLen = stripAnsi(row).length;
226
223
  if (currentLen < innerBoxWidth) {
227
224
  row += ' '.repeat(innerBoxWidth - currentLen);
@@ -230,59 +227,78 @@ export function askPrompt(opts = {}) {
230
227
  }
231
228
  dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╰' + botBorderStr + '╯'));
232
229
  }
233
- // 1. Move cursor back to Line 0 from lastCursorLine and erase everything down
234
- if (lastCursorLine > 0) {
235
- process.stdout.write(`\x1b[${lastCursorLine}A`);
236
- }
237
- process.stdout.write('\r\x1b[J');
238
- // 2. Prepare and write input display
239
- let inputDisplay = pc.dim('│') + ' ';
230
+ // === SCROLLING SINGLE-LINE INPUT (never wraps, works on all terminals) ===
231
+ const prefix = pc.dim('│') + ' ';
232
+ const prefixLen = 3; // visible width of "│ "
233
+ // Reserve 1 char safety margin so terminal never auto-wraps
234
+ const maxVisible = cols - prefixLen - 1;
235
+ let lineContent;
236
+ let screenCursorCol;
240
237
  if (input.length === 0) {
241
- const maxPlace = Math.max(12, cols - 8);
242
- const displayPlace = placeholder.length > maxPlace ? placeholder.slice(0, maxPlace - 1) + '…' : placeholder;
243
- inputDisplay += pc.dim(displayPlace);
238
+ // Show placeholder
239
+ const displayPlace = placeholder.length > maxVisible ? placeholder.slice(0, maxVisible - 1) + '…' : placeholder;
240
+ lineContent = prefix + pc.dim(displayPlace);
241
+ screenCursorCol = prefixLen;
242
+ }
243
+ else if (input.length <= maxVisible) {
244
+ // Input fits on screen entirely
245
+ lineContent = prefix + formatInputWithBadges(input);
246
+ screenCursorCol = prefixLen + cursorPos;
244
247
  }
245
248
  else {
246
- inputDisplay += formatInputWithBadges(input);
247
- }
248
- process.stdout.write(inputDisplay);
249
- // 3. Write dropdown lines below input if present
250
- if (dropdownLines.length > 0) {
251
- for (const dl of dropdownLines) {
252
- process.stdout.write(`\n${dl}`);
253
- }
254
- }
255
- // 4. Calculate cursor position
256
- // Calculate how many wrapped lines inputDisplay occupies
257
- const plainAll = stripAnsi(inputDisplay);
258
- const allLines = plainAll.split('\n');
259
- let totalInputLines = 0;
260
- for (const l of allLines) {
261
- totalInputLines += l.length === 0 ? 1 : Math.max(1, Math.floor((l.length - 1) / cols) + 1);
262
- }
263
- // Calculate where the cursor is inside inputDisplay
264
- const plainBefore = stripAnsi(pc.dim('│') + ' ' + formatInputWithBadges(input.slice(0, cursorPos)));
265
- const linesBefore = plainBefore.split('\n');
266
- let curLine = 0;
267
- for (let i = 0; i < linesBefore.length - 1; i++) {
268
- const l = linesBefore[i];
269
- curLine += l.length === 0 ? 1 : Math.max(1, Math.floor((l.length - 1) / cols) + 1);
270
- }
271
- const lastLineSegment = linesBefore[linesBefore.length - 1];
272
- const wrapOffsetInLastLine = Math.floor(lastLineSegment.length / cols);
273
- curLine += wrapOffsetInLastLine;
274
- const curCol = lastLineSegment.length % cols;
275
- // Current line after writing dropdown is: (totalInputLines - 1) + dropdownLines.length
276
- const totalBottomLine = (totalInputLines - 1) + dropdownLines.length;
277
- const linesUp = totalBottomLine - curLine;
278
- if (linesUp > 0) {
279
- process.stdout.write(`\x1b[${linesUp}A`);
280
- }
281
- process.stdout.write('\r');
282
- if (curCol > 0) {
283
- process.stdout.write(`\x1b[${curCol}C`);
284
- }
285
- lastCursorLine = curLine;
249
+ // Scrolling window: show a slice of input around cursor
250
+ const scrollPad = Math.floor(maxVisible / 3);
251
+ let winStart = 0;
252
+ if (cursorPos > maxVisible - scrollPad) {
253
+ winStart = cursorPos - (maxVisible - scrollPad);
254
+ }
255
+ if (winStart > input.length - maxVisible) {
256
+ winStart = Math.max(0, input.length - maxVisible);
257
+ }
258
+ const winEnd = Math.min(winStart + maxVisible, input.length);
259
+ let visibleSlice = input.slice(winStart, winEnd);
260
+ // Add scroll indicators
261
+ if (winStart > 0) {
262
+ visibleSlice = '…' + visibleSlice.slice(1);
263
+ }
264
+ if (winEnd < input.length) {
265
+ visibleSlice = visibleSlice.slice(0, -1) + '…';
266
+ }
267
+ lineContent = prefix + formatInputWithBadges(visibleSlice);
268
+ screenCursorCol = prefixLen + (cursorPos - winStart);
269
+ // Clamp cursor position for scroll indicator adjustments
270
+ if (winStart > 0 && cursorPos === winStart) {
271
+ screenCursorCol = prefixLen + 1;
272
+ }
273
+ }
274
+ // Pad line to exactly cols-1 to overwrite any stale characters
275
+ const visLen = stripAnsi(lineContent).length;
276
+ if (visLen < cols - 1) {
277
+ lineContent += ' '.repeat(cols - 1 - visLen);
278
+ }
279
+ // Move cursor up past any previous dropdown lines, then clear and redraw
280
+ if (lastDropdownLines > 0) {
281
+ process.stdout.write(`\x1b[${lastDropdownLines}A`);
282
+ }
283
+ // Write input line (always exactly 1 physical line, no wrap)
284
+ process.stdout.write(`\r\x1b[2K${lineContent}`);
285
+ // Write dropdown lines below
286
+ for (let i = 0; i < Math.max(dropdownLines.length, lastDropdownLines); i++) {
287
+ if (i < dropdownLines.length) {
288
+ process.stdout.write(`\n\x1b[2K${dropdownLines[i]}`);
289
+ }
290
+ else {
291
+ process.stdout.write(`\n\x1b[2K`);
292
+ }
293
+ }
294
+ // Move cursor back up to the input line
295
+ const totalBelow = Math.max(dropdownLines.length, lastDropdownLines);
296
+ if (totalBelow > 0) {
297
+ process.stdout.write(`\x1b[${totalBelow}A`);
298
+ }
299
+ // Position cursor at the correct column
300
+ process.stdout.write(`\r\x1b[${screenCursorCol}C`);
301
+ lastCursorLine = 0; // Always line 0 now
286
302
  lastDropdownLines = dropdownLines.length;
287
303
  }
288
304
  render();
@@ -298,10 +314,14 @@ export function askPrompt(opts = {}) {
298
314
  process.stdin.pause();
299
315
  }
300
316
  function clearBoxAndExit(finalInput) {
301
- if (lastCursorLine > 0) {
302
- process.stdout.write(`\x1b[${lastCursorLine}A`);
317
+ // Clear dropdown lines below
318
+ if (lastDropdownLines > 0) {
319
+ for (let i = 0; i < lastDropdownLines; i++) {
320
+ process.stdout.write(`\n\x1b[2K`);
321
+ }
322
+ process.stdout.write(`\x1b[${lastDropdownLines}A`);
303
323
  }
304
- process.stdout.write('\r\x1b[J');
324
+ process.stdout.write(`\r\x1b[2K`);
305
325
  const fullText = expandPastes(finalInput);
306
326
  if (fullText.trim()) {
307
327
  if (GLOBAL_PROMPT_HISTORY.length === 0 || GLOBAL_PROMPT_HISTORY[GLOBAL_PROMPT_HISTORY.length - 1] !== fullText.trim()) {
@@ -309,7 +329,7 @@ export function askPrompt(opts = {}) {
309
329
  }
310
330
  }
311
331
  const lines = fullText.split('\n');
312
- process.stdout.write(`\r\x1b[2K${pc.dim('│')} ${formatInputWithBadges(lines[0])}\n`);
332
+ process.stdout.write(`${pc.dim('│')} ${formatInputWithBadges(lines[0])}\n`);
313
333
  for (let i = 1; i < lines.length; i++) {
314
334
  process.stdout.write(pc.dim('│') + ' ' + lines[i] + '\n');
315
335
  }
@@ -435,10 +455,13 @@ export function askPrompt(opts = {}) {
435
455
  }
436
456
  // Ctrl+C
437
457
  if (str === '\x03' || (str.length === 1 && str.charCodeAt(0) === 3)) {
438
- if (lastCursorLine > 0) {
439
- process.stdout.write(`\x1b[${lastCursorLine}A`);
458
+ if (lastDropdownLines > 0) {
459
+ for (let i = 0; i < lastDropdownLines; i++) {
460
+ process.stdout.write(`\n\x1b[2K`);
461
+ }
462
+ process.stdout.write(`\x1b[${lastDropdownLines}A`);
440
463
  }
441
- process.stdout.write('\r\x1b[J\n');
464
+ process.stdout.write('\r\x1b[2K\n');
442
465
  cleanup();
443
466
  resolve('__CANCEL__');
444
467
  return;
@@ -498,10 +521,13 @@ export function askPrompt(opts = {}) {
498
521
  render();
499
522
  }
500
523
  else {
501
- if (lastCursorLine > 0) {
502
- process.stdout.write(`\x1b[${lastCursorLine}A`);
524
+ if (lastDropdownLines > 0) {
525
+ for (let i = 0; i < lastDropdownLines; i++) {
526
+ process.stdout.write(`\n\x1b[2K`);
527
+ }
528
+ process.stdout.write(`\x1b[${lastDropdownLines}A`);
503
529
  }
504
- process.stdout.write('\r\x1b[J');
530
+ process.stdout.write('\r\x1b[2K');
505
531
  cleanup();
506
532
  resolve(`__TOGGLE_MODE__:${input}`);
507
533
  }
@@ -607,36 +633,52 @@ export function askPrompt(opts = {}) {
607
633
  }
608
634
  return;
609
635
  }
610
- // Backspace
611
- if (str === '\x08' || str === '\x7f' || (str.length === 1 && (str.charCodeAt(0) === 8 || str.charCodeAt(0) === 127))) {
612
- if (cursorPos > 0) {
613
- const spans = getTagSpans();
614
- const endingSpan = spans.find(s => s.end === cursorPos);
615
- if (endingSpan) {
616
- input = input.slice(0, endingSpan.start) + input.slice(endingSpan.end);
617
- cursorPos = endingSpan.start;
636
+ // Backspace / DEL handling (handles single or multiple backspaces from mobile IME)
637
+ if (str.includes('\x08') || str.includes('\x7f')) {
638
+ for (let i = 0; i < str.length; i++) {
639
+ const ch = str[i];
640
+ if (ch === '\x08' || ch === '\x7f') {
641
+ if (cursorPos > 0) {
642
+ const spans = getTagSpans();
643
+ const endingSpan = spans.find(s => s.end === cursorPos);
644
+ if (endingSpan) {
645
+ input = input.slice(0, endingSpan.start) + input.slice(endingSpan.end);
646
+ cursorPos = endingSpan.start;
647
+ }
648
+ else {
649
+ input = input.slice(0, cursorPos - 1) + input.slice(cursorPos);
650
+ cursorPos--;
651
+ }
652
+ }
618
653
  }
619
- else {
620
- input = input.slice(0, cursorPos - 1) + input.slice(cursorPos);
621
- cursorPos--;
654
+ else if (ch.charCodeAt(0) >= 32) {
655
+ const spans = getTagSpans();
656
+ const inside = spans.find(s => cursorPos > s.start && cursorPos < s.end);
657
+ if (inside)
658
+ cursorPos = inside.end;
659
+ input = input.slice(0, cursorPos) + ch + input.slice(cursorPos);
660
+ cursorPos++;
622
661
  }
623
- selectedIndex = 0;
624
- render();
625
662
  }
663
+ selectedIndex = 0;
664
+ render();
626
665
  return;
627
666
  }
628
667
  // Ignore unknown escape sequences
629
668
  if (str.startsWith('\x1b')) {
630
669
  return;
631
670
  }
632
- // Normal character input - snap out of span if needed
671
+ // Normal character input - clean control characters
672
+ const cleanStr = str.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
673
+ if (!cleanStr)
674
+ return;
633
675
  const spans = getTagSpans();
634
676
  const inside = spans.find(s => cursorPos > s.start && cursorPos < s.end);
635
677
  if (inside) {
636
678
  cursorPos = inside.end;
637
679
  }
638
- input = input.slice(0, cursorPos) + str + input.slice(cursorPos);
639
- cursorPos += str.length;
680
+ input = input.slice(0, cursorPos) + cleanStr + input.slice(cursorPos);
681
+ cursorPos += cleanStr.length;
640
682
  selectedIndex = 0;
641
683
  render();
642
684
  }
@@ -139,7 +139,7 @@ function renderDirectoryHtml(dirPath, relPath, files, port) {
139
139
  ${parentLink}
140
140
  ${items || '<li style="padding: 20px; text-align: center; color: #6e7681;">No visible files in this directory</li>'}
141
141
  </ul>
142
- <div class="footer">devx v1.4.3 &bull; Terminal-Native AI Assistant</div>
142
+ <div class="footer">devx v1.4.5 &bull; Terminal-Native AI Assistant</div>
143
143
  </div>
144
144
  </body>
145
145
  </html>`;
@@ -63,7 +63,7 @@ export class MCPClient {
63
63
  capabilities: {},
64
64
  clientInfo: {
65
65
  name: 'devx',
66
- version: '1.4.3'
66
+ version: '1.4.5'
67
67
  }
68
68
  });
69
69
  if (!initResult) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termux-dev",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
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",