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 +7 -3
- package/assets/banner.svg +1 -1
- package/assets/preview.png +0 -0
- package/assets/preview.svg +12 -0
- package/dist/cli/banner.js +6 -2
- package/dist/cli/headless.js +7 -4
- package/dist/cli/index.js +54 -30
- package/dist/cli/prompt.js +84 -67
- package/dist/cli/prompt_palette.js +6 -0
- package/dist/cli/statusline.js +26 -0
- package/dist/core/types.js +1 -1
- package/package.json +4 -2
- package/assets/preview_work1.png +0 -0
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.
|
|
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
|
|
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.
|
|
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 -->
|
package/assets/preview.png
CHANGED
|
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>
|
package/dist/cli/banner.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import { getCurrentTheme } from './theme.js';
|
|
3
|
-
|
|
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 =
|
|
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();
|
package/dist/cli/headless.js
CHANGED
|
@@ -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?.
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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})`);
|
package/dist/cli/prompt.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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 =
|
|
278
|
-
let renderCursorCol =
|
|
294
|
+
let inputDisplay = promptPrefix;
|
|
295
|
+
let renderCursorCol = promptPrefixWidth;
|
|
279
296
|
if (input.length === 0) {
|
|
280
|
-
const maxPlace = Math.max(10, cols -
|
|
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 =
|
|
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 -
|
|
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 =
|
|
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 =
|
|
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${
|
|
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) {
|
package/dist/cli/statusline.js
CHANGED
|
@@ -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();
|
package/dist/core/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEVX_VERSION = '1.
|
|
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.
|
|
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",
|
package/assets/preview_work1.png
DELETED
|
Binary file
|