termux-dev 1.4.11 → 1.4.12
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 +1 -1
- package/dist/cli/doctor.js +1 -1
- package/dist/cli/headless.js +1 -1
- package/dist/cli/index.js +6 -6
- package/dist/cli/prompt.js +521 -126
- package/dist/cli/server.js +1 -1
- package/dist/mcp/client.js +1 -1
- package/package.json +1 -1
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.
|
|
28
|
+
THE TERMINAL-NATIVE AI CODING AGENT <tspan fill="#00f2fe" font-weight="bold">v1.4.12</tspan>
|
|
29
29
|
</text>
|
|
30
30
|
|
|
31
31
|
<!-- Top Accent Line -->
|
package/dist/cli/doctor.js
CHANGED
|
@@ -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.
|
|
21
|
+
'User-Agent': 'devx-doctor/1.4.12'
|
|
22
22
|
}
|
|
23
23
|
}, (res) => {
|
|
24
24
|
res.resume(); // consume response data to free up memory and release socket
|
package/dist/cli/headless.js
CHANGED
|
@@ -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.
|
|
38
|
+
console.log(pc.bold(pc.cyan(`⚡ devx v1.4.12 (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.
|
|
487
|
+
' ' + theme.boldFn('v1.4.12'),
|
|
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.
|
|
502
|
+
indent + theme.boldFn('v1.4.12'),
|
|
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.
|
|
648
|
+
message: `${pc.bold('⚙️ Settings')} ${pc.dim(`(devx v1.4.12 • 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.
|
|
689
|
+
name: `${currentTh.colorFn('✨ About devx')} ${pc.dim('(v1.4.12 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.
|
|
705
|
+
p.note(`⚡ devx v1.4.12 — 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.
|
|
774
|
+
.version('1.4.12')
|
|
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')
|
package/dist/cli/prompt.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import readline from 'readline';
|
|
2
1
|
import pc from 'picocolors';
|
|
3
2
|
import { scanProjectFiles } from './files.js';
|
|
4
3
|
import { saveClipboardImage, processPastedFilePath } from './clipboard.js';
|
|
@@ -35,6 +34,9 @@ export const SLASH_COMMANDS = [
|
|
|
35
34
|
{ cmd: '/exit', desc: 'Exit devx' }
|
|
36
35
|
];
|
|
37
36
|
const GLOBAL_PROMPT_HISTORY = [];
|
|
37
|
+
function stripAnsi(str) {
|
|
38
|
+
return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
39
|
+
}
|
|
38
40
|
export function askPrompt(opts = {}) {
|
|
39
41
|
return new Promise((resolve) => {
|
|
40
42
|
const msg = opts.message
|
|
@@ -48,79 +50,62 @@ export function askPrompt(opts = {}) {
|
|
|
48
50
|
CustomCommandManager.listCommands()
|
|
49
51
|
.then((c) => (customCommandsList = c.map((x) => ({ cmd: x.cmd, desc: x.desc }))))
|
|
50
52
|
.catch(() => { });
|
|
53
|
+
let input = opts.initialValue || '';
|
|
54
|
+
let cursorPos = input.length;
|
|
55
|
+
let selectedIndex = 0;
|
|
56
|
+
let lastRenderedDropdownLines = 0;
|
|
57
|
+
let historyIndex = GLOBAL_PROMPT_HISTORY.length;
|
|
58
|
+
let tempDraft = '';
|
|
51
59
|
const imageAttachments = [];
|
|
52
60
|
const usedImageNames = new Set();
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
// 2. @file autocomplete
|
|
65
|
-
const atMatch = line.match(/@([a-zA-Z0-9_\-./]*)$/);
|
|
66
|
-
if (atMatch && availableFiles.length > 0) {
|
|
67
|
-
const query = atMatch[1].toLowerCase();
|
|
68
|
-
const hits = availableFiles
|
|
69
|
-
.filter((f) => f.toLowerCase().includes(query) || query === '')
|
|
70
|
-
.slice(0, 15)
|
|
71
|
-
.map((f) => `@${f}${f.endsWith('/') ? '' : ' '}`);
|
|
72
|
-
return [hits, atMatch[0]];
|
|
73
|
-
}
|
|
74
|
-
return [[], line];
|
|
75
|
-
};
|
|
76
|
-
const rl = readline.createInterface({
|
|
77
|
-
input: process.stdin,
|
|
78
|
-
output: process.stdout,
|
|
79
|
-
prompt: pc.dim('│') + ' ',
|
|
80
|
-
completer,
|
|
81
|
-
historySize: 100
|
|
82
|
-
});
|
|
83
|
-
// Populate history in readline
|
|
84
|
-
if (GLOBAL_PROMPT_HISTORY.length > 0) {
|
|
85
|
-
rl.history = [...GLOBAL_PROMPT_HISTORY].reverse();
|
|
61
|
+
const pastedBlocks = [];
|
|
62
|
+
let nextPasteId = 1;
|
|
63
|
+
let inBracketedPaste = false;
|
|
64
|
+
let bracketedBuffer = '';
|
|
65
|
+
const placeholder = opts.placeholder || 'Describe a task, @file, /help, paste image, or press Tab to switch mode';
|
|
66
|
+
let disposed = false;
|
|
67
|
+
process.stdin.resume();
|
|
68
|
+
if (process.stdin.isTTY) {
|
|
69
|
+
process.stdin.setRawMode(true);
|
|
70
|
+
process.stdout.write('\x1b[?2004h'); // Enable bracketed paste mode
|
|
86
71
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
rl.close();
|
|
94
|
-
};
|
|
95
|
-
const onKeypress = (str, key) => {
|
|
96
|
-
if (isClosed)
|
|
97
|
-
return;
|
|
98
|
-
const curLine = rl.line || '';
|
|
99
|
-
// INSTANT COMMAND MENU TRIGGER
|
|
100
|
-
// If user types '/' as the first character, instantly launch the robust search menu!
|
|
101
|
-
if (str === '/' && curLine === '/') {
|
|
102
|
-
readline.cursorTo(process.stdout, 0);
|
|
103
|
-
readline.clearLine(process.stdout, 0);
|
|
104
|
-
cleanup();
|
|
105
|
-
resolve('/');
|
|
106
|
-
return;
|
|
72
|
+
function getTagSpans() {
|
|
73
|
+
const spans = [];
|
|
74
|
+
const regex = /\[(?:Pasted text #\d+ \+\d+ lines|\d+\.png \d+kb)\]/g;
|
|
75
|
+
let match;
|
|
76
|
+
while ((match = regex.exec(input)) !== null) {
|
|
77
|
+
spans.push({ start: match.index, end: match.index + match[0].length });
|
|
107
78
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
79
|
+
return spans;
|
|
80
|
+
}
|
|
81
|
+
function formatInputWithBadges(str) {
|
|
82
|
+
return str.replace(/(\[(?:Pasted text #\d+ \+\d+ lines|\d+\.png \d+kb)\])/g, (match) => theme.badgeFn(` ${match.slice(1, -1)} `));
|
|
83
|
+
}
|
|
84
|
+
function expandPastes(text) {
|
|
85
|
+
let expanded = text;
|
|
86
|
+
for (const block of pastedBlocks) {
|
|
87
|
+
expanded = expanded.split(block.tag).join(block.text);
|
|
88
|
+
}
|
|
89
|
+
for (const img of imageAttachments) {
|
|
90
|
+
expanded = expanded.split(img.tag).join(`@${img.filePath.replace(/\\/g, '/')} `);
|
|
91
|
+
}
|
|
92
|
+
return expanded;
|
|
93
|
+
}
|
|
94
|
+
function handlePaste(text) {
|
|
95
|
+
const trimmed = text.trim();
|
|
96
|
+
const lines = trimmed.split(/\r\n|\r|\n/);
|
|
97
|
+
if (lines.length <= 1 && text.length < 80) {
|
|
98
|
+
// Short paste: insert normally
|
|
99
|
+
input = input.slice(0, cursorPos) + text + input.slice(cursorPos);
|
|
100
|
+
cursorPos += text.length;
|
|
101
|
+
render();
|
|
102
|
+
return;
|
|
118
103
|
}
|
|
119
|
-
//
|
|
120
|
-
if (
|
|
121
|
-
|
|
104
|
+
// Check if pasted text is an image path
|
|
105
|
+
if (trimmed.match(/\.(png|jpe?g|webp|gif|bmp)$/i)) {
|
|
106
|
+
processPastedFilePath(trimmed, usedImageNames)
|
|
122
107
|
.then((imgRes) => {
|
|
123
|
-
if (imgRes && !
|
|
108
|
+
if (imgRes && !disposed) {
|
|
124
109
|
const tag = `[${imgRes.fileName} ${imgRes.sizeStr}]`;
|
|
125
110
|
imageAttachments.push({
|
|
126
111
|
tag,
|
|
@@ -129,82 +114,492 @@ export function askPrompt(opts = {}) {
|
|
|
129
114
|
sizeStr: imgRes.sizeStr
|
|
130
115
|
});
|
|
131
116
|
usedImageNames.add(imgRes.fileName);
|
|
132
|
-
|
|
117
|
+
input = input.slice(0, cursorPos) + tag + ' ' + input.slice(cursorPos);
|
|
118
|
+
cursorPos += tag.length + 1;
|
|
119
|
+
render();
|
|
133
120
|
}
|
|
134
121
|
})
|
|
135
122
|
.catch(() => { });
|
|
123
|
+
return;
|
|
136
124
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
const id = nextPasteId++;
|
|
126
|
+
const tag = `[Pasted text #${id} +${lines.length} lines]`;
|
|
127
|
+
pastedBlocks.push({ id, lines: lines.length, text, tag });
|
|
128
|
+
input = input.slice(0, cursorPos) + tag + input.slice(cursorPos);
|
|
129
|
+
cursorPos += tag.length;
|
|
130
|
+
render();
|
|
141
131
|
}
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
function getDropdownItems() {
|
|
133
|
+
// 1. Slash commands dropdown (when input starts with /)
|
|
134
|
+
if (input.startsWith('/')) {
|
|
135
|
+
const q = input.trim().toLowerCase();
|
|
136
|
+
const baseList = [
|
|
137
|
+
...SLASH_COMMANDS.map((c) => ({ label: c.cmd, desc: c.desc, replacement: c.cmd })),
|
|
138
|
+
...customCommandsList.map((c) => ({ label: c.cmd, desc: `(custom) ${c.desc}`, replacement: c.cmd }))
|
|
139
|
+
];
|
|
140
|
+
const filtered = baseList.filter((c) => c.label.toLowerCase().startsWith(q) || q === '/');
|
|
141
|
+
return filtered.map((item) => ({
|
|
142
|
+
label: item.label,
|
|
143
|
+
desc: item.desc,
|
|
144
|
+
replacement: item.replacement,
|
|
145
|
+
replaceStart: 0,
|
|
146
|
+
replaceLen: input.length
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
// 2. @ file mention autocomplete
|
|
150
|
+
const beforeCursor = input.slice(0, cursorPos);
|
|
151
|
+
const atMatch = beforeCursor.match(/@([a-zA-Z0-9_\-./]*)$/);
|
|
152
|
+
if (atMatch && availableFiles.length > 0) {
|
|
153
|
+
const query = atMatch[1].toLowerCase();
|
|
154
|
+
const replaceStart = cursorPos - atMatch[0].length;
|
|
155
|
+
const replaceLen = atMatch[0].length;
|
|
156
|
+
const filtered = availableFiles
|
|
157
|
+
.filter((f) => f.toLowerCase().includes(query) || query === '')
|
|
158
|
+
.slice(0, 15);
|
|
159
|
+
return filtered.map((file) => ({
|
|
160
|
+
label: `@${file}`,
|
|
161
|
+
desc: file.endsWith('/') ? 'Directory' : 'File',
|
|
162
|
+
replacement: `@${file}${file.endsWith('/') ? '' : ' '}`,
|
|
163
|
+
replaceStart,
|
|
164
|
+
replaceLen
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
return [];
|
|
144
168
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
169
|
+
function render() {
|
|
170
|
+
if (disposed)
|
|
171
|
+
return;
|
|
172
|
+
const cols = Math.max(20, process.stdout.columns || 40);
|
|
173
|
+
const rows = Math.max(8, process.stdout.rows || 20);
|
|
174
|
+
const items = getDropdownItems();
|
|
175
|
+
const dropdownLines = [];
|
|
176
|
+
if (items.length > 0) {
|
|
177
|
+
if (selectedIndex >= items.length)
|
|
178
|
+
selectedIndex = items.length - 1;
|
|
179
|
+
if (selectedIndex < 0)
|
|
180
|
+
selectedIndex = 0;
|
|
181
|
+
const pageSize = Math.min(4, Math.max(2, Math.floor(rows / 4)));
|
|
182
|
+
const innerBoxWidth = Math.max(14, Math.min(cols - 6, 50));
|
|
183
|
+
const total = items.length;
|
|
184
|
+
let startIndex = 0;
|
|
185
|
+
if (total > pageSize) {
|
|
186
|
+
if (selectedIndex >= pageSize) {
|
|
187
|
+
startIndex = Math.min(selectedIndex - pageSize + 1, total - pageSize);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const endIndex = Math.min(startIndex + pageSize, total);
|
|
191
|
+
const hasMoreUp = startIndex > 0;
|
|
192
|
+
const hasMoreDown = endIndex < total;
|
|
193
|
+
let topBorderStr = '─'.repeat(innerBoxWidth);
|
|
194
|
+
if (hasMoreUp) {
|
|
195
|
+
const mid = Math.max(0, Math.floor(innerBoxWidth / 2) - 2);
|
|
196
|
+
topBorderStr = '─'.repeat(mid) + ' ▲ ' + '─'.repeat(Math.max(0, innerBoxWidth - mid - 3));
|
|
197
|
+
}
|
|
198
|
+
let botBorderStr = '─'.repeat(innerBoxWidth);
|
|
199
|
+
if (hasMoreDown) {
|
|
200
|
+
const mid = Math.max(0, Math.floor(innerBoxWidth / 2) - 2);
|
|
201
|
+
botBorderStr = '─'.repeat(mid) + ' ▼ ' + '─'.repeat(Math.max(0, innerBoxWidth - mid - 3));
|
|
202
|
+
}
|
|
203
|
+
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╭' + topBorderStr + '╮'));
|
|
204
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
205
|
+
const item = items[i];
|
|
206
|
+
const isSelected = i === selectedIndex;
|
|
207
|
+
const pointer = isSelected ? '› ' : ' ';
|
|
208
|
+
const availWidth = innerBoxWidth - 2;
|
|
209
|
+
let row = '';
|
|
210
|
+
if (availWidth < 22) {
|
|
211
|
+
const labelStr = item.label.length > availWidth
|
|
212
|
+
? item.label.slice(0, availWidth - 1) + '…'
|
|
213
|
+
: item.label.padEnd(availWidth);
|
|
214
|
+
const plain = pointer + labelStr;
|
|
215
|
+
row = isSelected ? theme.badgeFn(plain) : pointer + theme.boldFn(labelStr);
|
|
163
216
|
}
|
|
164
217
|
else {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
218
|
+
const labelMax = Math.min(14, Math.floor(availWidth * 0.42));
|
|
219
|
+
const labelStr = item.label.length > labelMax
|
|
220
|
+
? item.label.slice(0, labelMax - 1) + '…'
|
|
221
|
+
: item.label.padEnd(labelMax);
|
|
222
|
+
const descMax = availWidth - labelMax - 1;
|
|
223
|
+
const descStr = item.desc.length > descMax
|
|
224
|
+
? item.desc.slice(0, descMax - 1) + '…'
|
|
225
|
+
: item.desc.padEnd(descMax);
|
|
226
|
+
const plain = `${pointer}${labelStr} ${descStr}`;
|
|
227
|
+
if (isSelected) {
|
|
228
|
+
row = theme.badgeFn(plain);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
row = `${pointer}${theme.boldFn(labelStr)} ${pc.gray(descStr)}`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const currentLen = stripAnsi(row).length;
|
|
235
|
+
if (currentLen < innerBoxWidth) {
|
|
236
|
+
row += ' '.repeat(innerBoxWidth - currentLen);
|
|
168
237
|
}
|
|
238
|
+
else if (currentLen > innerBoxWidth) {
|
|
239
|
+
row = row.slice(0, innerBoxWidth);
|
|
240
|
+
}
|
|
241
|
+
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('│') + row + pc.dim('│'));
|
|
169
242
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
243
|
+
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╰' + botBorderStr + '╯'));
|
|
244
|
+
}
|
|
245
|
+
// 1. Format and write the Prompt line safely (guaranteed <= cols - 1 chars)
|
|
246
|
+
let inputDisplay = pc.dim('│') + ' ';
|
|
247
|
+
let renderCursorCol = 3;
|
|
248
|
+
if (input.length === 0) {
|
|
249
|
+
const maxPlace = Math.max(10, cols - 6);
|
|
250
|
+
const displayPlace = placeholder.length > maxPlace ? placeholder.slice(0, maxPlace - 1) + '…' : placeholder;
|
|
251
|
+
inputDisplay += pc.dim(displayPlace);
|
|
252
|
+
renderCursorCol = 3;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
const maxInputLen = Math.max(10, cols - 6);
|
|
256
|
+
if (input.length <= maxInputLen) {
|
|
257
|
+
inputDisplay += formatInputWithBadges(input);
|
|
258
|
+
renderCursorCol = 3 + cursorPos;
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
// Horizontal scrolling to prevent auto-wrapping
|
|
262
|
+
const start = Math.max(0, Math.min(cursorPos - Math.floor(maxInputLen / 2), input.length - maxInputLen));
|
|
263
|
+
const visibleChunk = input.slice(start, start + maxInputLen);
|
|
264
|
+
const prefix = start > 0 ? '…' : '';
|
|
265
|
+
const suffix = start + maxInputLen < input.length ? '…' : '';
|
|
266
|
+
inputDisplay += pc.dim(prefix) + formatInputWithBadges(visibleChunk) + pc.dim(suffix);
|
|
267
|
+
renderCursorCol = 3 + (prefix ? 1 : 0) + (cursorPos - start);
|
|
173
268
|
}
|
|
174
269
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
usedImageNames.add(imgRes.fileName);
|
|
187
|
-
line = tag;
|
|
270
|
+
process.stdout.write(`\r\x1b[2K${inputDisplay}`);
|
|
271
|
+
// 2. Draw dropdown lines below, and clear extra lines from previous render
|
|
272
|
+
const maxDropdowns = Math.max(dropdownLines.length, lastRenderedDropdownLines);
|
|
273
|
+
if (maxDropdowns > 0) {
|
|
274
|
+
for (let i = 0; i < maxDropdowns; i++) {
|
|
275
|
+
if (i < dropdownLines.length) {
|
|
276
|
+
process.stdout.write(`\n\x1b[2K${dropdownLines[i]}`);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
process.stdout.write(`\n\x1b[2K`);
|
|
280
|
+
}
|
|
188
281
|
}
|
|
282
|
+
// Move cursor back up to input line (line 0)
|
|
283
|
+
process.stdout.write(`\x1b[${maxDropdowns}A`);
|
|
189
284
|
}
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
285
|
+
// 3. Place cursor precisely
|
|
286
|
+
process.stdout.write(`\r\x1b[${renderCursorCol}C`);
|
|
287
|
+
lastRenderedDropdownLines = dropdownLines.length;
|
|
288
|
+
}
|
|
289
|
+
render();
|
|
290
|
+
function cleanup() {
|
|
291
|
+
disposed = true;
|
|
292
|
+
if (process.stdout.isTTY) {
|
|
293
|
+
process.stdout.write('\x1b[?2004l');
|
|
294
|
+
}
|
|
295
|
+
process.stdin.removeListener('data', onData);
|
|
296
|
+
if (process.stdin.isTTY) {
|
|
297
|
+
process.stdin.setRawMode(false);
|
|
298
|
+
}
|
|
299
|
+
process.stdin.pause();
|
|
300
|
+
}
|
|
301
|
+
function clearBoxAndExit(finalInput) {
|
|
302
|
+
if (lastRenderedDropdownLines > 0) {
|
|
303
|
+
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
304
|
+
process.stdout.write(`\n\x1b[2K`);
|
|
305
|
+
}
|
|
306
|
+
process.stdout.write(`\x1b[${lastRenderedDropdownLines}A`);
|
|
307
|
+
lastRenderedDropdownLines = 0;
|
|
194
308
|
}
|
|
195
|
-
|
|
309
|
+
const fullText = expandPastes(finalInput);
|
|
310
|
+
if (fullText.trim()) {
|
|
196
311
|
if (GLOBAL_PROMPT_HISTORY.length === 0 ||
|
|
197
|
-
GLOBAL_PROMPT_HISTORY[GLOBAL_PROMPT_HISTORY.length - 1] !==
|
|
198
|
-
GLOBAL_PROMPT_HISTORY.push(
|
|
312
|
+
GLOBAL_PROMPT_HISTORY[GLOBAL_PROMPT_HISTORY.length - 1] !== fullText.trim()) {
|
|
313
|
+
GLOBAL_PROMPT_HISTORY.push(fullText.trim());
|
|
199
314
|
}
|
|
200
315
|
}
|
|
201
|
-
|
|
202
|
-
resolve(expanded);
|
|
203
|
-
});
|
|
204
|
-
rl.on('SIGINT', () => {
|
|
316
|
+
process.stdout.write(`\r\x1b[2K${pc.dim('│')} ${formatInputWithBadges(finalInput)}\n\n`);
|
|
205
317
|
cleanup();
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
318
|
+
resolve(fullText);
|
|
319
|
+
}
|
|
320
|
+
function onData(data) {
|
|
321
|
+
if (disposed)
|
|
322
|
+
return;
|
|
323
|
+
const str = data.toString('utf-8');
|
|
324
|
+
// Bracketed paste mode handling
|
|
325
|
+
if (str.includes('\x1b[200~')) {
|
|
326
|
+
inBracketedPaste = true;
|
|
327
|
+
bracketedBuffer = '';
|
|
328
|
+
const afterTag = str.split('\x1b[200~')[1] || '';
|
|
329
|
+
if (afterTag.includes('\x1b[201~')) {
|
|
330
|
+
const content = afterTag.split('\x1b[201~')[0];
|
|
331
|
+
inBracketedPaste = false;
|
|
332
|
+
handlePaste(content);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
bracketedBuffer += afterTag;
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (inBracketedPaste) {
|
|
341
|
+
if (str.includes('\x1b[201~')) {
|
|
342
|
+
const beforeTag = str.split('\x1b[201~')[0];
|
|
343
|
+
bracketedBuffer += beforeTag;
|
|
344
|
+
inBracketedPaste = false;
|
|
345
|
+
handlePaste(bracketedBuffer);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
bracketedBuffer += str;
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Check for raw multi-character paste with newlines or large size
|
|
354
|
+
if (str.length > 1 && (str.includes('\n') || str.includes('\r') || str.length > 80)) {
|
|
355
|
+
handlePaste(str);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
// Ctrl+C / SIGINT
|
|
359
|
+
if (str === '\x03') {
|
|
360
|
+
if (lastRenderedDropdownLines > 0) {
|
|
361
|
+
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
362
|
+
process.stdout.write(`\n\x1b[2K`);
|
|
363
|
+
}
|
|
364
|
+
process.stdout.write(`\x1b[${lastRenderedDropdownLines}A`);
|
|
365
|
+
lastRenderedDropdownLines = 0;
|
|
366
|
+
}
|
|
367
|
+
process.stdout.write(`\r\x1b[2K\n`);
|
|
368
|
+
cleanup();
|
|
369
|
+
resolve('__CANCEL__');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const items = getDropdownItems();
|
|
373
|
+
// Enter / Return
|
|
374
|
+
if (str === '\r' ||
|
|
375
|
+
str === '\n' ||
|
|
376
|
+
(str.length === 1 && (str.charCodeAt(0) === 13 || str.charCodeAt(0) === 10))) {
|
|
377
|
+
// Direct /image command
|
|
378
|
+
if (input.trim() === '/image' ||
|
|
379
|
+
(items.length > 0 &&
|
|
380
|
+
selectedIndex >= 0 &&
|
|
381
|
+
selectedIndex < items.length &&
|
|
382
|
+
items[selectedIndex].label === '/image')) {
|
|
383
|
+
saveClipboardImage('image.png', usedImageNames)
|
|
384
|
+
.then((imgRes) => {
|
|
385
|
+
if (imgRes) {
|
|
386
|
+
const tag = `[${imgRes.fileName} ${imgRes.sizeStr}]`;
|
|
387
|
+
imageAttachments.push({
|
|
388
|
+
tag,
|
|
389
|
+
fileName: imgRes.fileName,
|
|
390
|
+
filePath: imgRes.filePath,
|
|
391
|
+
sizeStr: imgRes.sizeStr
|
|
392
|
+
});
|
|
393
|
+
usedImageNames.add(imgRes.fileName);
|
|
394
|
+
input = `${tag} `;
|
|
395
|
+
cursorPos = input.length;
|
|
396
|
+
render();
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
process.stdout.write(`\r\x1b[2K${pc.yellow('⚠️ No image in clipboard. Take a screenshot first (Win+Shift+S)\n')}`);
|
|
400
|
+
input = '';
|
|
401
|
+
cursorPos = 0;
|
|
402
|
+
render();
|
|
403
|
+
}
|
|
404
|
+
})
|
|
405
|
+
.catch(() => { });
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (items.length > 0 && selectedIndex >= 0 && selectedIndex < items.length) {
|
|
409
|
+
const selected = items[selectedIndex];
|
|
410
|
+
if (input.startsWith('/')) {
|
|
411
|
+
input = selected.replacement;
|
|
412
|
+
clearBoxAndExit(input);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
// @ mention autocomplete on Enter
|
|
417
|
+
input =
|
|
418
|
+
input.slice(0, selected.replaceStart) +
|
|
419
|
+
selected.replacement +
|
|
420
|
+
input.slice(selected.replaceStart + selected.replaceLen);
|
|
421
|
+
cursorPos = selected.replaceStart + selected.replacement.length;
|
|
422
|
+
render();
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
clearBoxAndExit(input);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// Tab
|
|
430
|
+
if (str === '\t' || (str.length === 1 && str.charCodeAt(0) === 9)) {
|
|
431
|
+
if (items.length > 0 && selectedIndex >= 0 && selectedIndex < items.length) {
|
|
432
|
+
const selected = items[selectedIndex];
|
|
433
|
+
input =
|
|
434
|
+
input.slice(0, selected.replaceStart) +
|
|
435
|
+
selected.replacement +
|
|
436
|
+
input.slice(selected.replaceStart + selected.replaceLen);
|
|
437
|
+
cursorPos = selected.replaceStart + selected.replacement.length;
|
|
438
|
+
render();
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
if (lastRenderedDropdownLines > 0) {
|
|
442
|
+
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
443
|
+
process.stdout.write(`\n\x1b[2K`);
|
|
444
|
+
}
|
|
445
|
+
process.stdout.write(`\x1b[${lastRenderedDropdownLines}A`);
|
|
446
|
+
lastRenderedDropdownLines = 0;
|
|
447
|
+
}
|
|
448
|
+
process.stdout.write(`\r\x1b[2K`);
|
|
449
|
+
cleanup();
|
|
450
|
+
resolve(`__TOGGLE_MODE__:${input}`);
|
|
451
|
+
}
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
// Up Arrow
|
|
455
|
+
if (str === '\x1b[A' || str === '\x1bOA') {
|
|
456
|
+
if (items.length > 0) {
|
|
457
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
458
|
+
render();
|
|
459
|
+
}
|
|
460
|
+
else if (GLOBAL_PROMPT_HISTORY.length > 0 && historyIndex > 0) {
|
|
461
|
+
if (historyIndex === GLOBAL_PROMPT_HISTORY.length) {
|
|
462
|
+
tempDraft = input;
|
|
463
|
+
}
|
|
464
|
+
historyIndex--;
|
|
465
|
+
input = GLOBAL_PROMPT_HISTORY[historyIndex];
|
|
466
|
+
cursorPos = input.length;
|
|
467
|
+
render();
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
// Down Arrow
|
|
472
|
+
if (str === '\x1b[B' || str === '\x1bOB') {
|
|
473
|
+
if (items.length > 0) {
|
|
474
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
475
|
+
render();
|
|
476
|
+
}
|
|
477
|
+
else if (historyIndex < GLOBAL_PROMPT_HISTORY.length) {
|
|
478
|
+
historyIndex++;
|
|
479
|
+
if (historyIndex === GLOBAL_PROMPT_HISTORY.length) {
|
|
480
|
+
input = tempDraft;
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
input = GLOBAL_PROMPT_HISTORY[historyIndex];
|
|
484
|
+
}
|
|
485
|
+
cursorPos = input.length;
|
|
486
|
+
render();
|
|
487
|
+
}
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
// Left Arrow
|
|
491
|
+
if (str === '\x1b[D' || str === '\x1bOD') {
|
|
492
|
+
if (cursorPos > 0) {
|
|
493
|
+
const spans = getTagSpans();
|
|
494
|
+
const endingSpan = spans.find((s) => s.end === cursorPos);
|
|
495
|
+
if (endingSpan) {
|
|
496
|
+
cursorPos = endingSpan.start;
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
cursorPos--;
|
|
500
|
+
const inside = spans.find((s) => cursorPos > s.start && cursorPos < s.end);
|
|
501
|
+
if (inside)
|
|
502
|
+
cursorPos = inside.start;
|
|
503
|
+
}
|
|
504
|
+
render();
|
|
505
|
+
}
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
// Right Arrow
|
|
509
|
+
if (str === '\x1b[C' || str === '\x1bOC') {
|
|
510
|
+
if (cursorPos < input.length) {
|
|
511
|
+
const spans = getTagSpans();
|
|
512
|
+
const startingSpan = spans.find((s) => s.start === cursorPos);
|
|
513
|
+
if (startingSpan) {
|
|
514
|
+
cursorPos = startingSpan.end;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
cursorPos++;
|
|
518
|
+
const inside = spans.find((s) => cursorPos > s.start && cursorPos < s.end);
|
|
519
|
+
if (inside)
|
|
520
|
+
cursorPos = inside.end;
|
|
521
|
+
}
|
|
522
|
+
render();
|
|
523
|
+
}
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
// Home
|
|
527
|
+
if (str === '\x1b[H' || str === '\x1b[1~') {
|
|
528
|
+
cursorPos = 0;
|
|
529
|
+
render();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
// End
|
|
533
|
+
if (str === '\x1b[F' || str === '\x1b[4~') {
|
|
534
|
+
cursorPos = input.length;
|
|
535
|
+
render();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
// Delete key
|
|
539
|
+
if (str === '\x1b[3~') {
|
|
540
|
+
if (cursorPos < input.length) {
|
|
541
|
+
const spans = getTagSpans();
|
|
542
|
+
const startingSpan = spans.find((s) => s.start === cursorPos);
|
|
543
|
+
if (startingSpan) {
|
|
544
|
+
input = input.slice(0, startingSpan.start) + input.slice(startingSpan.end);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
input = input.slice(0, cursorPos) + input.slice(cursorPos + 1);
|
|
548
|
+
}
|
|
549
|
+
selectedIndex = 0;
|
|
550
|
+
render();
|
|
551
|
+
}
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
// Backspace / DEL handling (including Android IME chunks)
|
|
555
|
+
if (str.includes('\x08') || str.includes('\x7f')) {
|
|
556
|
+
for (let i = 0; i < str.length; i++) {
|
|
557
|
+
const ch = str[i];
|
|
558
|
+
if (ch === '\x08' || ch === '\x7f') {
|
|
559
|
+
if (cursorPos > 0) {
|
|
560
|
+
const spans = getTagSpans();
|
|
561
|
+
const endingSpan = spans.find((s) => s.end === cursorPos);
|
|
562
|
+
if (endingSpan) {
|
|
563
|
+
input = input.slice(0, endingSpan.start) + input.slice(endingSpan.end);
|
|
564
|
+
cursorPos = endingSpan.start;
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
input = input.slice(0, cursorPos - 1) + input.slice(cursorPos);
|
|
568
|
+
cursorPos--;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else if (ch.charCodeAt(0) >= 32) {
|
|
573
|
+
const spans = getTagSpans();
|
|
574
|
+
const inside = spans.find((s) => cursorPos > s.start && cursorPos < s.end);
|
|
575
|
+
if (inside)
|
|
576
|
+
cursorPos = inside.end;
|
|
577
|
+
input = input.slice(0, cursorPos) + ch + input.slice(cursorPos);
|
|
578
|
+
cursorPos++;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
selectedIndex = 0;
|
|
582
|
+
render();
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
// Ignore unknown escape sequences
|
|
586
|
+
if (str.startsWith('\x1b')) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
// Normal character input - clean control characters
|
|
590
|
+
const cleanStr = str.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
|
|
591
|
+
if (!cleanStr)
|
|
592
|
+
return;
|
|
593
|
+
const spans = getTagSpans();
|
|
594
|
+
const inside = spans.find((s) => cursorPos > s.start && cursorPos < s.end);
|
|
595
|
+
if (inside) {
|
|
596
|
+
cursorPos = inside.end;
|
|
597
|
+
}
|
|
598
|
+
input = input.slice(0, cursorPos) + cleanStr + input.slice(cursorPos);
|
|
599
|
+
cursorPos += cleanStr.length;
|
|
600
|
+
selectedIndex = 0;
|
|
601
|
+
render();
|
|
602
|
+
}
|
|
603
|
+
process.stdin.on('data', onData);
|
|
209
604
|
});
|
|
210
605
|
}
|
package/dist/cli/server.js
CHANGED
|
@@ -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.
|
|
142
|
+
<div class="footer">devx v1.4.12 • Terminal-Native AI Assistant</div>
|
|
143
143
|
</div>
|
|
144
144
|
</body>
|
|
145
145
|
</html>`;
|
package/dist/mcp/client.js
CHANGED
package/package.json
CHANGED