termux-dev 1.4.6 → 1.4.8
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 +138 -117
- 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.8</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.8'
|
|
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.8 (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.8'),
|
|
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.8'),
|
|
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.8 • 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.8 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.8 — 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.8')
|
|
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
|
@@ -34,32 +34,36 @@ export const SLASH_COMMANDS = [
|
|
|
34
34
|
{ cmd: '/exit', desc: 'Exit devx' }
|
|
35
35
|
];
|
|
36
36
|
const GLOBAL_PROMPT_HISTORY = [];
|
|
37
|
+
function stripAnsi(str) {
|
|
38
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
|
39
|
+
}
|
|
37
40
|
export function askPrompt(opts = {}) {
|
|
38
41
|
return new Promise((resolve) => {
|
|
39
42
|
process.stdin.resume();
|
|
40
|
-
const msg = opts.message
|
|
43
|
+
const msg = opts.message
|
|
44
|
+
? `${opts.message} ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`
|
|
45
|
+
: `Ask anything... ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`;
|
|
41
46
|
const placeholder = opts.placeholder || 'Describe a task, @file, /help, paste image, or press Tab to switch mode';
|
|
42
47
|
let input = opts.initialValue || '';
|
|
43
48
|
let cursorPos = input.length;
|
|
44
49
|
let selectedIndex = 0;
|
|
45
|
-
let
|
|
50
|
+
let lastRenderedDropdownLines = 0;
|
|
46
51
|
let disposed = false;
|
|
47
52
|
let historyIndex = GLOBAL_PROMPT_HISTORY.length;
|
|
48
53
|
let tempDraft = '';
|
|
49
54
|
let availableFiles = [];
|
|
50
55
|
let customCommandsList = [];
|
|
51
56
|
// Preload project files and custom slash commands
|
|
52
|
-
scanProjectFiles()
|
|
53
|
-
availableFiles = files
|
|
54
|
-
|
|
55
|
-
CustomCommandManager.listCommands()
|
|
56
|
-
customCommandsList = cmds.map(c => ({ cmd: c.cmd, desc: c.desc }))
|
|
57
|
-
|
|
57
|
+
scanProjectFiles()
|
|
58
|
+
.then((files) => (availableFiles = files))
|
|
59
|
+
.catch(() => { });
|
|
60
|
+
CustomCommandManager.listCommands()
|
|
61
|
+
.then((cmds) => (customCommandsList = cmds.map((c) => ({ cmd: c.cmd, desc: c.desc }))))
|
|
62
|
+
.catch(() => { });
|
|
58
63
|
const pastes = [];
|
|
59
64
|
const imageAttachments = [];
|
|
60
65
|
const usedImageNames = new Set();
|
|
61
66
|
const theme = getCurrentTheme();
|
|
62
|
-
// Header printed once
|
|
63
67
|
console.log(theme.colorFn('◆') + ' ' + pc.bold(msg));
|
|
64
68
|
if (process.stdin.isTTY) {
|
|
65
69
|
process.stdin.setRawMode(true);
|
|
@@ -68,10 +72,11 @@ export function askPrompt(opts = {}) {
|
|
|
68
72
|
process.stdout.write('\x1b[?2004h');
|
|
69
73
|
}
|
|
70
74
|
function getDropdownItems() {
|
|
75
|
+
// 1. Theme command autocomplete
|
|
71
76
|
if (input.startsWith('/theme ') || input.startsWith('/themes ') || input === '/theme') {
|
|
72
77
|
const afterCmd = input.replace(/^\/(?:theme|themes)\s*/i, '').trim().toLowerCase();
|
|
73
78
|
const themes = listThemes();
|
|
74
|
-
const matched = themes.filter(t => !afterCmd ||
|
|
79
|
+
const matched = themes.filter((t) => !afterCmd ||
|
|
75
80
|
t.id.toLowerCase().startsWith(afterCmd) ||
|
|
76
81
|
t.name.toLowerCase().includes(afterCmd));
|
|
77
82
|
const list = [];
|
|
@@ -95,16 +100,17 @@ export function askPrompt(opts = {}) {
|
|
|
95
100
|
}
|
|
96
101
|
return list;
|
|
97
102
|
}
|
|
103
|
+
// 2. All Slash commands autocomplete
|
|
98
104
|
if (input.startsWith('/')) {
|
|
99
105
|
const q = input.trim().toLowerCase();
|
|
100
106
|
const baseList = [...SLASH_COMMANDS];
|
|
101
107
|
for (const cc of customCommandsList) {
|
|
102
|
-
if (!baseList.some(b => b.cmd === cc.cmd)) {
|
|
108
|
+
if (!baseList.some((b) => b.cmd === cc.cmd)) {
|
|
103
109
|
baseList.push({ cmd: cc.cmd, desc: cc.desc });
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
|
-
const filtered = baseList.filter(c => c.cmd.toLowerCase().startsWith(q) || q === '/');
|
|
107
|
-
return filtered.map(c => ({
|
|
112
|
+
const filtered = baseList.filter((c) => c.cmd.toLowerCase().startsWith(q) || q === '/');
|
|
113
|
+
return filtered.map((c) => ({
|
|
108
114
|
label: c.cmd,
|
|
109
115
|
desc: c.desc,
|
|
110
116
|
replacement: c.cmd,
|
|
@@ -112,15 +118,15 @@ export function askPrompt(opts = {}) {
|
|
|
112
118
|
replaceLen: input.length
|
|
113
119
|
}));
|
|
114
120
|
}
|
|
115
|
-
//
|
|
121
|
+
// 3. @file autocomplete at cursor position
|
|
116
122
|
const beforeCursor = input.slice(0, cursorPos);
|
|
117
123
|
const atMatch = beforeCursor.match(/@([a-zA-Z0-9_\-./]*)$/);
|
|
118
124
|
if (atMatch && availableFiles.length > 0) {
|
|
119
125
|
const query = atMatch[1].toLowerCase();
|
|
120
126
|
const matched = availableFiles
|
|
121
|
-
.filter(f => f.toLowerCase().includes(query) || query === '')
|
|
127
|
+
.filter((f) => f.toLowerCase().includes(query) || query === '')
|
|
122
128
|
.slice(0, 8);
|
|
123
|
-
return matched.map(f => ({
|
|
129
|
+
return matched.map((f) => ({
|
|
124
130
|
label: `@${f}`,
|
|
125
131
|
desc: f.endsWith('/') ? 'directory' : 'file',
|
|
126
132
|
replacement: `@${f}${f.endsWith('/') ? '' : ' '}`,
|
|
@@ -152,47 +158,13 @@ export function askPrompt(opts = {}) {
|
|
|
152
158
|
}
|
|
153
159
|
return result;
|
|
154
160
|
}
|
|
155
|
-
function stripAnsi(str) {
|
|
156
|
-
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
|
157
|
-
}
|
|
158
|
-
// Helper: clear all previously rendered lines and move cursor to line 0
|
|
159
|
-
function clearPreviousRender() {
|
|
160
|
-
if (lastTotalLines > 1) {
|
|
161
|
-
process.stdout.write(`\x1b[${lastTotalLines - 1}A`);
|
|
162
|
-
}
|
|
163
|
-
for (let i = 0; i < lastTotalLines; i++) {
|
|
164
|
-
process.stdout.write(`\r\x1b[2K`);
|
|
165
|
-
if (i < lastTotalLines - 1)
|
|
166
|
-
process.stdout.write('\n');
|
|
167
|
-
}
|
|
168
|
-
if (lastTotalLines > 1) {
|
|
169
|
-
process.stdout.write(`\x1b[${lastTotalLines - 1}A`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
161
|
function render() {
|
|
173
162
|
if (disposed)
|
|
174
163
|
return;
|
|
175
|
-
const items = getDropdownItems();
|
|
176
164
|
const cols = Math.max(28, process.stdout.columns || 80);
|
|
177
165
|
const rows = Math.max(8, process.stdout.rows || 24);
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
const allLines = [];
|
|
181
|
-
let inputLineCount = 0;
|
|
182
|
-
if (input.length === 0) {
|
|
183
|
-
const displayPlace = placeholder.length > maxPerLine ? placeholder.slice(0, maxPerLine - 1) + '…' : placeholder;
|
|
184
|
-
allLines.push(pc.dim('│') + ' ' + pc.dim(displayPlace));
|
|
185
|
-
inputLineCount = 1;
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
let pos = 0;
|
|
189
|
-
while (pos < input.length) {
|
|
190
|
-
const chunk = input.slice(pos, pos + maxPerLine);
|
|
191
|
-
allLines.push(pc.dim('│') + ' ' + formatInputWithBadges(chunk));
|
|
192
|
-
pos += maxPerLine;
|
|
193
|
-
}
|
|
194
|
-
inputLineCount = allLines.length;
|
|
195
|
-
}
|
|
166
|
+
const items = getDropdownItems();
|
|
167
|
+
const dropdownLines = [];
|
|
196
168
|
if (items.length > 0) {
|
|
197
169
|
if (selectedIndex >= items.length)
|
|
198
170
|
selectedIndex = 0;
|
|
@@ -223,7 +195,7 @@ export function askPrompt(opts = {}) {
|
|
|
223
195
|
const mid = Math.max(0, Math.floor(innerBoxWidth / 2) - 2);
|
|
224
196
|
botBorderStr = '─'.repeat(mid) + ' ▼ ' + '─'.repeat(Math.max(0, innerBoxWidth - mid - 3));
|
|
225
197
|
}
|
|
226
|
-
|
|
198
|
+
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╭' + topBorderStr + '╮'));
|
|
227
199
|
for (let i = startIndex; i < endIndex; i++) {
|
|
228
200
|
const item = items[i];
|
|
229
201
|
const isSelected = i === selectedIndex;
|
|
@@ -231,15 +203,21 @@ export function askPrompt(opts = {}) {
|
|
|
231
203
|
const availWidth = innerBoxWidth - 2;
|
|
232
204
|
let row = '';
|
|
233
205
|
if (availWidth < 20) {
|
|
234
|
-
const labelStr = item.label.length > availWidth
|
|
206
|
+
const labelStr = item.label.length > availWidth
|
|
207
|
+
? item.label.slice(0, availWidth - 1) + '…'
|
|
208
|
+
: item.label.padEnd(availWidth);
|
|
235
209
|
const plain = pointer + labelStr;
|
|
236
|
-
row = isSelected ? theme.badgeFn(plain) :
|
|
210
|
+
row = isSelected ? theme.badgeFn(plain) : pointer + theme.boldFn(labelStr);
|
|
237
211
|
}
|
|
238
212
|
else {
|
|
239
213
|
const labelMax = Math.min(15, Math.floor(availWidth * 0.45));
|
|
240
|
-
const labelStr = item.label.length > labelMax
|
|
214
|
+
const labelStr = item.label.length > labelMax
|
|
215
|
+
? item.label.slice(0, labelMax - 1) + '…'
|
|
216
|
+
: item.label.padEnd(labelMax);
|
|
241
217
|
const descMax = availWidth - labelMax - 1;
|
|
242
|
-
const descStr = item.desc.length > descMax
|
|
218
|
+
const descStr = item.desc.length > descMax
|
|
219
|
+
? item.desc.slice(0, descMax - 1) + '…'
|
|
220
|
+
: item.desc.padEnd(descMax);
|
|
243
221
|
const plain = `${pointer}${labelStr} ${descStr}`;
|
|
244
222
|
if (isSelected) {
|
|
245
223
|
row = theme.badgeFn(plain);
|
|
@@ -252,33 +230,43 @@ export function askPrompt(opts = {}) {
|
|
|
252
230
|
if (currentLen < innerBoxWidth) {
|
|
253
231
|
row += ' '.repeat(innerBoxWidth - currentLen);
|
|
254
232
|
}
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
allLines.push(pc.dim('│') + ' ' + pc.dim('╰' + botBorderStr + '╯'));
|
|
258
|
-
}
|
|
259
|
-
clearPreviousRender();
|
|
260
|
-
const maxLinesToWrite = Math.max(allLines.length, lastTotalLines);
|
|
261
|
-
for (let i = 0; i < maxLinesToWrite; i++) {
|
|
262
|
-
process.stdout.write('\r\x1b[2K');
|
|
263
|
-
if (i < allLines.length) {
|
|
264
|
-
process.stdout.write(allLines[i]);
|
|
265
|
-
}
|
|
266
|
-
if (i < maxLinesToWrite - 1) {
|
|
267
|
-
process.stdout.write('\n');
|
|
233
|
+
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('│') + row + pc.dim('│'));
|
|
268
234
|
}
|
|
235
|
+
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╰' + botBorderStr + '╯'));
|
|
236
|
+
}
|
|
237
|
+
// 1. Move cursor up if previous dropdown had lines
|
|
238
|
+
if (lastRenderedDropdownLines > 0) {
|
|
239
|
+
process.stdout.write(`\x1b[${lastRenderedDropdownLines}A`);
|
|
269
240
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
241
|
+
// 2. Clear line 0 and write prompt line
|
|
242
|
+
let inputDisplay = pc.dim('│') + ' ';
|
|
243
|
+
if (input.length === 0) {
|
|
244
|
+
const maxPlace = Math.max(12, cols - 8);
|
|
245
|
+
const displayPlace = placeholder.length > maxPlace ? placeholder.slice(0, maxPlace - 1) + '…' : placeholder;
|
|
246
|
+
inputDisplay += pc.dim(displayPlace);
|
|
276
247
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
248
|
+
else {
|
|
249
|
+
inputDisplay += formatInputWithBadges(input);
|
|
250
|
+
}
|
|
251
|
+
process.stdout.write(`\r\x1b[2K${inputDisplay}`);
|
|
252
|
+
// 3. Draw new dropdown lines below, and clear extra lines from previous render
|
|
253
|
+
const maxDropdowns = Math.max(dropdownLines.length, lastRenderedDropdownLines);
|
|
254
|
+
if (maxDropdowns > 0) {
|
|
255
|
+
for (let i = 0; i < maxDropdowns; i++) {
|
|
256
|
+
if (i < dropdownLines.length) {
|
|
257
|
+
process.stdout.write(`\n\x1b[2K${dropdownLines[i]}`);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
process.stdout.write(`\n\x1b[2K`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Move cursor back up to input line (line 0)
|
|
264
|
+
process.stdout.write(`\x1b[${maxDropdowns}A`);
|
|
280
265
|
}
|
|
281
|
-
|
|
266
|
+
// 4. Place cursor at column 3 + cursorPos
|
|
267
|
+
const cursorCol = 3 + cursorPos;
|
|
268
|
+
process.stdout.write(`\r\x1b[${cursorCol}C`);
|
|
269
|
+
lastRenderedDropdownLines = dropdownLines.length;
|
|
282
270
|
}
|
|
283
271
|
render();
|
|
284
272
|
function cleanup() {
|
|
@@ -293,27 +281,31 @@ export function askPrompt(opts = {}) {
|
|
|
293
281
|
process.stdin.pause();
|
|
294
282
|
}
|
|
295
283
|
function clearBoxAndExit(finalInput) {
|
|
296
|
-
|
|
297
|
-
|
|
284
|
+
if (lastRenderedDropdownLines > 0) {
|
|
285
|
+
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
286
|
+
process.stdout.write(`\n\x1b[2K`);
|
|
287
|
+
}
|
|
288
|
+
process.stdout.write(`\x1b[${lastRenderedDropdownLines}A`);
|
|
289
|
+
lastRenderedDropdownLines = 0;
|
|
290
|
+
}
|
|
298
291
|
const fullText = expandPastes(finalInput);
|
|
299
292
|
if (fullText.trim()) {
|
|
300
|
-
if (GLOBAL_PROMPT_HISTORY.length === 0 ||
|
|
293
|
+
if (GLOBAL_PROMPT_HISTORY.length === 0 ||
|
|
294
|
+
GLOBAL_PROMPT_HISTORY[GLOBAL_PROMPT_HISTORY.length - 1] !== fullText.trim()) {
|
|
301
295
|
GLOBAL_PROMPT_HISTORY.push(fullText.trim());
|
|
302
296
|
}
|
|
303
297
|
}
|
|
304
|
-
|
|
305
|
-
process.stdout.write(`${pc.dim('│')} ${formatInputWithBadges(lines[0])}\n`);
|
|
306
|
-
for (let i = 1; i < lines.length; i++) {
|
|
307
|
-
process.stdout.write(pc.dim('│') + ' ' + lines[i] + '\n');
|
|
308
|
-
}
|
|
309
|
-
process.stdout.write('\n');
|
|
298
|
+
process.stdout.write(`\r\x1b[2K${pc.dim('│')} ${formatInputWithBadges(finalInput)}\n\n`);
|
|
310
299
|
cleanup();
|
|
311
300
|
resolve(fullText);
|
|
312
301
|
}
|
|
313
302
|
async function handlePaste(pastedContent) {
|
|
314
303
|
const cleaned = pastedContent.replace(/\x1b\[200~/g, '').replace(/\x1b\[201~/g, '');
|
|
315
304
|
// Check if pastedContent contains file path(s) to images
|
|
316
|
-
const pathCandidates = cleaned
|
|
305
|
+
const pathCandidates = cleaned
|
|
306
|
+
.split(/[&;\n\r]+/)
|
|
307
|
+
.map((s) => s.trim())
|
|
308
|
+
.filter(Boolean);
|
|
317
309
|
let foundAnyImage = false;
|
|
318
310
|
for (const cand of pathCandidates) {
|
|
319
311
|
const imgRes = await processPastedFilePath(cand, usedImageNames);
|
|
@@ -406,10 +398,12 @@ export function askPrompt(opts = {}) {
|
|
|
406
398
|
handlePaste(str);
|
|
407
399
|
return;
|
|
408
400
|
}
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
401
|
+
// Ctrl+V / Ctrl+P: Clipboard image paste
|
|
402
|
+
if (str === '\x16' ||
|
|
403
|
+
str === '\x10' ||
|
|
404
|
+
(str.length === 1 && (str.charCodeAt(0) === 22 || str.charCodeAt(0) === 16))) {
|
|
405
|
+
saveClipboardImage('image.png', usedImageNames)
|
|
406
|
+
.then((imgRes) => {
|
|
413
407
|
if (imgRes) {
|
|
414
408
|
const tag = `[${imgRes.fileName} ${imgRes.sizeStr}]`;
|
|
415
409
|
imageAttachments.push({
|
|
@@ -423,23 +417,37 @@ export function askPrompt(opts = {}) {
|
|
|
423
417
|
cursorPos += tag.length + 1;
|
|
424
418
|
render();
|
|
425
419
|
}
|
|
426
|
-
})
|
|
420
|
+
})
|
|
421
|
+
.catch(() => { });
|
|
427
422
|
return;
|
|
428
423
|
}
|
|
429
424
|
// Ctrl+C
|
|
430
425
|
if (str === '\x03' || (str.length === 1 && str.charCodeAt(0) === 3)) {
|
|
431
|
-
|
|
432
|
-
|
|
426
|
+
if (lastRenderedDropdownLines > 0) {
|
|
427
|
+
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
428
|
+
process.stdout.write(`\n\x1b[2K`);
|
|
429
|
+
}
|
|
430
|
+
process.stdout.write(`\x1b[${lastRenderedDropdownLines}A`);
|
|
431
|
+
lastRenderedDropdownLines = 0;
|
|
432
|
+
}
|
|
433
|
+
process.stdout.write(`\r\x1b[2K\n`);
|
|
433
434
|
cleanup();
|
|
434
435
|
resolve('__CANCEL__');
|
|
435
436
|
return;
|
|
436
437
|
}
|
|
437
438
|
const items = getDropdownItems();
|
|
438
439
|
// Enter / Return
|
|
439
|
-
if (str === '\r' ||
|
|
440
|
+
if (str === '\r' ||
|
|
441
|
+
str === '\n' ||
|
|
442
|
+
(str.length === 1 && (str.charCodeAt(0) === 13 || str.charCodeAt(0) === 10))) {
|
|
440
443
|
// Direct /image command
|
|
441
|
-
if (input.trim() === '/image' ||
|
|
442
|
-
|
|
444
|
+
if (input.trim() === '/image' ||
|
|
445
|
+
(items.length > 0 &&
|
|
446
|
+
selectedIndex >= 0 &&
|
|
447
|
+
selectedIndex < items.length &&
|
|
448
|
+
items[selectedIndex].label === '/image')) {
|
|
449
|
+
saveClipboardImage('image.png', usedImageNames)
|
|
450
|
+
.then((imgRes) => {
|
|
443
451
|
if (imgRes) {
|
|
444
452
|
const tag = `[${imgRes.fileName} ${imgRes.sizeStr}]`;
|
|
445
453
|
imageAttachments.push({
|
|
@@ -459,7 +467,8 @@ export function askPrompt(opts = {}) {
|
|
|
459
467
|
cursorPos = 0;
|
|
460
468
|
render();
|
|
461
469
|
}
|
|
462
|
-
})
|
|
470
|
+
})
|
|
471
|
+
.catch(() => { });
|
|
463
472
|
return;
|
|
464
473
|
}
|
|
465
474
|
if (items.length > 0 && selectedIndex >= 0 && selectedIndex < items.length) {
|
|
@@ -471,7 +480,10 @@ export function askPrompt(opts = {}) {
|
|
|
471
480
|
}
|
|
472
481
|
else {
|
|
473
482
|
// @ mention autocomplete on Enter
|
|
474
|
-
input =
|
|
483
|
+
input =
|
|
484
|
+
input.slice(0, selected.replaceStart) +
|
|
485
|
+
selected.replacement +
|
|
486
|
+
input.slice(selected.replaceStart + selected.replaceLen);
|
|
475
487
|
cursorPos = selected.replaceStart + selected.replacement.length;
|
|
476
488
|
render();
|
|
477
489
|
return;
|
|
@@ -484,13 +496,22 @@ export function askPrompt(opts = {}) {
|
|
|
484
496
|
if (str === '\t' || (str.length === 1 && str.charCodeAt(0) === 9)) {
|
|
485
497
|
if (items.length > 0 && selectedIndex >= 0 && selectedIndex < items.length) {
|
|
486
498
|
const selected = items[selectedIndex];
|
|
487
|
-
input =
|
|
499
|
+
input =
|
|
500
|
+
input.slice(0, selected.replaceStart) +
|
|
501
|
+
selected.replacement +
|
|
502
|
+
input.slice(selected.replaceStart + selected.replaceLen);
|
|
488
503
|
cursorPos = selected.replaceStart + selected.replacement.length;
|
|
489
504
|
render();
|
|
490
505
|
}
|
|
491
506
|
else {
|
|
492
|
-
|
|
493
|
-
|
|
507
|
+
if (lastRenderedDropdownLines > 0) {
|
|
508
|
+
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
509
|
+
process.stdout.write(`\n\x1b[2K`);
|
|
510
|
+
}
|
|
511
|
+
process.stdout.write(`\x1b[${lastRenderedDropdownLines}A`);
|
|
512
|
+
lastRenderedDropdownLines = 0;
|
|
513
|
+
}
|
|
514
|
+
process.stdout.write(`\r\x1b[2K`);
|
|
494
515
|
cleanup();
|
|
495
516
|
resolve(`__TOGGLE_MODE__:${input}`);
|
|
496
517
|
}
|
|
@@ -532,17 +553,17 @@ export function askPrompt(opts = {}) {
|
|
|
532
553
|
}
|
|
533
554
|
return;
|
|
534
555
|
}
|
|
535
|
-
// Left Arrow
|
|
556
|
+
// Left Arrow
|
|
536
557
|
if (str === '\x1b[D' || str === '\x1bOD') {
|
|
537
558
|
if (cursorPos > 0) {
|
|
538
559
|
const spans = getTagSpans();
|
|
539
|
-
const endingSpan = spans.find(s => s.end === cursorPos);
|
|
560
|
+
const endingSpan = spans.find((s) => s.end === cursorPos);
|
|
540
561
|
if (endingSpan) {
|
|
541
562
|
cursorPos = endingSpan.start;
|
|
542
563
|
}
|
|
543
564
|
else {
|
|
544
565
|
cursorPos--;
|
|
545
|
-
const inside = spans.find(s => cursorPos > s.start && cursorPos < s.end);
|
|
566
|
+
const inside = spans.find((s) => cursorPos > s.start && cursorPos < s.end);
|
|
546
567
|
if (inside)
|
|
547
568
|
cursorPos = inside.start;
|
|
548
569
|
}
|
|
@@ -550,17 +571,17 @@ export function askPrompt(opts = {}) {
|
|
|
550
571
|
}
|
|
551
572
|
return;
|
|
552
573
|
}
|
|
553
|
-
// Right Arrow
|
|
574
|
+
// Right Arrow
|
|
554
575
|
if (str === '\x1b[C' || str === '\x1bOC') {
|
|
555
576
|
if (cursorPos < input.length) {
|
|
556
577
|
const spans = getTagSpans();
|
|
557
|
-
const startingSpan = spans.find(s => s.start === cursorPos);
|
|
578
|
+
const startingSpan = spans.find((s) => s.start === cursorPos);
|
|
558
579
|
if (startingSpan) {
|
|
559
580
|
cursorPos = startingSpan.end;
|
|
560
581
|
}
|
|
561
582
|
else {
|
|
562
583
|
cursorPos++;
|
|
563
|
-
const inside = spans.find(s => cursorPos > s.start && cursorPos < s.end);
|
|
584
|
+
const inside = spans.find((s) => cursorPos > s.start && cursorPos < s.end);
|
|
564
585
|
if (inside)
|
|
565
586
|
cursorPos = inside.end;
|
|
566
587
|
}
|
|
@@ -580,11 +601,11 @@ export function askPrompt(opts = {}) {
|
|
|
580
601
|
render();
|
|
581
602
|
return;
|
|
582
603
|
}
|
|
583
|
-
// Delete key
|
|
604
|
+
// Delete key
|
|
584
605
|
if (str === '\x1b[3~') {
|
|
585
606
|
if (cursorPos < input.length) {
|
|
586
607
|
const spans = getTagSpans();
|
|
587
|
-
const startingSpan = spans.find(s => s.start === cursorPos);
|
|
608
|
+
const startingSpan = spans.find((s) => s.start === cursorPos);
|
|
588
609
|
if (startingSpan) {
|
|
589
610
|
input = input.slice(0, startingSpan.start) + input.slice(startingSpan.end);
|
|
590
611
|
}
|
|
@@ -596,14 +617,14 @@ export function askPrompt(opts = {}) {
|
|
|
596
617
|
}
|
|
597
618
|
return;
|
|
598
619
|
}
|
|
599
|
-
// Backspace / DEL handling
|
|
620
|
+
// Backspace / DEL handling
|
|
600
621
|
if (str.includes('\x08') || str.includes('\x7f')) {
|
|
601
622
|
for (let i = 0; i < str.length; i++) {
|
|
602
623
|
const ch = str[i];
|
|
603
624
|
if (ch === '\x08' || ch === '\x7f') {
|
|
604
625
|
if (cursorPos > 0) {
|
|
605
626
|
const spans = getTagSpans();
|
|
606
|
-
const endingSpan = spans.find(s => s.end === cursorPos);
|
|
627
|
+
const endingSpan = spans.find((s) => s.end === cursorPos);
|
|
607
628
|
if (endingSpan) {
|
|
608
629
|
input = input.slice(0, endingSpan.start) + input.slice(endingSpan.end);
|
|
609
630
|
cursorPos = endingSpan.start;
|
|
@@ -616,7 +637,7 @@ export function askPrompt(opts = {}) {
|
|
|
616
637
|
}
|
|
617
638
|
else if (ch.charCodeAt(0) >= 32) {
|
|
618
639
|
const spans = getTagSpans();
|
|
619
|
-
const inside = spans.find(s => cursorPos > s.start && cursorPos < s.end);
|
|
640
|
+
const inside = spans.find((s) => cursorPos > s.start && cursorPos < s.end);
|
|
620
641
|
if (inside)
|
|
621
642
|
cursorPos = inside.end;
|
|
622
643
|
input = input.slice(0, cursorPos) + ch + input.slice(cursorPos);
|
|
@@ -636,7 +657,7 @@ export function askPrompt(opts = {}) {
|
|
|
636
657
|
if (!cleanStr)
|
|
637
658
|
return;
|
|
638
659
|
const spans = getTagSpans();
|
|
639
|
-
const inside = spans.find(s => cursorPos > s.start && cursorPos < s.end);
|
|
660
|
+
const inside = spans.find((s) => cursorPos > s.start && cursorPos < s.end);
|
|
640
661
|
if (inside) {
|
|
641
662
|
cursorPos = inside.end;
|
|
642
663
|
}
|
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.8 • Terminal-Native AI Assistant</div>
|
|
143
143
|
</div>
|
|
144
144
|
</body>
|
|
145
145
|
</html>`;
|
package/dist/mcp/client.js
CHANGED
package/package.json
CHANGED