termux-dev 1.5.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 CHANGED
@@ -20,7 +20,7 @@ Built specifically for **Android Termux**, **Windows**, **macOS**, and **Linux**
20
20
 
21
21
  <br/><br/>
22
22
 
23
- <img src="assets/preview.png" alt="devx Terminal Interface" width="750" style="border-radius: 8px;" />
23
+ <img src="assets/preview.svg" alt="devx Terminal Interface" width="750" style="border-radius: 8px;" />
24
24
 
25
25
  </div>
26
26
 
@@ -28,6 +28,8 @@ Built specifically for **Android Termux**, **Windows**, **macOS**, and **Linux**
28
28
 
29
29
  ## 🌟 Highlights
30
30
 
31
+ The terminal interface uses a compact layout on both desktop and Termux. Switch to the original ASCII layout in `/settings` → `Interface`.
32
+
31
33
  - **🧠 Dual-Brain Architecture (PLAN & AGENT Modes):**
32
34
  Instantly toggle between **PLAN** (safe architect, interactive requirements questionnaire, no code dumps) and **AGENT** (autonomous file edits, terminal commands, auto-installer).
33
35
  - **🚀 One-Click Plan Approval (`[🚀 Go / ✏️ Other]`):**
@@ -155,7 +157,7 @@ Type `/` in the prompt to open the autocomplete command palette:
155
157
  | **`/model`** | Switch model for the active provider with live search |
156
158
  | **`/provider`** | Switch provider (OpenRouter, Gemini, Groq, DeepSeek, Local) |
157
159
  | **`/compact`** | Compact and summarize chat context tokens |
158
- | **`/clear`** | Clear screen and redraw banner |
160
+ | **`/clear`** | Clear screen without deleting conversation history |
159
161
  | **`/init`** | Generate `AGENTS.md` developer guide in project root |
160
162
  | **`/help`** | Display all available commands |
161
163
  | **`/exit`** | Exit devx cleanly |
@@ -182,6 +184,8 @@ If you find **devx** useful, please consider giving it a ⭐️ star on [GitHub]
182
184
 
183
185
  # 🇷🇺 Русский
184
186
 
187
+ Компактный интерфейс используется по умолчанию и на ПК, и в Termux. Исходный ASCII-вариант можно включить в `/settings` → `Interface`.
188
+
185
189
  ## 🌟 Главные возможности
186
190
 
187
191
  - **🧠 Двухрежимная архитектура (PLAN и AGENT):**
@@ -311,7 +315,7 @@ devx
311
315
  | **`/model`** | Сменить модель текущего провайдера с живым поиском |
312
316
  | **`/provider`** | Сменить AI-провайдера (OpenRouter, Gemini, Groq, DeepSeek, Local) |
313
317
  | **`/compact`** | Сжать контекст диалога для экономии токенов |
314
- | **`/clear`** | Очистить экран терминала и перерисовать баннер |
318
+ | **`/clear`** | Очистить экран терминала, сохранив историю диалога |
315
319
  | **`/init`** | Создать файл инструкций `AGENTS.md` в корне проекта |
316
320
  | **`/help`** | Показать список всех доступных команд |
317
321
  | **`/exit`** | Завершить работу devx |
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.5.0</tspan>
28
+ THE TERMINAL-NATIVE AI CODING AGENT <tspan fill="#00f2fe" font-weight="bold">v1.6.0</tspan>
29
29
  </text>
30
30
 
31
31
  <!-- Top Accent Line -->
Binary file
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="760" height="220" viewBox="0 0 760 220">
2
+ <rect width="760" height="220" rx="12" fill="#0a0a0c"/>
3
+ <g font-family="DejaVu Sans Mono, Consolas, monospace" font-size="18">
4
+ <text x="32" y="52" fill="#f1f5f9" font-weight="700">devx</text>
5
+ <text x="92" y="52" fill="#84909c">v1.6.0</text>
6
+ <text x="32" y="102" fill="#67d8e2">agent</text>
7
+ <text x="104" y="102" fill="#e2e8f0" font-weight="700">minimax/minimax-m3:free</text>
8
+ <text x="32" y="132" fill="#84909c">context 0% · $0.0000 · main</text>
9
+ <text x="32" y="186" fill="#67d8e2">›</text>
10
+ <text x="56" y="186" fill="#84909c">Ask or describe a task…</text>
11
+ </g>
12
+ </svg>
@@ -1,10 +1,14 @@
1
1
  import pc from 'picocolors';
2
2
  import { getCurrentTheme } from './theme.js';
3
- export function renderBannerLines(mode = 'full', version = '1.5.0', cols = 80) {
3
+ import { DEVX_VERSION } from '../core/types.js';
4
+ export function renderBannerLines(mode = 'full', version = DEVX_VERSION, cols = 80) {
4
5
  if (mode === 'off') {
5
6
  return [];
6
7
  }
7
8
  const theme = getCurrentTheme();
9
+ if (mode === 'clean') {
10
+ return [pc.bold('devx') + pc.dim(` v${version}`)];
11
+ }
8
12
  if (mode === 'minimal') {
9
13
  return [
10
14
  theme.colorFn('⚡ ') + theme.boldFn('TERMUX·DEV') + ' ' + pc.dim(`v${version}`) + pc.dim(' · Type /help for commands')
@@ -29,7 +33,7 @@ export function renderBannerLines(mode = 'full', version = '1.5.0', cols = 80) {
29
33
  indent + theme.boldFn(`v${version}`)
30
34
  ];
31
35
  }
32
- export function drawBanner(mode = 'full', version = '1.5.0', cols = 80) {
36
+ export function drawBanner(mode = 'full', version = DEVX_VERSION, cols = 80) {
33
37
  const lines = renderBannerLines(mode, version, cols);
34
38
  if (lines.length > 0) {
35
39
  console.log();
@@ -8,6 +8,7 @@ import { getTools } from '../tools/index.js';
8
8
  import { CLIConsoleGuard } from '../permissions/guard.js';
9
9
  import { globalSnapshotManager } from '../core/snapshot.js';
10
10
  import { UsageTracker } from '../core/usage.js';
11
+ import { MCPManager } from '../mcp/manager.js';
11
12
  export async function runHeadlessMode(userPrompt, config, options = {}) {
12
13
  const planMode = !!options.planMode;
13
14
  const isYolo = !!options.yolo || !!config.autoApprove;
@@ -17,10 +18,6 @@ export async function runHeadlessMode(userPrompt, config, options = {}) {
17
18
  const sysPrompt = await buildSystemPrompt(planMode);
18
19
  history.updateSystemPrompt(sysPrompt);
19
20
  history.addMessage({ role: 'user', content: userPrompt });
20
- const provider = createProvider(config);
21
- const tools = getTools(planMode);
22
- const guard = new CLIConsoleGuard(isYolo);
23
- const agent = new Agent(config, provider, tools, history, guard);
24
21
  let executedTools = [];
25
22
  let accumulatedText = '';
26
23
  let accumulatedReasoning = '';
@@ -35,6 +32,11 @@ export async function runHeadlessMode(userPrompt, config, options = {}) {
35
32
  process.exit(130);
36
33
  });
37
34
  try {
35
+ await MCPManager.getInstance().init();
36
+ const provider = createProvider(config);
37
+ const tools = getTools(planMode);
38
+ const guard = new CLIConsoleGuard(isYolo);
39
+ const agent = new Agent(config, provider, tools, history, guard);
38
40
  if (!isQuiet && !isJson) {
39
41
  console.log(pc.bold(pc.cyan(`⚡ devx v${DEVX_VERSION} (headless) | ${planMode ? 'PLAN' : 'AGENT'} | ${config.model}`)));
40
42
  console.log(pc.dim(`Task: ${userPrompt}\n`));
@@ -109,5 +111,6 @@ export async function runHeadlessMode(userPrompt, config, options = {}) {
109
111
  }
110
112
  finally {
111
113
  globalSnapshotManager.finishTurn();
114
+ MCPManager.getInstance().stopAll();
112
115
  }
113
116
  }
package/dist/cli/index.js CHANGED
@@ -34,7 +34,7 @@ import { UsageTracker } from '../core/usage.js';
34
34
  import { SessionExporter } from './export.js';
35
35
  import { MCPManager } from '../mcp/manager.js';
36
36
  import { LiveTelemetryTracker } from './telemetry.js';
37
- import { renderPowerlineStatus, GitStatusCache } from './statusline.js';
37
+ import { renderCleanStatus, renderPowerlineStatus, GitStatusCache } from './statusline.js';
38
38
  import { formatDiffBox } from './diff.js';
39
39
  import { drawBanner } from './banner.js';
40
40
  const CONFIG_PATH = path.join(os.homedir(), '.devxrc.json');
@@ -523,11 +523,11 @@ function formatToolGeneratingLabel(name, targetHint) {
523
523
  return `→ Clarifying questions...`;
524
524
  return `→ ${name}${target}...`;
525
525
  }
526
- async function handleSessionDelete() {
526
+ async function handleSessionDelete(bannerMode) {
527
527
  while (true) {
528
528
  const sessions = await SessionManager.listSessions();
529
529
  if (sessions.length === 0) {
530
- drawLogo();
530
+ drawLogo(bannerMode);
531
531
  p.log.warn('No saved sessions found.');
532
532
  break;
533
533
  }
@@ -562,7 +562,7 @@ async function handleSessionDelete() {
562
562
  break;
563
563
  }
564
564
  if (!chosenSessionId || chosenSessionId === '__cancel__') {
565
- drawLogo();
565
+ drawLogo(bannerMode);
566
566
  p.log.info('Session deletion finished.');
567
567
  break;
568
568
  }
@@ -573,7 +573,7 @@ async function handleSessionDelete() {
573
573
  });
574
574
  if (!p.isCancel(confirmAll) && confirmAll) {
575
575
  const count = await SessionManager.deleteAllSessions();
576
- drawLogo();
576
+ drawLogo(bannerMode);
577
577
  p.log.success(`Deleted all ${count} sessions.`);
578
578
  break;
579
579
  }
@@ -582,7 +582,7 @@ async function handleSessionDelete() {
582
582
  const target = sessions.find(s => s.id === chosenSessionId);
583
583
  const title = target ? target.title : chosenSessionId;
584
584
  const ok = await SessionManager.deleteSession(chosenSessionId);
585
- drawLogo();
585
+ drawLogo(bannerMode);
586
586
  if (ok) {
587
587
  p.log.success(`Deleted session: "${title}"`);
588
588
  }
@@ -607,7 +607,7 @@ async function handleThemeSelect(config) {
607
607
  config.theme = selected;
608
608
  await saveConfig(config);
609
609
  const th = setActiveTheme(selected);
610
- drawLogo();
610
+ drawLogo(config.banner || 'full');
611
611
  p.log.success(th.boldFn(`🎨 Theme switched to ${th.emoji} ${th.name}!`));
612
612
  }
613
613
  }
@@ -627,6 +627,11 @@ async function handleSettings(config) {
627
627
  value: 'change_theme',
628
628
  description: `Switch UI accent colors (${currentTh.desc})`
629
629
  },
630
+ {
631
+ name: `Interface: ${config.ui?.layout === 'classic' ? 'Classic' : 'Clean'}`,
632
+ value: 'toggle_layout',
633
+ description: 'Switch between the compact layout and the original terminal UI'
634
+ },
630
635
  {
631
636
  name: `${config.pureBlackTheme !== false ? pc.green('🖤 Pure Black Background: ON') : pc.yellow('🖤 Pure Black Background: OFF')}`,
632
637
  value: 'toggle_black_theme',
@@ -676,6 +681,14 @@ async function handleSettings(config) {
676
681
  await handleThemeSelect(config);
677
682
  continue;
678
683
  }
684
+ if (choice === 'toggle_layout') {
685
+ config.ui = config.ui || {};
686
+ config.ui.layout = config.ui.layout === 'classic' ? 'clean' : 'classic';
687
+ config.banner = config.ui.layout === 'clean' ? 'clean' : 'full';
688
+ await saveConfig(config);
689
+ drawLogo(config.banner);
690
+ continue;
691
+ }
679
692
  if (choice === 'about') {
680
693
  p.note(`⚡ devx v${DEVX_VERSION} — Terminal-Native AI Coding Agent\n` +
681
694
  `🎨 Theme: ${currentTh.emoji} ${currentTh.name}\n` +
@@ -694,7 +707,7 @@ async function handleSettings(config) {
694
707
  else {
695
708
  resetTerminalTheme();
696
709
  }
697
- drawLogo();
710
+ drawLogo(config.banner || 'full');
698
711
  p.log.success(`Pure Black background: ${config.pureBlackTheme ? pc.bold(pc.green('ON (Deep Black)')) : pc.bold(pc.yellow('OFF (System Default)'))}`);
699
712
  continue;
700
713
  }
@@ -785,6 +798,9 @@ export async function main() {
785
798
  const planModeInitial = !!options.plan;
786
799
  const isHeadless = !!options.prompt;
787
800
  let config = await loadConfig(!isHeadless);
801
+ config.banner = config.ui?.layout === 'classic'
802
+ ? config.banner || 'full'
803
+ : config.banner === 'off' ? 'off' : 'clean';
788
804
  if (options.model) {
789
805
  config.model = options.model;
790
806
  config.maxContextTokens = getModelContextLimit(options.model);
@@ -864,7 +880,18 @@ export async function main() {
864
880
  const cols = process.stdout.columns || 80;
865
881
  const modeName = planMode ? 'PLAN' : 'AGENT';
866
882
  const theme = getCurrentTheme();
867
- if (config.ui?.powerlineStatus) {
883
+ if (config.ui?.layout !== 'classic') {
884
+ console.log(renderCleanStatus({
885
+ mode: modeName,
886
+ model: config.model,
887
+ currentTokens,
888
+ maxTokens,
889
+ cost: totalSessionCost,
890
+ cols,
891
+ isYolo: !!config.autoApprove
892
+ }) + '\n');
893
+ }
894
+ else if (config.ui?.powerlineStatus) {
868
895
  const statusLine = renderPowerlineStatus({
869
896
  mode: modeName,
870
897
  model: config.model,
@@ -901,12 +928,16 @@ export async function main() {
901
928
  if (autoTriggerPrompt) {
902
929
  answer = autoTriggerPrompt;
903
930
  autoTriggerPrompt = '';
904
- console.log(theme.colorFn('◆') + ' ' + pc.bold(pc.white(answer)));
931
+ console.log(config.ui?.layout === 'classic'
932
+ ? theme.colorFn('◆') + ' ' + pc.bold(pc.white(answer))
933
+ : pc.cyan('›') + ' ' + answer);
905
934
  }
906
935
  else {
907
936
  const inputStr = await askPrompt({
908
937
  message: 'Ask anything...',
909
- placeholder: 'Fix a TODO, type /help, or press Tab to switch mode',
938
+ placeholder: config.ui?.layout === 'classic'
939
+ ? 'Fix a TODO, type /help, or press Tab to switch mode'
940
+ : 'Ask or describe a task…',
910
941
  initialValue: currentDraft,
911
942
  planMode,
912
943
  isYolo: !!config.autoApprove,
@@ -1014,7 +1045,7 @@ export async function main() {
1014
1045
  continue;
1015
1046
  }
1016
1047
  if (picked === '/session del') {
1017
- await handleSessionDelete();
1048
+ await handleSessionDelete(config.banner || 'full');
1018
1049
  continue;
1019
1050
  }
1020
1051
  cmd = picked;
@@ -1026,7 +1057,7 @@ export async function main() {
1026
1057
  }
1027
1058
  if (cmd === '/settings') {
1028
1059
  config = await handleSettings(config);
1029
- drawLogo();
1060
+ drawLogo(config.banner || 'full');
1030
1061
  continue;
1031
1062
  }
1032
1063
  if (cmd === '/update') {
@@ -1058,9 +1089,7 @@ export async function main() {
1058
1089
  break;
1059
1090
  }
1060
1091
  if (cmd === '/clear') {
1061
- history.clear();
1062
- history.addMessage({ role: 'system', content: await buildSystemPrompt(planMode) });
1063
- p.log.success('History cleared.');
1092
+ drawLogo(config.banner || 'full');
1064
1093
  continue;
1065
1094
  }
1066
1095
  if (cmd === '/new' || cmd === '/reset') {
@@ -1068,7 +1097,7 @@ export async function main() {
1068
1097
  history = new History();
1069
1098
  history.addMessage({ role: 'system', content: await buildSystemPrompt(planMode) });
1070
1099
  totalSessionCost = 0;
1071
- drawLogo();
1100
+ drawLogo(config.banner || 'full');
1072
1101
  const initialPrompt = parts.slice(1).join(' ').trim();
1073
1102
  if (initialPrompt) {
1074
1103
  p.log.success('Started new session with prompt.');
@@ -1082,7 +1111,7 @@ export async function main() {
1082
1111
  else if (cmd === '/session' || cmd === '/sessions' || cmd === '/resume' || cmd === '/history') {
1083
1112
  const sub = (parts[1] || '').toLowerCase();
1084
1113
  if (sub === 'del' || sub === 'delete' || sub === 'rm') {
1085
- await handleSessionDelete();
1114
+ await handleSessionDelete(config.banner || 'full');
1086
1115
  continue;
1087
1116
  }
1088
1117
  const sessions = await SessionManager.listSessions();
@@ -1124,7 +1153,7 @@ export async function main() {
1124
1153
  continue;
1125
1154
  }
1126
1155
  if (chosenSessionId === '__delete_mode__') {
1127
- await handleSessionDelete();
1156
+ await handleSessionDelete(config.banner || 'full');
1128
1157
  continue;
1129
1158
  }
1130
1159
  const loaded = await SessionManager.loadSession(chosenSessionId);
@@ -1142,7 +1171,7 @@ export async function main() {
1142
1171
  planMode = loaded.planMode;
1143
1172
  }
1144
1173
  totalSessionCost = loaded.totalCost || 0;
1145
- drawLogo();
1174
+ drawLogo(config.banner || 'full');
1146
1175
  const nonSysMessages = loaded.messages.filter(m => m.role !== 'system');
1147
1176
  const totalMsgs = nonSysMessages.length;
1148
1177
  const displayLimit = 20;
@@ -1194,7 +1223,7 @@ export async function main() {
1194
1223
  planMode = true;
1195
1224
  const newSys = await buildSystemPrompt(true);
1196
1225
  history.updateSystemPrompt(newSys);
1197
- drawLogo();
1226
+ drawLogo(config.banner || 'full');
1198
1227
  p.log.success(pc.cyan('Switched to PLAN mode (Architect & Planner). Modifying tools are disabled.'));
1199
1228
  continue;
1200
1229
  }
@@ -1202,7 +1231,7 @@ export async function main() {
1202
1231
  planMode = false;
1203
1232
  const newSys = await buildSystemPrompt(false);
1204
1233
  history.updateSystemPrompt(newSys);
1205
- drawLogo();
1234
+ drawLogo(config.banner || 'full');
1206
1235
  p.log.success(pc.green('Switched to AGENT mode (Coder & Executor). Full tools enabled.'));
1207
1236
  continue;
1208
1237
  }
@@ -1366,11 +1395,6 @@ export async function main() {
1366
1395
  }
1367
1396
  continue;
1368
1397
  }
1369
- if (cmd === '/clear') {
1370
- console.clear();
1371
- drawLogo();
1372
- continue;
1373
- }
1374
1398
  if (cmd === '/session' && parts[1]?.toLowerCase() !== 'del') {
1375
1399
  const cur = sessionManager.getSession();
1376
1400
  const nonSys = history.getMessages().filter(m => m.role !== 'system');
@@ -1432,7 +1456,7 @@ export async function main() {
1432
1456
  config.theme = matched.id;
1433
1457
  await saveConfig(config);
1434
1458
  const th = setActiveTheme(matched.id);
1435
- drawLogo();
1459
+ drawLogo(config.banner || 'full');
1436
1460
  p.log.success(th.boldFn(`🎨 Theme switched to ${th.emoji} ${th.name}!`));
1437
1461
  }
1438
1462
  else {
@@ -1518,7 +1542,7 @@ export async function main() {
1518
1542
  }
1519
1543
  if (cmd === '/model') {
1520
1544
  const res = await selectModel(config.baseUrl, config.apiKey, config.model, config.provider || '', true);
1521
- drawLogo();
1545
+ drawLogo(config.banner || 'full');
1522
1546
  if (res) {
1523
1547
  config.model = res.model;
1524
1548
  config.maxContextTokens = res.contextLimit;
@@ -1532,7 +1556,7 @@ export async function main() {
1532
1556
  }
1533
1557
  if (cmd === '/provider' || cmd === '/providers') {
1534
1558
  const newConfig = await selectProvider(config, true);
1535
- drawLogo();
1559
+ drawLogo(config.banner || 'full');
1536
1560
  if (newConfig) {
1537
1561
  config = newConfig;
1538
1562
  p.log.success(`Switched provider to: ${pc.bold(config.provider || 'custom')} (${config.model})`);
@@ -30,7 +30,7 @@ export const SLASH_COMMANDS = [
30
30
  { cmd: '/commit', desc: 'AI-generated git commit message & commit' },
31
31
  { cmd: '/status', desc: 'Show git repository file status' },
32
32
  { cmd: '/compact', desc: 'Compact & summarize chat context' },
33
- { cmd: '/clear', desc: 'Clear screen & redraw banner' },
33
+ { cmd: '/clear', desc: 'Clear screen' },
34
34
  { cmd: '/init', desc: 'Generate AGENTS.md project instructions' },
35
35
  { cmd: '/config', desc: 'View current configuration' },
36
36
  { cmd: '/help', desc: 'Show all available commands' },
@@ -43,15 +43,20 @@ function stripAnsi(str) {
43
43
  export function askPrompt(opts = {}) {
44
44
  return new Promise((resolve) => {
45
45
  const theme = getCurrentTheme();
46
- if (opts.ui?.modeBorder) {
47
- const header = formatPromptHeader(!!opts.planMode, !!opts.isYolo, opts.message);
48
- console.log(header);
49
- }
50
- else {
51
- const msg = opts.message
52
- ? `${opts.message} ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`
53
- : `Ask anything... ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`;
54
- console.log(theme.colorFn('◆') + ' ' + pc.bold(msg));
46
+ const cleanLayout = opts.ui?.layout !== 'classic';
47
+ const promptPrefix = cleanLayout ? pc.cyan('›') + ' ' : pc.dim('│') + ' ';
48
+ const promptPrefixWidth = cleanLayout ? 2 : 3;
49
+ if (!cleanLayout) {
50
+ if (opts.ui?.modeBorder) {
51
+ const header = formatPromptHeader(!!opts.planMode, !!opts.isYolo, opts.message);
52
+ console.log(header);
53
+ }
54
+ else {
55
+ const msg = opts.message
56
+ ? `${opts.message} ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`
57
+ : `Ask anything... ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`;
58
+ console.log(theme.colorFn('◆') + ' ' + pc.bold(msg));
59
+ }
55
60
  }
56
61
  let availableFiles = [];
57
62
  let customCommandsList = [];
@@ -97,7 +102,7 @@ export function askPrompt(opts = {}) {
97
102
  return spans;
98
103
  }
99
104
  function formatInputWithBadges(str) {
100
- return str.replace(/(\[(?:Pasted text #\d+ \+\d+ lines|\d+\.png \d+kb)\])/g, (match) => theme.badgeFn(` ${match.slice(1, -1)} `) + '\x1b[0m');
105
+ return str.replace(/(\[(?:Pasted text #\d+ \+\d+ lines|\d+\.png \d+kb)\])/g, (match) => (cleanLayout ? pc.dim(match) : theme.badgeFn(` ${match.slice(1, -1)} `)) + '\x1b[0m');
101
106
  }
102
107
  function expandPastes(text) {
103
108
  let expanded = text;
@@ -148,7 +153,7 @@ export function askPrompt(opts = {}) {
148
153
  function getDropdownItems() {
149
154
  if (input.startsWith('/')) {
150
155
  const q = input.trim().toLowerCase();
151
- if (opts.ui?.commandPalette) {
156
+ if (cleanLayout || opts.ui?.commandPalette) {
152
157
  const baseList = [
153
158
  ...SLASH_COMMANDS.map((c) => ({ cmd: c.cmd, desc: c.desc })),
154
159
  ...customCommandsList.map((c) => ({ cmd: c.cmd, desc: `(custom) ${c.desc}` }))
@@ -211,8 +216,6 @@ export function askPrompt(opts = {}) {
211
216
  // Limit dropdown height gracefully so it always fits comfortably
212
217
  const maxDropdownRows = Math.max(2, Math.min(rows - 3, 5));
213
218
  const pageSize = Math.min(4, Math.max(2, Math.min(maxDropdownRows - 2, Math.floor(rows / 4))));
214
- // Strict boundary: left margin (2) + box borders (2) + inner width <= cols - 4
215
- // This mathematically prevents terminal auto-wrapping on narrow mobile screens
216
219
  const boxWidth = Math.max(12, Math.min(cols - 6, 46));
217
220
  const innerWidth = Math.max(8, boxWidth - 2);
218
221
  const total = items.length;
@@ -223,72 +226,86 @@ export function askPrompt(opts = {}) {
223
226
  }
224
227
  }
225
228
  const endIndex = Math.min(startIndex + pageSize, total);
226
- const hasMoreUp = startIndex > 0;
227
- const hasMoreDown = endIndex < total;
228
- let topBorderStr = '─'.repeat(innerWidth);
229
- if (hasMoreUp) {
230
- const mid = Math.max(0, Math.floor(innerWidth / 2) - 2);
231
- topBorderStr = '─'.repeat(mid) + ' ▲ ' + '─'.repeat(Math.max(0, innerWidth - mid - 3));
232
- }
233
- let botBorderStr = '─'.repeat(innerWidth);
234
- if (hasMoreDown) {
235
- const mid = Math.max(0, Math.floor(innerWidth / 2) - 2);
236
- botBorderStr = '─'.repeat(mid) + ' ▼ ' + '─'.repeat(Math.max(0, innerWidth - mid - 3));
237
- }
238
- dropdownLines.push(pc.dim('│ ') + pc.dim('╭' + topBorderStr + '╮') + '\x1b[0m');
239
- for (let i = startIndex; i < endIndex; i++) {
240
- const item = items[i];
241
- const isSelected = i === selectedIndex;
242
- const pointer = isSelected ? '› ' : ' ';
243
- const avail = Math.max(4, innerWidth - pointer.length);
244
- let rowText = '';
245
- if (avail < 16) {
246
- const labelStr = item.label.length > avail
247
- ? item.label.slice(0, avail - 1) + '…'
248
- : item.label.padEnd(avail, ' ');
249
- rowText = pointer + (isSelected ? theme.boldFn(labelStr) : labelStr);
229
+ if (cleanLayout) {
230
+ for (let i = startIndex; i < endIndex; i++) {
231
+ const item = items[i];
232
+ const selected = i === selectedIndex;
233
+ const labelWidth = Math.min(20, cols - 5);
234
+ const label = item.label.length > labelWidth ? `${item.label.slice(0, labelWidth - 1)}…` : item.label;
235
+ const descWidth = cols - label.length - 7;
236
+ const desc = descWidth >= 5
237
+ ? item.desc.slice(0, descWidth)
238
+ : '';
239
+ dropdownLines.push(` ${selected ? pc.cyan('›') : ' '} ${selected ? pc.bold(label) : label}${desc ? ` ${pc.dim(desc)}` : ''}`);
250
240
  }
251
- else {
252
- const labelMax = Math.min(12, Math.max(6, Math.floor(avail * 0.38)));
253
- const labelStr = item.label.length > labelMax
254
- ? item.label.slice(0, labelMax - 1) + '…'
255
- : item.label.padEnd(labelMax, ' ');
256
- const descMax = Math.max(3, avail - labelMax - 1);
257
- const descStr = item.desc.length > descMax
258
- ? item.desc.slice(0, descMax - 1) + '…'
259
- : item.desc.padEnd(descMax, ' ');
260
- rowText = pointer + (isSelected ? theme.boldFn(labelStr) : labelStr) + ' ' + pc.gray(descStr);
241
+ }
242
+ else {
243
+ const hasMoreUp = startIndex > 0;
244
+ const hasMoreDown = endIndex < total;
245
+ let topBorderStr = '─'.repeat(innerWidth);
246
+ if (hasMoreUp) {
247
+ const mid = Math.max(0, Math.floor(innerWidth / 2) - 2);
248
+ topBorderStr = '─'.repeat(mid) + ' ▲ ' + '─'.repeat(Math.max(0, innerWidth - mid - 3));
261
249
  }
262
- // Calculate visible plain length without ANSI
263
- const plainLen = rowText.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '').length;
264
- const padCount = Math.max(0, innerWidth - plainLen);
265
- const fullRow = rowText + ' '.repeat(padCount);
266
- if (isSelected) {
267
- const cleanFull = fullRow.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
268
- dropdownLines.push(pc.dim('│ ') + pc.dim('│') + theme.badgeFn(cleanFull) + pc.dim('│') + '\x1b[0m');
250
+ let botBorderStr = '─'.repeat(innerWidth);
251
+ if (hasMoreDown) {
252
+ const mid = Math.max(0, Math.floor(innerWidth / 2) - 2);
253
+ botBorderStr = '─'.repeat(mid) + ' ▼ ' + '─'.repeat(Math.max(0, innerWidth - mid - 3));
269
254
  }
270
- else {
271
- dropdownLines.push(pc.dim('│ ') + pc.dim('│') + fullRow + pc.dim('│') + '\x1b[0m');
255
+ dropdownLines.push(pc.dim('│ ') + pc.dim('╭' + topBorderStr + '╮') + '\x1b[0m');
256
+ for (let i = startIndex; i < endIndex; i++) {
257
+ const item = items[i];
258
+ const isSelected = i === selectedIndex;
259
+ const pointer = isSelected ? '› ' : ' ';
260
+ const avail = Math.max(4, innerWidth - pointer.length);
261
+ let rowText = '';
262
+ if (avail < 16) {
263
+ const labelStr = item.label.length > avail
264
+ ? item.label.slice(0, avail - 1) + '…'
265
+ : item.label.padEnd(avail, ' ');
266
+ rowText = pointer + (isSelected ? theme.boldFn(labelStr) : labelStr);
267
+ }
268
+ else {
269
+ const labelMax = Math.min(12, Math.max(6, Math.floor(avail * 0.38)));
270
+ const labelStr = item.label.length > labelMax
271
+ ? item.label.slice(0, labelMax - 1) + '…'
272
+ : item.label.padEnd(labelMax, ' ');
273
+ const descMax = Math.max(3, avail - labelMax - 1);
274
+ const descStr = item.desc.length > descMax
275
+ ? item.desc.slice(0, descMax - 1) + '…'
276
+ : item.desc.padEnd(descMax, ' ');
277
+ rowText = pointer + (isSelected ? theme.boldFn(labelStr) : labelStr) + ' ' + pc.gray(descStr);
278
+ }
279
+ const plainLen = rowText.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '').length;
280
+ const padCount = Math.max(0, innerWidth - plainLen);
281
+ const fullRow = rowText + ' '.repeat(padCount);
282
+ if (isSelected) {
283
+ const cleanFull = fullRow.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
284
+ dropdownLines.push(pc.dim('│ ') + pc.dim('│') + theme.badgeFn(cleanFull) + pc.dim('│') + '\x1b[0m');
285
+ }
286
+ else {
287
+ dropdownLines.push(pc.dim('│ ') + pc.dim('│') + fullRow + pc.dim('│') + '\x1b[0m');
288
+ }
272
289
  }
290
+ dropdownLines.push(pc.dim('│ ') + pc.dim('╰' + botBorderStr + '╯') + '\x1b[0m');
273
291
  }
274
- dropdownLines.push(pc.dim('│ ') + pc.dim('╰' + botBorderStr + '╯') + '\x1b[0m');
275
292
  }
276
293
  // 1. Format and write the Prompt line safely
277
- let inputDisplay = pc.dim('│') + ' ';
278
- let renderCursorCol = 3;
294
+ let inputDisplay = promptPrefix;
295
+ let renderCursorCol = promptPrefixWidth;
279
296
  if (input.length === 0) {
280
- const maxPlace = Math.max(10, cols - 6);
297
+ const maxPlace = Math.max(10, cols - promptPrefixWidth - 2);
281
298
  const displayPlace = placeholder.length > maxPlace ? placeholder.slice(0, maxPlace - 1) + '…' : placeholder;
282
299
  inputDisplay += pc.dim(displayPlace) + '\x1b[0m';
283
- renderCursorCol = 3;
300
+ renderCursorCol = promptPrefixWidth;
284
301
  }
285
302
  else {
286
- const tokenBadge = opts.ui?.inputTokenCounter ? formatPromptTokenBadge(estimatePromptTokens(input), 40) : '';
303
+ const tokenBadge = !cleanLayout && opts.ui?.inputTokenCounter ? formatPromptTokenBadge(estimatePromptTokens(input), 40) : '';
287
304
  const badgeSuffix = tokenBadge ? ` ${tokenBadge}` : '';
288
- const maxInputLen = Math.max(10, cols - 6 - (tokenBadge ? 12 : 0));
305
+ const maxInputLen = Math.max(10, cols - promptPrefixWidth - 3 - (tokenBadge ? 12 : 0));
289
306
  if (input.length <= maxInputLen) {
290
307
  inputDisplay += formatInputWithBadges(input) + badgeSuffix + '\x1b[0m';
291
- renderCursorCol = 3 + cursorPos;
308
+ renderCursorCol = promptPrefixWidth + cursorPos;
292
309
  }
293
310
  else {
294
311
  // Horizontal scrolling to prevent auto-wrapping
@@ -297,7 +314,7 @@ export function askPrompt(opts = {}) {
297
314
  const prefix = start > 0 ? '…' : '';
298
315
  const suffix = start + maxInputLen < input.length ? '…' : '';
299
316
  inputDisplay += pc.dim(prefix) + formatInputWithBadges(visibleChunk) + pc.dim(suffix) + badgeSuffix + '\x1b[0m';
300
- renderCursorCol = 3 + (prefix ? 1 : 0) + (cursorPos - start);
317
+ renderCursorCol = promptPrefixWidth + (prefix ? 1 : 0) + (cursorPos - start);
301
318
  }
302
319
  }
303
320
  // Write prompt line
@@ -356,7 +373,7 @@ export function askPrompt(opts = {}) {
356
373
  GLOBAL_PROMPT_HISTORY.push(fullText.trim());
357
374
  }
358
375
  }
359
- process.stdout.write(`\x1b[0m\r\x1b[2K${pc.dim('│')} ${formatInputWithBadges(finalInput)}\x1b[0m\n\n`);
376
+ process.stdout.write(`\x1b[0m\r\x1b[2K${promptPrefix}${formatInputWithBadges(finalInput)}\x1b[0m\n\n`);
360
377
  cleanup();
361
378
  resolve(fullText);
362
379
  }
@@ -7,6 +7,12 @@ export function filterCommandPalette(query, commands) {
7
7
  return commands.filter((c) => {
8
8
  const name = c.cmd.toLowerCase().replace(/^\//, '');
9
9
  return name.includes(q) || c.desc.toLowerCase().includes(q);
10
+ }).sort((a, b) => {
11
+ const rank = (cmd) => {
12
+ const name = cmd.toLowerCase().replace(/^\//, '');
13
+ return name.startsWith(q) ? 0 : name.includes(q) ? 1 : 2;
14
+ };
15
+ return rank(a.cmd) - rank(b.cmd);
10
16
  });
11
17
  }
12
18
  export function estimatePromptTokens(text) {
@@ -65,6 +65,32 @@ export function formatCostBadge(cost, maxCostUSD) {
65
65
  }
66
66
  return pc.green(costFormatted);
67
67
  }
68
+ function clipLabel(label, width) {
69
+ if (label.length <= width)
70
+ return label;
71
+ return width > 1 ? `${label.slice(0, width - 1)}…` : label.slice(0, width);
72
+ }
73
+ export function renderCleanStatus(opts) {
74
+ const cols = Math.max(20, opts.cols || process.stdout.columns || 80);
75
+ const available = cols - 2;
76
+ const mode = opts.isYolo ? 'YOLO' : opts.mode;
77
+ const modelWidth = Math.max(4, available - mode.length - 2);
78
+ const shortModel = opts.model.length > modelWidth && opts.model.includes('/')
79
+ ? opts.model.split('/').pop() || opts.model
80
+ : opts.model;
81
+ const model = clipLabel(shortModel, modelWidth);
82
+ const percent = Math.min(100, Math.round((opts.currentTokens / Math.max(1, opts.maxTokens)) * 100));
83
+ const contextLabel = cols < 38 ? 'ctx' : 'context';
84
+ let details = `${contextLabel} ${percent}% · $${opts.cost.toFixed(4)}`;
85
+ if (details.length > available)
86
+ details = `${percent}% · $${opts.cost.toFixed(2)}`;
87
+ const git = GitStatusCache.getStatus();
88
+ const branch = git ? ` · ${git.branch}${git.isDirty ? '*' : ''}` : '';
89
+ if (details.length + branch.length <= available)
90
+ details += branch;
91
+ details = clipLabel(details, available);
92
+ return `${pc.cyan(mode.toLowerCase())} ${pc.bold(model)}\n${pc.dim(details)}`;
93
+ }
68
94
  export function renderPowerlineStatus(opts) {
69
95
  const cols = opts.cols || process.stdout.columns || 80;
70
96
  const theme = getCurrentTheme();
@@ -1 +1 @@
1
- export const DEVX_VERSION = '1.5.0';
1
+ export const DEVX_VERSION = '1.6.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termux-dev",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
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",
@@ -15,7 +15,9 @@
15
15
  "files": [
16
16
  "bin",
17
17
  "dist",
18
- "assets"
18
+ "assets/banner.svg",
19
+ "assets/preview.svg",
20
+ "assets/preview.png"
19
21
  ],
20
22
  "scripts": {
21
23
  "build": "tsc && node ./scripts/copy-assets.js",
Binary file