termux-dev 1.4.5 → 1.4.7
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 +128 -614
- 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.7</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.7'
|
|
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.7 (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.7'),
|
|
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.7'),
|
|
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.7 • 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.7 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.7 — 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.7')
|
|
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,7 +1,8 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
1
2
|
import pc from 'picocolors';
|
|
2
3
|
import { scanProjectFiles } from './files.js';
|
|
3
4
|
import { saveClipboardImage, processPastedFilePath } from './clipboard.js';
|
|
4
|
-
import { getCurrentTheme
|
|
5
|
+
import { getCurrentTheme } from './theme.js';
|
|
5
6
|
import { CustomCommandManager } from '../core/commands.js';
|
|
6
7
|
export const SLASH_COMMANDS = [
|
|
7
8
|
{ cmd: '/new', desc: 'Start a new clean chat session' },
|
|
@@ -36,407 +37,110 @@ export const SLASH_COMMANDS = [
|
|
|
36
37
|
const GLOBAL_PROMPT_HISTORY = [];
|
|
37
38
|
export function askPrompt(opts = {}) {
|
|
38
39
|
return new Promise((resolve) => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
let selectedIndex = 0;
|
|
45
|
-
let lastDropdownLines = 0;
|
|
46
|
-
let lastCursorLine = 0;
|
|
47
|
-
let disposed = false;
|
|
48
|
-
let historyIndex = GLOBAL_PROMPT_HISTORY.length;
|
|
49
|
-
let tempDraft = '';
|
|
40
|
+
const msg = opts.message
|
|
41
|
+
? `${opts.message} ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`
|
|
42
|
+
: `Ask anything... ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`;
|
|
43
|
+
const theme = getCurrentTheme();
|
|
44
|
+
console.log(theme.colorFn('◆') + ' ' + pc.bold(msg));
|
|
50
45
|
let availableFiles = [];
|
|
51
46
|
let customCommandsList = [];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
CustomCommandManager.listCommands().then(cmds => {
|
|
57
|
-
customCommandsList = cmds.map(c => ({ cmd: c.cmd, desc: c.desc }));
|
|
58
|
-
}).catch(() => { });
|
|
59
|
-
const pastes = [];
|
|
47
|
+
scanProjectFiles().then((f) => (availableFiles = f)).catch(() => { });
|
|
48
|
+
CustomCommandManager.listCommands()
|
|
49
|
+
.then((c) => (customCommandsList = c.map((x) => ({ cmd: x.cmd, desc: x.desc }))))
|
|
50
|
+
.catch(() => { });
|
|
60
51
|
const imageAttachments = [];
|
|
61
52
|
const usedImageNames = new Set();
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const matched = themes.filter(t => !afterCmd ||
|
|
76
|
-
t.id.toLowerCase().startsWith(afterCmd) ||
|
|
77
|
-
t.name.toLowerCase().includes(afterCmd));
|
|
78
|
-
const list = [];
|
|
79
|
-
if (!afterCmd || '/theme'.startsWith(input.trim().toLowerCase())) {
|
|
80
|
-
list.push({
|
|
81
|
-
label: '/theme',
|
|
82
|
-
desc: 'Interactive UI theme picker menu',
|
|
83
|
-
replacement: '/theme',
|
|
84
|
-
replaceStart: 0,
|
|
85
|
-
replaceLen: input.length
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
for (const t of matched) {
|
|
89
|
-
list.push({
|
|
90
|
-
label: `/theme ${t.id}`,
|
|
91
|
-
desc: `${t.emoji} ${t.name} (${t.desc})`,
|
|
92
|
-
replacement: `/theme ${t.id}`,
|
|
93
|
-
replaceStart: 0,
|
|
94
|
-
replaceLen: input.length
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return list;
|
|
98
|
-
}
|
|
99
|
-
if (input.startsWith('/')) {
|
|
100
|
-
const q = input.trim().toLowerCase();
|
|
101
|
-
const baseList = [...SLASH_COMMANDS];
|
|
102
|
-
for (const cc of customCommandsList) {
|
|
103
|
-
if (!baseList.some(b => b.cmd === cc.cmd)) {
|
|
104
|
-
baseList.push({ cmd: cc.cmd, desc: cc.desc });
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const filtered = baseList.filter(c => c.cmd.toLowerCase().startsWith(q) || q === '/');
|
|
108
|
-
return filtered.map(c => ({
|
|
109
|
-
label: c.cmd,
|
|
110
|
-
desc: c.desc,
|
|
111
|
-
replacement: c.cmd,
|
|
112
|
-
replaceStart: 0,
|
|
113
|
-
replaceLen: input.length
|
|
114
|
-
}));
|
|
115
|
-
}
|
|
116
|
-
// Check for @ mention at cursor position
|
|
117
|
-
const beforeCursor = input.slice(0, cursorPos);
|
|
118
|
-
const atMatch = beforeCursor.match(/@([a-zA-Z0-9_\-./]*)$/);
|
|
53
|
+
const completer = (line) => {
|
|
54
|
+
const allCmds = [
|
|
55
|
+
...SLASH_COMMANDS.map((c) => c.cmd),
|
|
56
|
+
...customCommandsList.map((c) => c.cmd)
|
|
57
|
+
];
|
|
58
|
+
// 1. Slash commands autocomplete
|
|
59
|
+
if (line.startsWith('/')) {
|
|
60
|
+
const q = line.trim().toLowerCase();
|
|
61
|
+
const hits = allCmds.filter((cmd) => cmd.toLowerCase().startsWith(q));
|
|
62
|
+
return [hits.length ? hits : allCmds, line];
|
|
63
|
+
}
|
|
64
|
+
// 2. @file autocomplete
|
|
65
|
+
const atMatch = line.match(/@([a-zA-Z0-9_\-./]*)$/);
|
|
119
66
|
if (atMatch && availableFiles.length > 0) {
|
|
120
67
|
const query = atMatch[1].toLowerCase();
|
|
121
|
-
const
|
|
122
|
-
.filter(f => f.toLowerCase().includes(query) || query === '')
|
|
123
|
-
.slice(0,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
for (const img of imageAttachments) {
|
|
141
|
-
const styled = pc.bold(pc.magenta(img.tag));
|
|
142
|
-
formatted = formatted.split(img.tag).join(styled);
|
|
143
|
-
}
|
|
144
|
-
return formatted;
|
|
145
|
-
}
|
|
146
|
-
function expandPastes(rawText) {
|
|
147
|
-
let result = rawText;
|
|
148
|
-
for (const p of pastes) {
|
|
149
|
-
result = result.split(p.tag).join(p.content);
|
|
150
|
-
}
|
|
151
|
-
for (const img of imageAttachments) {
|
|
152
|
-
result = result.split(img.tag).join(`@${img.filePath.replace(/\\/g, '/')} `);
|
|
153
|
-
}
|
|
154
|
-
return result;
|
|
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();
|
|
155
86
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const total = items.length;
|
|
178
|
-
let startIndex = 0;
|
|
179
|
-
if (total > pageSize) {
|
|
180
|
-
if (selectedIndex >= pageSize) {
|
|
181
|
-
startIndex = Math.min(selectedIndex - pageSize + 1, total - pageSize);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const endIndex = Math.min(startIndex + pageSize, total);
|
|
185
|
-
const hasMoreUp = startIndex > 0;
|
|
186
|
-
const hasMoreDown = endIndex < total;
|
|
187
|
-
let topBorderStr = '─'.repeat(innerBoxWidth);
|
|
188
|
-
if (hasMoreUp) {
|
|
189
|
-
const mid = Math.max(0, Math.floor(innerBoxWidth / 2) - 2);
|
|
190
|
-
topBorderStr = '─'.repeat(mid) + ' ▲ ' + '─'.repeat(Math.max(0, innerBoxWidth - mid - 3));
|
|
191
|
-
}
|
|
192
|
-
let botBorderStr = '─'.repeat(innerBoxWidth);
|
|
193
|
-
if (hasMoreDown) {
|
|
194
|
-
const mid = Math.max(0, Math.floor(innerBoxWidth / 2) - 2);
|
|
195
|
-
botBorderStr = '─'.repeat(mid) + ' ▼ ' + '─'.repeat(Math.max(0, innerBoxWidth - mid - 3));
|
|
196
|
-
}
|
|
197
|
-
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╭' + topBorderStr + '╮'));
|
|
198
|
-
for (let i = startIndex; i < endIndex; i++) {
|
|
199
|
-
const item = items[i];
|
|
200
|
-
const isSelected = i === selectedIndex;
|
|
201
|
-
const pointer = isSelected ? '› ' : ' ';
|
|
202
|
-
const availWidth = innerBoxWidth - 2;
|
|
203
|
-
let row = '';
|
|
204
|
-
if (availWidth < 20) {
|
|
205
|
-
const labelStr = item.label.length > availWidth ? item.label.slice(0, availWidth - 1) + '…' : item.label.padEnd(availWidth);
|
|
206
|
-
const plain = pointer + labelStr;
|
|
207
|
-
row = isSelected ? theme.badgeFn(plain) : (pointer + theme.boldFn(labelStr));
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
const labelMax = Math.min(15, Math.floor(availWidth * 0.45));
|
|
211
|
-
const labelStr = item.label.length > labelMax ? item.label.slice(0, labelMax - 1) + '…' : item.label.padEnd(labelMax);
|
|
212
|
-
const descMax = availWidth - labelMax - 1;
|
|
213
|
-
const descStr = item.desc.length > descMax ? item.desc.slice(0, descMax - 1) + '…' : item.desc.padEnd(descMax);
|
|
214
|
-
const plain = `${pointer}${labelStr} ${descStr}`;
|
|
215
|
-
if (isSelected) {
|
|
216
|
-
row = theme.badgeFn(plain);
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
row = `${pointer}${theme.boldFn(labelStr)} ${pc.gray(descStr)}`;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
const currentLen = stripAnsi(row).length;
|
|
223
|
-
if (currentLen < innerBoxWidth) {
|
|
224
|
-
row += ' '.repeat(innerBoxWidth - currentLen);
|
|
225
|
-
}
|
|
226
|
-
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('│') + row + pc.dim('│'));
|
|
227
|
-
}
|
|
228
|
-
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╰' + botBorderStr + '╯'));
|
|
229
|
-
}
|
|
230
|
-
// === SCROLLING SINGLE-LINE INPUT (never wraps, works on all terminals) ===
|
|
231
|
-
const prefix = pc.dim('│') + ' ';
|
|
232
|
-
const prefixLen = 3; // visible width of "│ "
|
|
233
|
-
// Reserve 1 char safety margin so terminal never auto-wraps
|
|
234
|
-
const maxVisible = cols - prefixLen - 1;
|
|
235
|
-
let lineContent;
|
|
236
|
-
let screenCursorCol;
|
|
237
|
-
if (input.length === 0) {
|
|
238
|
-
// Show placeholder
|
|
239
|
-
const displayPlace = placeholder.length > maxVisible ? placeholder.slice(0, maxVisible - 1) + '…' : placeholder;
|
|
240
|
-
lineContent = prefix + pc.dim(displayPlace);
|
|
241
|
-
screenCursorCol = prefixLen;
|
|
242
|
-
}
|
|
243
|
-
else if (input.length <= maxVisible) {
|
|
244
|
-
// Input fits on screen entirely
|
|
245
|
-
lineContent = prefix + formatInputWithBadges(input);
|
|
246
|
-
screenCursorCol = prefixLen + cursorPos;
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
// Scrolling window: show a slice of input around cursor
|
|
250
|
-
const scrollPad = Math.floor(maxVisible / 3);
|
|
251
|
-
let winStart = 0;
|
|
252
|
-
if (cursorPos > maxVisible - scrollPad) {
|
|
253
|
-
winStart = cursorPos - (maxVisible - scrollPad);
|
|
254
|
-
}
|
|
255
|
-
if (winStart > input.length - maxVisible) {
|
|
256
|
-
winStart = Math.max(0, input.length - maxVisible);
|
|
257
|
-
}
|
|
258
|
-
const winEnd = Math.min(winStart + maxVisible, input.length);
|
|
259
|
-
let visibleSlice = input.slice(winStart, winEnd);
|
|
260
|
-
// Add scroll indicators
|
|
261
|
-
if (winStart > 0) {
|
|
262
|
-
visibleSlice = '…' + visibleSlice.slice(1);
|
|
263
|
-
}
|
|
264
|
-
if (winEnd < input.length) {
|
|
265
|
-
visibleSlice = visibleSlice.slice(0, -1) + '…';
|
|
266
|
-
}
|
|
267
|
-
lineContent = prefix + formatInputWithBadges(visibleSlice);
|
|
268
|
-
screenCursorCol = prefixLen + (cursorPos - winStart);
|
|
269
|
-
// Clamp cursor position for scroll indicator adjustments
|
|
270
|
-
if (winStart > 0 && cursorPos === winStart) {
|
|
271
|
-
screenCursorCol = prefixLen + 1;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
// Pad line to exactly cols-1 to overwrite any stale characters
|
|
275
|
-
const visLen = stripAnsi(lineContent).length;
|
|
276
|
-
if (visLen < cols - 1) {
|
|
277
|
-
lineContent += ' '.repeat(cols - 1 - visLen);
|
|
278
|
-
}
|
|
279
|
-
// Move cursor up past any previous dropdown lines, then clear and redraw
|
|
280
|
-
if (lastDropdownLines > 0) {
|
|
281
|
-
process.stdout.write(`\x1b[${lastDropdownLines}A`);
|
|
282
|
-
}
|
|
283
|
-
// Write input line (always exactly 1 physical line, no wrap)
|
|
284
|
-
process.stdout.write(`\r\x1b[2K${lineContent}`);
|
|
285
|
-
// Write dropdown lines below
|
|
286
|
-
for (let i = 0; i < Math.max(dropdownLines.length, lastDropdownLines); i++) {
|
|
287
|
-
if (i < dropdownLines.length) {
|
|
288
|
-
process.stdout.write(`\n\x1b[2K${dropdownLines[i]}`);
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
process.stdout.write(`\n\x1b[2K`);
|
|
87
|
+
let isClosed = false;
|
|
88
|
+
const cleanup = () => {
|
|
89
|
+
if (isClosed)
|
|
90
|
+
return;
|
|
91
|
+
isClosed = true;
|
|
92
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
93
|
+
rl.close();
|
|
94
|
+
};
|
|
95
|
+
const onKeypress = (str, key) => {
|
|
96
|
+
if (isClosed)
|
|
97
|
+
return;
|
|
98
|
+
// Tab key pressed
|
|
99
|
+
if (key && key.name === 'tab') {
|
|
100
|
+
const curLine = rl.line || '';
|
|
101
|
+
// If line is empty or does not start with / or contain @, switch mode
|
|
102
|
+
if (curLine.trim() === '' || (!curLine.startsWith('/') && !curLine.includes('@'))) {
|
|
103
|
+
readline.cursorTo(process.stdout, 0);
|
|
104
|
+
readline.clearLine(process.stdout, 0);
|
|
105
|
+
cleanup();
|
|
106
|
+
resolve(`__TOGGLE_MODE__:${curLine}`);
|
|
107
|
+
return;
|
|
292
108
|
}
|
|
293
109
|
}
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
110
|
+
// Ctrl+V or Ctrl+P: Clipboard image paste
|
|
111
|
+
if ((key && key.ctrl && (key.name === 'v' || key.name === 'p')) || str === '\x16' || str === '\x10') {
|
|
112
|
+
saveClipboardImage('image.png', usedImageNames)
|
|
113
|
+
.then((imgRes) => {
|
|
114
|
+
if (imgRes && !isClosed) {
|
|
115
|
+
const tag = `[${imgRes.fileName} ${imgRes.sizeStr}]`;
|
|
116
|
+
imageAttachments.push({
|
|
117
|
+
tag,
|
|
118
|
+
fileName: imgRes.fileName,
|
|
119
|
+
filePath: imgRes.filePath,
|
|
120
|
+
sizeStr: imgRes.sizeStr
|
|
121
|
+
});
|
|
122
|
+
usedImageNames.add(imgRes.fileName);
|
|
123
|
+
rl.write(`${tag} `);
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
.catch(() => { });
|
|
298
127
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
128
|
+
};
|
|
129
|
+
if (process.stdin.isTTY) {
|
|
130
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
131
|
+
process.stdin.on('keypress', onKeypress);
|
|
303
132
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
disposed = true;
|
|
307
|
-
if (process.stdout.isTTY) {
|
|
308
|
-
process.stdout.write('\x1b[?2004l');
|
|
309
|
-
}
|
|
310
|
-
process.stdin.removeListener('data', onData);
|
|
311
|
-
if (process.stdin.isTTY) {
|
|
312
|
-
process.stdin.setRawMode(false);
|
|
313
|
-
}
|
|
314
|
-
process.stdin.pause();
|
|
133
|
+
if (opts.initialValue) {
|
|
134
|
+
rl.write(opts.initialValue);
|
|
315
135
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (lastDropdownLines > 0) {
|
|
319
|
-
for (let i = 0; i < lastDropdownLines; i++) {
|
|
320
|
-
process.stdout.write(`\n\x1b[2K`);
|
|
321
|
-
}
|
|
322
|
-
process.stdout.write(`\x1b[${lastDropdownLines}A`);
|
|
323
|
-
}
|
|
324
|
-
process.stdout.write(`\r\x1b[2K`);
|
|
325
|
-
const fullText = expandPastes(finalInput);
|
|
326
|
-
if (fullText.trim()) {
|
|
327
|
-
if (GLOBAL_PROMPT_HISTORY.length === 0 || GLOBAL_PROMPT_HISTORY[GLOBAL_PROMPT_HISTORY.length - 1] !== fullText.trim()) {
|
|
328
|
-
GLOBAL_PROMPT_HISTORY.push(fullText.trim());
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
const lines = fullText.split('\n');
|
|
332
|
-
process.stdout.write(`${pc.dim('│')} ${formatInputWithBadges(lines[0])}\n`);
|
|
333
|
-
for (let i = 1; i < lines.length; i++) {
|
|
334
|
-
process.stdout.write(pc.dim('│') + ' ' + lines[i] + '\n');
|
|
335
|
-
}
|
|
336
|
-
process.stdout.write('\n');
|
|
136
|
+
rl.prompt();
|
|
137
|
+
rl.on('line', async (rawLine) => {
|
|
337
138
|
cleanup();
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const pathCandidates = cleaned.split(/[&;\n\r]+/).map(s => s.trim()).filter(Boolean);
|
|
344
|
-
let foundAnyImage = false;
|
|
345
|
-
for (const cand of pathCandidates) {
|
|
346
|
-
const imgRes = await processPastedFilePath(cand, usedImageNames);
|
|
347
|
-
if (imgRes) {
|
|
348
|
-
foundAnyImage = true;
|
|
349
|
-
const tag = `[${imgRes.fileName} ${imgRes.sizeStr}]`;
|
|
350
|
-
imageAttachments.push({
|
|
351
|
-
tag,
|
|
352
|
-
fileName: imgRes.fileName,
|
|
353
|
-
filePath: imgRes.filePath,
|
|
354
|
-
sizeStr: imgRes.sizeStr
|
|
355
|
-
});
|
|
356
|
-
usedImageNames.add(imgRes.fileName);
|
|
357
|
-
input = input.slice(0, cursorPos) + tag + ' ' + input.slice(cursorPos);
|
|
358
|
-
cursorPos += tag.length + 1;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (foundAnyImage) {
|
|
362
|
-
selectedIndex = 0;
|
|
363
|
-
render();
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
const normalized = cleaned.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
367
|
-
const lines = normalized.split('\n');
|
|
368
|
-
if (lines.length >= 2 || normalized.length > 80) {
|
|
369
|
-
const linesCount = lines.length;
|
|
370
|
-
const id = pastes.length + 1;
|
|
371
|
-
const tag = `[Pasted text #${id} +${linesCount} lines]`;
|
|
372
|
-
pastes.push({ id, tag, content: normalized, linesCount });
|
|
373
|
-
input = input.slice(0, cursorPos) + tag + input.slice(cursorPos);
|
|
374
|
-
cursorPos += tag.length;
|
|
375
|
-
selectedIndex = 0;
|
|
376
|
-
render();
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
input = input.slice(0, cursorPos) + normalized + input.slice(cursorPos);
|
|
380
|
-
cursorPos += normalized.length;
|
|
381
|
-
selectedIndex = 0;
|
|
382
|
-
render();
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
function getTagSpans() {
|
|
386
|
-
const spans = [];
|
|
387
|
-
for (const p of pastes) {
|
|
388
|
-
let pos = 0;
|
|
389
|
-
while ((pos = input.indexOf(p.tag, pos)) !== -1) {
|
|
390
|
-
spans.push({
|
|
391
|
-
start: pos,
|
|
392
|
-
end: pos + p.tag.length,
|
|
393
|
-
tag: p.tag
|
|
394
|
-
});
|
|
395
|
-
pos += p.tag.length;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
for (const img of imageAttachments) {
|
|
399
|
-
let pos = 0;
|
|
400
|
-
while ((pos = input.indexOf(img.tag, pos)) !== -1) {
|
|
401
|
-
spans.push({
|
|
402
|
-
start: pos,
|
|
403
|
-
end: pos + img.tag.length,
|
|
404
|
-
tag: img.tag
|
|
405
|
-
});
|
|
406
|
-
pos += img.tag.length;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return spans.sort((a, b) => a.start - b.start);
|
|
410
|
-
}
|
|
411
|
-
let pasteBuffer = '';
|
|
412
|
-
let inBracketedPaste = false;
|
|
413
|
-
function onData(chunk) {
|
|
414
|
-
if (disposed)
|
|
415
|
-
return;
|
|
416
|
-
const str = chunk.toString('utf8');
|
|
417
|
-
// Check for bracketed paste start/end
|
|
418
|
-
if (str.includes('\x1b[200~')) {
|
|
419
|
-
inBracketedPaste = true;
|
|
420
|
-
pasteBuffer = '';
|
|
421
|
-
}
|
|
422
|
-
if (inBracketedPaste) {
|
|
423
|
-
pasteBuffer += str;
|
|
424
|
-
if (pasteBuffer.includes('\x1b[201~')) {
|
|
425
|
-
inBracketedPaste = false;
|
|
426
|
-
handlePaste(pasteBuffer);
|
|
427
|
-
pasteBuffer = '';
|
|
428
|
-
}
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
// Check for raw multi-character paste with newlines or large size
|
|
432
|
-
if (str.length > 1 && (str.includes('\n') || str.includes('\r') || str.length > 80)) {
|
|
433
|
-
handlePaste(str);
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
// Handle individual keys
|
|
437
|
-
// Ctrl+V (ASCII 22 / 0x16) or Ctrl+P (ASCII 16 / 0x10)
|
|
438
|
-
if (str === '\x16' || str === '\x10' || (str.length === 1 && (str.charCodeAt(0) === 22 || str.charCodeAt(0) === 16))) {
|
|
439
|
-
saveClipboardImage('image.png', usedImageNames).then((imgRes) => {
|
|
139
|
+
let line = rawLine.trim();
|
|
140
|
+
// Handle direct /image command
|
|
141
|
+
if (line === '/image') {
|
|
142
|
+
try {
|
|
143
|
+
const imgRes = await saveClipboardImage('image.png', usedImageNames);
|
|
440
144
|
if (imgRes) {
|
|
441
145
|
const tag = `[${imgRes.fileName} ${imgRes.sizeStr}]`;
|
|
442
146
|
imageAttachments.push({
|
|
@@ -446,242 +150,52 @@ export function askPrompt(opts = {}) {
|
|
|
446
150
|
sizeStr: imgRes.sizeStr
|
|
447
151
|
});
|
|
448
152
|
usedImageNames.add(imgRes.fileName);
|
|
449
|
-
|
|
450
|
-
cursorPos += tag.length + 1;
|
|
451
|
-
render();
|
|
452
|
-
}
|
|
453
|
-
}).catch(() => { });
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
// Ctrl+C
|
|
457
|
-
if (str === '\x03' || (str.length === 1 && str.charCodeAt(0) === 3)) {
|
|
458
|
-
if (lastDropdownLines > 0) {
|
|
459
|
-
for (let i = 0; i < lastDropdownLines; i++) {
|
|
460
|
-
process.stdout.write(`\n\x1b[2K`);
|
|
461
|
-
}
|
|
462
|
-
process.stdout.write(`\x1b[${lastDropdownLines}A`);
|
|
463
|
-
}
|
|
464
|
-
process.stdout.write('\r\x1b[2K\n');
|
|
465
|
-
cleanup();
|
|
466
|
-
resolve('__CANCEL__');
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
const items = getDropdownItems();
|
|
470
|
-
// Enter / Return
|
|
471
|
-
if (str === '\r' || str === '\n' || (str.length === 1 && (str.charCodeAt(0) === 13 || str.charCodeAt(0) === 10))) {
|
|
472
|
-
// Direct /image command
|
|
473
|
-
if (input.trim() === '/image' || (items.length > 0 && selectedIndex >= 0 && selectedIndex < items.length && items[selectedIndex].label === '/image')) {
|
|
474
|
-
saveClipboardImage('image.png', usedImageNames).then((imgRes) => {
|
|
475
|
-
if (imgRes) {
|
|
476
|
-
const tag = `[${imgRes.fileName} ${imgRes.sizeStr}]`;
|
|
477
|
-
imageAttachments.push({
|
|
478
|
-
tag,
|
|
479
|
-
fileName: imgRes.fileName,
|
|
480
|
-
filePath: imgRes.filePath,
|
|
481
|
-
sizeStr: imgRes.sizeStr
|
|
482
|
-
});
|
|
483
|
-
usedImageNames.add(imgRes.fileName);
|
|
484
|
-
input = `${tag} `;
|
|
485
|
-
cursorPos = input.length;
|
|
486
|
-
render();
|
|
487
|
-
}
|
|
488
|
-
else {
|
|
489
|
-
process.stdout.write(`\r\x1b[2K${pc.yellow('⚠️ No image in clipboard. Take a screenshot first (Win+Shift+S)\n')}`);
|
|
490
|
-
input = '';
|
|
491
|
-
cursorPos = 0;
|
|
492
|
-
render();
|
|
493
|
-
}
|
|
494
|
-
}).catch(() => { });
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
if (items.length > 0 && selectedIndex >= 0 && selectedIndex < items.length) {
|
|
498
|
-
const selected = items[selectedIndex];
|
|
499
|
-
if (input.startsWith('/')) {
|
|
500
|
-
input = selected.replacement;
|
|
501
|
-
clearBoxAndExit(input);
|
|
502
|
-
return;
|
|
153
|
+
line = tag;
|
|
503
154
|
}
|
|
504
155
|
else {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
cursorPos = selected.replaceStart + selected.replacement.length;
|
|
508
|
-
render();
|
|
156
|
+
console.log(pc.yellow('⚠️ No image in clipboard. Take a screenshot first (Win+Shift+S)\n'));
|
|
157
|
+
resolve('');
|
|
509
158
|
return;
|
|
510
159
|
}
|
|
511
160
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
// Tab
|
|
516
|
-
if (str === '\t' || (str.length === 1 && str.charCodeAt(0) === 9)) {
|
|
517
|
-
if (items.length > 0 && selectedIndex >= 0 && selectedIndex < items.length) {
|
|
518
|
-
const selected = items[selectedIndex];
|
|
519
|
-
input = input.slice(0, selected.replaceStart) + selected.replacement + input.slice(selected.replaceStart + selected.replaceLen);
|
|
520
|
-
cursorPos = selected.replaceStart + selected.replacement.length;
|
|
521
|
-
render();
|
|
522
|
-
}
|
|
523
|
-
else {
|
|
524
|
-
if (lastDropdownLines > 0) {
|
|
525
|
-
for (let i = 0; i < lastDropdownLines; i++) {
|
|
526
|
-
process.stdout.write(`\n\x1b[2K`);
|
|
527
|
-
}
|
|
528
|
-
process.stdout.write(`\x1b[${lastDropdownLines}A`);
|
|
529
|
-
}
|
|
530
|
-
process.stdout.write('\r\x1b[2K');
|
|
531
|
-
cleanup();
|
|
532
|
-
resolve(`__TOGGLE_MODE__:${input}`);
|
|
533
|
-
}
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
// Up Arrow
|
|
537
|
-
if (str === '\x1b[A' || str === '\x1bOA') {
|
|
538
|
-
if (items.length > 0) {
|
|
539
|
-
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
540
|
-
render();
|
|
541
|
-
}
|
|
542
|
-
else if (GLOBAL_PROMPT_HISTORY.length > 0 && historyIndex > 0) {
|
|
543
|
-
if (historyIndex === GLOBAL_PROMPT_HISTORY.length) {
|
|
544
|
-
tempDraft = input;
|
|
545
|
-
}
|
|
546
|
-
historyIndex--;
|
|
547
|
-
input = GLOBAL_PROMPT_HISTORY[historyIndex];
|
|
548
|
-
cursorPos = input.length;
|
|
549
|
-
render();
|
|
550
|
-
}
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
// Down Arrow
|
|
554
|
-
if (str === '\x1b[B' || str === '\x1bOB') {
|
|
555
|
-
if (items.length > 0) {
|
|
556
|
-
selectedIndex = (selectedIndex + 1) % items.length;
|
|
557
|
-
render();
|
|
558
|
-
}
|
|
559
|
-
else if (historyIndex < GLOBAL_PROMPT_HISTORY.length) {
|
|
560
|
-
historyIndex++;
|
|
561
|
-
if (historyIndex === GLOBAL_PROMPT_HISTORY.length) {
|
|
562
|
-
input = tempDraft;
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
input = GLOBAL_PROMPT_HISTORY[historyIndex];
|
|
566
|
-
}
|
|
567
|
-
cursorPos = input.length;
|
|
568
|
-
render();
|
|
569
|
-
}
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
// Left Arrow (Jump across whole paste badge atomically)
|
|
573
|
-
if (str === '\x1b[D' || str === '\x1bOD') {
|
|
574
|
-
if (cursorPos > 0) {
|
|
575
|
-
const spans = getTagSpans();
|
|
576
|
-
const endingSpan = spans.find(s => s.end === cursorPos);
|
|
577
|
-
if (endingSpan) {
|
|
578
|
-
cursorPos = endingSpan.start;
|
|
579
|
-
}
|
|
580
|
-
else {
|
|
581
|
-
cursorPos--;
|
|
582
|
-
const inside = spans.find(s => cursorPos > s.start && cursorPos < s.end);
|
|
583
|
-
if (inside)
|
|
584
|
-
cursorPos = inside.start;
|
|
585
|
-
}
|
|
586
|
-
render();
|
|
161
|
+
catch {
|
|
162
|
+
resolve('');
|
|
163
|
+
return;
|
|
587
164
|
}
|
|
588
|
-
return;
|
|
589
165
|
}
|
|
590
|
-
//
|
|
591
|
-
if (
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
}
|
|
604
|
-
render();
|
|
166
|
+
// Check for pasted file path to image
|
|
167
|
+
if (line.match(/\.(png|jpe?g|webp|gif|bmp)$/i)) {
|
|
168
|
+
const imgRes = await processPastedFilePath(line, usedImageNames);
|
|
169
|
+
if (imgRes) {
|
|
170
|
+
const tag = `[${imgRes.fileName} ${imgRes.sizeStr}]`;
|
|
171
|
+
imageAttachments.push({
|
|
172
|
+
tag,
|
|
173
|
+
fileName: imgRes.fileName,
|
|
174
|
+
filePath: imgRes.filePath,
|
|
175
|
+
sizeStr: imgRes.sizeStr
|
|
176
|
+
});
|
|
177
|
+
usedImageNames.add(imgRes.fileName);
|
|
178
|
+
line = tag;
|
|
605
179
|
}
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
// Home
|
|
609
|
-
if (str === '\x1b[H' || str === '\x1b[1~') {
|
|
610
|
-
cursorPos = 0;
|
|
611
|
-
render();
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
// End
|
|
615
|
-
if (str === '\x1b[F' || str === '\x1b[4~') {
|
|
616
|
-
cursorPos = input.length;
|
|
617
|
-
render();
|
|
618
|
-
return;
|
|
619
180
|
}
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const startingSpan = spans.find(s => s.start === cursorPos);
|
|
625
|
-
if (startingSpan) {
|
|
626
|
-
input = input.slice(0, startingSpan.start) + input.slice(startingSpan.end);
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
input = input.slice(0, cursorPos) + input.slice(cursorPos + 1);
|
|
630
|
-
}
|
|
631
|
-
selectedIndex = 0;
|
|
632
|
-
render();
|
|
633
|
-
}
|
|
634
|
-
return;
|
|
181
|
+
// Expand image attachment tags to @path
|
|
182
|
+
let expanded = line;
|
|
183
|
+
for (const img of imageAttachments) {
|
|
184
|
+
expanded = expanded.split(img.tag).join(`@${img.filePath.replace(/\\/g, '/')} `);
|
|
635
185
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (ch === '\x08' || ch === '\x7f') {
|
|
641
|
-
if (cursorPos > 0) {
|
|
642
|
-
const spans = getTagSpans();
|
|
643
|
-
const endingSpan = spans.find(s => s.end === cursorPos);
|
|
644
|
-
if (endingSpan) {
|
|
645
|
-
input = input.slice(0, endingSpan.start) + input.slice(endingSpan.end);
|
|
646
|
-
cursorPos = endingSpan.start;
|
|
647
|
-
}
|
|
648
|
-
else {
|
|
649
|
-
input = input.slice(0, cursorPos - 1) + input.slice(cursorPos);
|
|
650
|
-
cursorPos--;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
else if (ch.charCodeAt(0) >= 32) {
|
|
655
|
-
const spans = getTagSpans();
|
|
656
|
-
const inside = spans.find(s => cursorPos > s.start && cursorPos < s.end);
|
|
657
|
-
if (inside)
|
|
658
|
-
cursorPos = inside.end;
|
|
659
|
-
input = input.slice(0, cursorPos) + ch + input.slice(cursorPos);
|
|
660
|
-
cursorPos++;
|
|
661
|
-
}
|
|
186
|
+
if (expanded.trim()) {
|
|
187
|
+
if (GLOBAL_PROMPT_HISTORY.length === 0 ||
|
|
188
|
+
GLOBAL_PROMPT_HISTORY[GLOBAL_PROMPT_HISTORY.length - 1] !== expanded.trim()) {
|
|
189
|
+
GLOBAL_PROMPT_HISTORY.push(expanded.trim());
|
|
662
190
|
}
|
|
663
|
-
selectedIndex = 0;
|
|
664
|
-
render();
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
// Ignore unknown escape sequences
|
|
668
|
-
if (str.startsWith('\x1b')) {
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
// Normal character input - clean control characters
|
|
672
|
-
const cleanStr = str.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
|
|
673
|
-
if (!cleanStr)
|
|
674
|
-
return;
|
|
675
|
-
const spans = getTagSpans();
|
|
676
|
-
const inside = spans.find(s => cursorPos > s.start && cursorPos < s.end);
|
|
677
|
-
if (inside) {
|
|
678
|
-
cursorPos = inside.end;
|
|
679
191
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
192
|
+
console.log();
|
|
193
|
+
resolve(expanded);
|
|
194
|
+
});
|
|
195
|
+
rl.on('SIGINT', () => {
|
|
196
|
+
cleanup();
|
|
197
|
+
console.log();
|
|
198
|
+
resolve('__CANCEL__');
|
|
199
|
+
});
|
|
686
200
|
});
|
|
687
201
|
}
|
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.7 • Terminal-Native AI Assistant</div>
|
|
143
143
|
</div>
|
|
144
144
|
</body>
|
|
145
145
|
</html>`;
|
package/dist/mcp/client.js
CHANGED
package/package.json
CHANGED