termux-dev 1.4.12 → 1.4.14
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/clipboard.js +10 -1
- package/dist/cli/doctor.js +1 -1
- package/dist/cli/files.js +1 -1
- package/dist/cli/headless.js +1 -1
- package/dist/cli/index.js +34 -7
- package/dist/cli/prompt.js +49 -51
- package/dist/cli/server.js +13 -10
- package/dist/cli/theme.js +6 -6
- package/dist/core/commands.js +1 -1
- package/dist/core/history.js +23 -5
- package/dist/core/loop.js +13 -0
- package/dist/core/model-limits.json +6543 -0
- package/dist/core/model-pricing.json +26166 -0
- package/dist/core/models.js +13 -1
- package/dist/core/notify.js +15 -1
- package/dist/core/pricing.js +3 -2
- package/dist/mcp/client.js +1 -1
- package/dist/permissions/guard.js +19 -4
- package/dist/providers/openai.js +69 -30
- package/dist/tools/bash.js +16 -4
- package/dist/tools/diagnostics.js +4 -3
- package/dist/tools/fs.js +11 -2
- package/dist/tools/packages.js +3 -1
- package/dist/tools/questions.js +55 -38
- package/package.json +2 -2
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.14</tspan>
|
|
29
29
|
</text>
|
|
30
30
|
|
|
31
31
|
<!-- Top Accent Line -->
|
package/dist/cli/clipboard.js
CHANGED
|
@@ -4,6 +4,15 @@ import path from 'path';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import { exec } from 'child_process';
|
|
6
6
|
const MEDIA_DIR = path.join(process.cwd(), '.devx', 'media');
|
|
7
|
+
export function expandHomePath(p) {
|
|
8
|
+
if (!p)
|
|
9
|
+
return p;
|
|
10
|
+
const trimmed = p.trim().replace(/^['"]|['"]$/g, '');
|
|
11
|
+
if (trimmed === '~' || trimmed.startsWith('~/') || trimmed.startsWith('~\\')) {
|
|
12
|
+
return path.join(os.homedir(), trimmed.slice(2));
|
|
13
|
+
}
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
7
16
|
export function formatFileSize(bytes) {
|
|
8
17
|
if (bytes < 1024)
|
|
9
18
|
return `${bytes}b`;
|
|
@@ -107,7 +116,7 @@ if ($img -ne $null) {
|
|
|
107
116
|
});
|
|
108
117
|
}
|
|
109
118
|
export async function processPastedFilePath(rawPath, existingNames) {
|
|
110
|
-
const cleanPath = rawPath.trim().replace(/^['"&]+|['"]+$/g, '').trim();
|
|
119
|
+
const cleanPath = expandHomePath(rawPath).trim().replace(/^['"&]+|['"]+$/g, '').trim();
|
|
111
120
|
const resolved = path.resolve(process.cwd(), cleanPath);
|
|
112
121
|
const imageExts = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.bmp']);
|
|
113
122
|
const ext = path.extname(resolved).toLowerCase();
|
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.14'
|
|
22
22
|
}
|
|
23
23
|
}, (res) => {
|
|
24
24
|
res.resume(); // consume response data to free up memory and release socket
|
package/dist/cli/files.js
CHANGED
|
@@ -58,7 +58,7 @@ const IMAGE_EXTS = {
|
|
|
58
58
|
'.svg': 'image/svg+xml'
|
|
59
59
|
};
|
|
60
60
|
export async function resolveAtMentions(text) {
|
|
61
|
-
const mentionRegex = /@([a-zA-Z0-9_
|
|
61
|
+
const mentionRegex = /@([a-zA-Z0-9_\-./\u0400-\u04FF]+)/g;
|
|
62
62
|
const attachments = [];
|
|
63
63
|
const images = [];
|
|
64
64
|
const seenPaths = new Set();
|
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.14 (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
|
@@ -16,7 +16,7 @@ import { CLIConsoleGuard } from '../permissions/guard.js';
|
|
|
16
16
|
import { globalSnapshotManager } from '../core/snapshot.js';
|
|
17
17
|
import { MemoryManager } from '../core/memory.js';
|
|
18
18
|
import { startServer, stopServer, displayServerBanner } from './server.js';
|
|
19
|
-
import { notifyDevice } from '../core/notify.js';
|
|
19
|
+
import { notifyDevice, acquireWakeLock } from '../core/notify.js';
|
|
20
20
|
import { runDoctor } from './doctor.js';
|
|
21
21
|
import { ALL_PROVIDERS } from './providers.js';
|
|
22
22
|
import { getModelContextLimit } from '../core/models.js';
|
|
@@ -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.14'),
|
|
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.14'),
|
|
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.14 • 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.14 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.14 — 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` +
|
|
@@ -767,11 +767,38 @@ async function handleSettings(config) {
|
|
|
767
767
|
return config;
|
|
768
768
|
}
|
|
769
769
|
export async function main() {
|
|
770
|
+
// === Termux / Android Stability: Global Signal Handlers ===
|
|
771
|
+
// Ignore SIGHUP so the process survives when Termux is minimized
|
|
772
|
+
if (process.platform !== 'win32') {
|
|
773
|
+
process.on('SIGHUP', () => {
|
|
774
|
+
// Termux minimized or window lost focus — keep process alive
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
// Graceful shutdown on SIGTERM (closing Termux tab / kill command)
|
|
778
|
+
process.on('SIGTERM', () => {
|
|
779
|
+
try {
|
|
780
|
+
resetTerminalTheme();
|
|
781
|
+
MCPManager.getInstance().stopAll();
|
|
782
|
+
}
|
|
783
|
+
catch { }
|
|
784
|
+
process.exit(0);
|
|
785
|
+
});
|
|
786
|
+
// Protect against crash on background I/O errors (EIO/EPIPE/ERR_STREAM_DESTROYED)
|
|
787
|
+
process.on('uncaughtException', (err) => {
|
|
788
|
+
const isIgnorableIo = err.code === 'EIO' || err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED';
|
|
789
|
+
if (isIgnorableIo)
|
|
790
|
+
return;
|
|
791
|
+
console.error('\n[devx runtime error]:', err.message);
|
|
792
|
+
});
|
|
793
|
+
process.on('unhandledRejection', (reason) => {
|
|
794
|
+
// Prevent Node.js crash from unhandled network promise rejections
|
|
795
|
+
});
|
|
796
|
+
acquireWakeLock();
|
|
770
797
|
const program = new Command();
|
|
771
798
|
program
|
|
772
799
|
.name('devx')
|
|
773
800
|
.description('Terminal-native AI coding assistant and vibe-coding agent')
|
|
774
|
-
.version('1.4.
|
|
801
|
+
.version('1.4.14')
|
|
775
802
|
.option('-p, --prompt <task>', 'Run one-shot task non-interactively (headless mode)')
|
|
776
803
|
.option('-y, --yolo', 'Automatically approve all tool executions without confirmation')
|
|
777
804
|
.option('-m, --model <model>', 'Specify AI model to use for this execution')
|
package/dist/cli/prompt.js
CHANGED
|
@@ -69,6 +69,9 @@ export function askPrompt(opts = {}) {
|
|
|
69
69
|
process.stdin.setRawMode(true);
|
|
70
70
|
process.stdout.write('\x1b[?2004h'); // Enable bracketed paste mode
|
|
71
71
|
}
|
|
72
|
+
const onResize = () => { if (!disposed)
|
|
73
|
+
render(); };
|
|
74
|
+
process.stdout.on('resize', onResize);
|
|
72
75
|
function getTagSpans() {
|
|
73
76
|
const spans = [];
|
|
74
77
|
const regex = /\[(?:Pasted text #\d+ \+\d+ lines|\d+\.png \d+kb)\]/g;
|
|
@@ -79,7 +82,7 @@ export function askPrompt(opts = {}) {
|
|
|
79
82
|
return spans;
|
|
80
83
|
}
|
|
81
84
|
function formatInputWithBadges(str) {
|
|
82
|
-
return str.replace(/(\[(?:Pasted text #\d+ \+\d+ lines|\d+\.png \d+kb)\])/g, (match) => theme.badgeFn(` ${match.slice(1, -1)} `));
|
|
85
|
+
return str.replace(/(\[(?:Pasted text #\d+ \+\d+ lines|\d+\.png \d+kb)\])/g, (match) => theme.badgeFn(` ${match.slice(1, -1)} `) + '\x1b[0m');
|
|
83
86
|
}
|
|
84
87
|
function expandPastes(text) {
|
|
85
88
|
let expanded = text;
|
|
@@ -95,13 +98,11 @@ export function askPrompt(opts = {}) {
|
|
|
95
98
|
const trimmed = text.trim();
|
|
96
99
|
const lines = trimmed.split(/\r\n|\r|\n/);
|
|
97
100
|
if (lines.length <= 1 && text.length < 80) {
|
|
98
|
-
// Short paste: insert normally
|
|
99
101
|
input = input.slice(0, cursorPos) + text + input.slice(cursorPos);
|
|
100
102
|
cursorPos += text.length;
|
|
101
103
|
render();
|
|
102
104
|
return;
|
|
103
105
|
}
|
|
104
|
-
// Check if pasted text is an image path
|
|
105
106
|
if (trimmed.match(/\.(png|jpe?g|webp|gif|bmp)$/i)) {
|
|
106
107
|
processPastedFilePath(trimmed, usedImageNames)
|
|
107
108
|
.then((imgRes) => {
|
|
@@ -130,7 +131,6 @@ export function askPrompt(opts = {}) {
|
|
|
130
131
|
render();
|
|
131
132
|
}
|
|
132
133
|
function getDropdownItems() {
|
|
133
|
-
// 1. Slash commands dropdown (when input starts with /)
|
|
134
134
|
if (input.startsWith('/')) {
|
|
135
135
|
const q = input.trim().toLowerCase();
|
|
136
136
|
const baseList = [
|
|
@@ -146,9 +146,8 @@ export function askPrompt(opts = {}) {
|
|
|
146
146
|
replaceLen: input.length
|
|
147
147
|
}));
|
|
148
148
|
}
|
|
149
|
-
// 2. @ file mention autocomplete
|
|
150
149
|
const beforeCursor = input.slice(0, cursorPos);
|
|
151
|
-
const atMatch = beforeCursor.match(/@([a-zA-Z0-9_
|
|
150
|
+
const atMatch = beforeCursor.match(/@([a-zA-Z0-9_\-./\u0400-\u04FF]*)$/);
|
|
152
151
|
if (atMatch && availableFiles.length > 0) {
|
|
153
152
|
const query = atMatch[1].toLowerCase();
|
|
154
153
|
const replaceStart = cursorPos - atMatch[0].length;
|
|
@@ -179,7 +178,7 @@ export function askPrompt(opts = {}) {
|
|
|
179
178
|
if (selectedIndex < 0)
|
|
180
179
|
selectedIndex = 0;
|
|
181
180
|
const pageSize = Math.min(4, Math.max(2, Math.floor(rows / 4)));
|
|
182
|
-
const innerBoxWidth = Math.max(
|
|
181
|
+
const innerBoxWidth = Math.max(10, Math.min(cols - 8, 48));
|
|
183
182
|
const total = items.length;
|
|
184
183
|
let startIndex = 0;
|
|
185
184
|
if (total > pageSize) {
|
|
@@ -200,47 +199,42 @@ export function askPrompt(opts = {}) {
|
|
|
200
199
|
const mid = Math.max(0, Math.floor(innerBoxWidth / 2) - 2);
|
|
201
200
|
botBorderStr = '─'.repeat(mid) + ' ▼ ' + '─'.repeat(Math.max(0, innerBoxWidth - mid - 3));
|
|
202
201
|
}
|
|
203
|
-
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╭' + topBorderStr + '╮'));
|
|
202
|
+
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╭' + topBorderStr + '╮') + '\x1b[0m');
|
|
204
203
|
for (let i = startIndex; i < endIndex; i++) {
|
|
205
204
|
const item = items[i];
|
|
206
205
|
const isSelected = i === selectedIndex;
|
|
207
206
|
const pointer = isSelected ? '› ' : ' ';
|
|
208
|
-
const availWidth = innerBoxWidth
|
|
207
|
+
const availWidth = innerBoxWidth;
|
|
209
208
|
let row = '';
|
|
210
|
-
if (availWidth <
|
|
211
|
-
const labelStr = item.label.length > availWidth
|
|
212
|
-
? item.label.slice(0, availWidth -
|
|
213
|
-
: item.label.padEnd(availWidth);
|
|
214
|
-
const plain = pointer + labelStr;
|
|
215
|
-
row = isSelected
|
|
209
|
+
if (availWidth < 20) {
|
|
210
|
+
const labelStr = item.label.length > availWidth - 2
|
|
211
|
+
? item.label.slice(0, availWidth - 3) + '…'
|
|
212
|
+
: item.label.padEnd(availWidth - 2, ' ');
|
|
213
|
+
const plain = (pointer + labelStr).padEnd(availWidth, ' ').slice(0, availWidth);
|
|
214
|
+
row = isSelected
|
|
215
|
+
? theme.badgeFn(plain) + '\x1b[0m'
|
|
216
|
+
: pointer + theme.boldFn(labelStr) + '\x1b[0m';
|
|
216
217
|
}
|
|
217
218
|
else {
|
|
218
|
-
const labelMax = Math.min(
|
|
219
|
+
const labelMax = Math.min(13, Math.max(6, Math.floor(availWidth * 0.35)));
|
|
219
220
|
const labelStr = item.label.length > labelMax
|
|
220
221
|
? item.label.slice(0, labelMax - 1) + '…'
|
|
221
|
-
: item.label.padEnd(labelMax);
|
|
222
|
-
const descMax = availWidth - labelMax - 1;
|
|
222
|
+
: item.label.padEnd(labelMax, ' ');
|
|
223
|
+
const descMax = Math.max(4, availWidth - labelMax - pointer.length - 1);
|
|
223
224
|
const descStr = item.desc.length > descMax
|
|
224
225
|
? item.desc.slice(0, descMax - 1) + '…'
|
|
225
|
-
: item.desc.padEnd(descMax);
|
|
226
|
-
const plain =
|
|
226
|
+
: item.desc.padEnd(descMax, ' ');
|
|
227
|
+
const plain = (pointer + labelStr + ' ' + descStr).padEnd(availWidth, ' ').slice(0, availWidth);
|
|
227
228
|
if (isSelected) {
|
|
228
|
-
row = theme.badgeFn(plain);
|
|
229
|
+
row = theme.badgeFn(plain) + '\x1b[0m';
|
|
229
230
|
}
|
|
230
231
|
else {
|
|
231
|
-
row = `${pointer}${theme.boldFn(labelStr)} ${pc.gray(descStr)}`;
|
|
232
|
+
row = `${pointer}${theme.boldFn(labelStr)} ${pc.gray(descStr)}\x1b[0m`;
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
|
-
|
|
235
|
-
if (currentLen < innerBoxWidth) {
|
|
236
|
-
row += ' '.repeat(innerBoxWidth - currentLen);
|
|
237
|
-
}
|
|
238
|
-
else if (currentLen > innerBoxWidth) {
|
|
239
|
-
row = row.slice(0, innerBoxWidth);
|
|
240
|
-
}
|
|
241
|
-
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('│') + row + pc.dim('│'));
|
|
235
|
+
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('│') + row + pc.dim('│') + '\x1b[0m');
|
|
242
236
|
}
|
|
243
|
-
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╰' + botBorderStr + '╯'));
|
|
237
|
+
dropdownLines.push(pc.dim('│') + ' ' + pc.dim('╰' + botBorderStr + '╯') + '\x1b[0m');
|
|
244
238
|
}
|
|
245
239
|
// 1. Format and write the Prompt line safely (guaranteed <= cols - 1 chars)
|
|
246
240
|
let inputDisplay = pc.dim('│') + ' ';
|
|
@@ -248,13 +242,13 @@ export function askPrompt(opts = {}) {
|
|
|
248
242
|
if (input.length === 0) {
|
|
249
243
|
const maxPlace = Math.max(10, cols - 6);
|
|
250
244
|
const displayPlace = placeholder.length > maxPlace ? placeholder.slice(0, maxPlace - 1) + '…' : placeholder;
|
|
251
|
-
inputDisplay += pc.dim(displayPlace);
|
|
245
|
+
inputDisplay += pc.dim(displayPlace) + '\x1b[0m';
|
|
252
246
|
renderCursorCol = 3;
|
|
253
247
|
}
|
|
254
248
|
else {
|
|
255
249
|
const maxInputLen = Math.max(10, cols - 6);
|
|
256
250
|
if (input.length <= maxInputLen) {
|
|
257
|
-
inputDisplay += formatInputWithBadges(input);
|
|
251
|
+
inputDisplay += formatInputWithBadges(input) + '\x1b[0m';
|
|
258
252
|
renderCursorCol = 3 + cursorPos;
|
|
259
253
|
}
|
|
260
254
|
else {
|
|
@@ -263,24 +257,24 @@ export function askPrompt(opts = {}) {
|
|
|
263
257
|
const visibleChunk = input.slice(start, start + maxInputLen);
|
|
264
258
|
const prefix = start > 0 ? '…' : '';
|
|
265
259
|
const suffix = start + maxInputLen < input.length ? '…' : '';
|
|
266
|
-
inputDisplay += pc.dim(prefix) + formatInputWithBadges(visibleChunk) + pc.dim(suffix);
|
|
260
|
+
inputDisplay += pc.dim(prefix) + formatInputWithBadges(visibleChunk) + pc.dim(suffix) + '\x1b[0m';
|
|
267
261
|
renderCursorCol = 3 + (prefix ? 1 : 0) + (cursorPos - start);
|
|
268
262
|
}
|
|
269
263
|
}
|
|
270
|
-
process.stdout.write(`\r\x1b[2K${inputDisplay}`);
|
|
264
|
+
process.stdout.write(`\x1b[0m\r\x1b[2K${inputDisplay}\x1b[0m`);
|
|
271
265
|
// 2. Draw dropdown lines below, and clear extra lines from previous render
|
|
272
266
|
const maxDropdowns = Math.max(dropdownLines.length, lastRenderedDropdownLines);
|
|
273
267
|
if (maxDropdowns > 0) {
|
|
274
268
|
for (let i = 0; i < maxDropdowns; i++) {
|
|
275
269
|
if (i < dropdownLines.length) {
|
|
276
|
-
process.stdout.write(`\n\x1b[2K${dropdownLines[i]}`);
|
|
270
|
+
process.stdout.write(`\n\x1b[0m\x1b[2K${dropdownLines[i]}\x1b[0m`);
|
|
277
271
|
}
|
|
278
272
|
else {
|
|
279
|
-
process.stdout.write(`\n\x1b[2K`);
|
|
273
|
+
process.stdout.write(`\n\x1b[0m\x1b[2K`);
|
|
280
274
|
}
|
|
281
275
|
}
|
|
282
276
|
// Move cursor back up to input line (line 0)
|
|
283
|
-
process.stdout.write(`\x1b[${maxDropdowns}A`);
|
|
277
|
+
process.stdout.write(`\x1b[0m\x1b[${maxDropdowns}A`);
|
|
284
278
|
}
|
|
285
279
|
// 3. Place cursor precisely
|
|
286
280
|
process.stdout.write(`\r\x1b[${renderCursorCol}C`);
|
|
@@ -292,6 +286,7 @@ export function askPrompt(opts = {}) {
|
|
|
292
286
|
if (process.stdout.isTTY) {
|
|
293
287
|
process.stdout.write('\x1b[?2004l');
|
|
294
288
|
}
|
|
289
|
+
process.stdout.removeListener('resize', onResize);
|
|
295
290
|
process.stdin.removeListener('data', onData);
|
|
296
291
|
if (process.stdin.isTTY) {
|
|
297
292
|
process.stdin.setRawMode(false);
|
|
@@ -301,9 +296,9 @@ export function askPrompt(opts = {}) {
|
|
|
301
296
|
function clearBoxAndExit(finalInput) {
|
|
302
297
|
if (lastRenderedDropdownLines > 0) {
|
|
303
298
|
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
304
|
-
process.stdout.write(`\n\x1b[2K`);
|
|
299
|
+
process.stdout.write(`\n\x1b[0m\x1b[2K`);
|
|
305
300
|
}
|
|
306
|
-
process.stdout.write(`\x1b[${lastRenderedDropdownLines}A`);
|
|
301
|
+
process.stdout.write(`\x1b[0m\x1b[${lastRenderedDropdownLines}A`);
|
|
307
302
|
lastRenderedDropdownLines = 0;
|
|
308
303
|
}
|
|
309
304
|
const fullText = expandPastes(finalInput);
|
|
@@ -313,7 +308,7 @@ export function askPrompt(opts = {}) {
|
|
|
313
308
|
GLOBAL_PROMPT_HISTORY.push(fullText.trim());
|
|
314
309
|
}
|
|
315
310
|
}
|
|
316
|
-
process.stdout.write(`\r\x1b[2K${pc.dim('│')} ${formatInputWithBadges(finalInput)}\n\n`);
|
|
311
|
+
process.stdout.write(`\x1b[0m\r\x1b[2K${pc.dim('│')} ${formatInputWithBadges(finalInput)}\x1b[0m\n\n`);
|
|
317
312
|
cleanup();
|
|
318
313
|
resolve(fullText);
|
|
319
314
|
}
|
|
@@ -359,12 +354,12 @@ export function askPrompt(opts = {}) {
|
|
|
359
354
|
if (str === '\x03') {
|
|
360
355
|
if (lastRenderedDropdownLines > 0) {
|
|
361
356
|
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
362
|
-
process.stdout.write(`\n\x1b[2K`);
|
|
357
|
+
process.stdout.write(`\n\x1b[0m\x1b[2K`);
|
|
363
358
|
}
|
|
364
|
-
process.stdout.write(`\x1b[${lastRenderedDropdownLines}A`);
|
|
359
|
+
process.stdout.write(`\x1b[0m\x1b[${lastRenderedDropdownLines}A`);
|
|
365
360
|
lastRenderedDropdownLines = 0;
|
|
366
361
|
}
|
|
367
|
-
process.stdout.write(`\r\x1b[2K\n`);
|
|
362
|
+
process.stdout.write(`\x1b[0m\r\x1b[2K\n`);
|
|
368
363
|
cleanup();
|
|
369
364
|
resolve('__CANCEL__');
|
|
370
365
|
return;
|
|
@@ -396,7 +391,7 @@ export function askPrompt(opts = {}) {
|
|
|
396
391
|
render();
|
|
397
392
|
}
|
|
398
393
|
else {
|
|
399
|
-
process.stdout.write(`\r\x1b[2K${pc.yellow('⚠️ No image in clipboard. Take a screenshot first (Win+Shift+S)\n')}`);
|
|
394
|
+
process.stdout.write(`\x1b[0m\r\x1b[2K${pc.yellow('⚠️ No image in clipboard. Take a screenshot first (Win+Shift+S)\n')}`);
|
|
400
395
|
input = '';
|
|
401
396
|
cursorPos = 0;
|
|
402
397
|
render();
|
|
@@ -440,12 +435,12 @@ export function askPrompt(opts = {}) {
|
|
|
440
435
|
else {
|
|
441
436
|
if (lastRenderedDropdownLines > 0) {
|
|
442
437
|
for (let i = 0; i < lastRenderedDropdownLines; i++) {
|
|
443
|
-
process.stdout.write(`\n\x1b[2K`);
|
|
438
|
+
process.stdout.write(`\n\x1b[0m\x1b[2K`);
|
|
444
439
|
}
|
|
445
|
-
process.stdout.write(`\x1b[${lastRenderedDropdownLines}A`);
|
|
440
|
+
process.stdout.write(`\x1b[0m\x1b[${lastRenderedDropdownLines}A`);
|
|
446
441
|
lastRenderedDropdownLines = 0;
|
|
447
442
|
}
|
|
448
|
-
process.stdout.write(`\r\x1b[2K`);
|
|
443
|
+
process.stdout.write(`\x1b[0m\r\x1b[2K`);
|
|
449
444
|
cleanup();
|
|
450
445
|
resolve(`__TOGGLE_MODE__:${input}`);
|
|
451
446
|
}
|
|
@@ -551,7 +546,7 @@ export function askPrompt(opts = {}) {
|
|
|
551
546
|
}
|
|
552
547
|
return;
|
|
553
548
|
}
|
|
554
|
-
// Backspace / DEL handling
|
|
549
|
+
// Backspace / DEL handling
|
|
555
550
|
if (str.includes('\x08') || str.includes('\x7f')) {
|
|
556
551
|
for (let i = 0; i < str.length; i++) {
|
|
557
552
|
const ch = str[i];
|
|
@@ -564,8 +559,11 @@ export function askPrompt(opts = {}) {
|
|
|
564
559
|
cursorPos = endingSpan.start;
|
|
565
560
|
}
|
|
566
561
|
else {
|
|
567
|
-
|
|
568
|
-
cursorPos
|
|
562
|
+
// Handle surrogate pairs (emoji)
|
|
563
|
+
const prevChar = input.charCodeAt(cursorPos - 1);
|
|
564
|
+
const deleteCount = (prevChar >= 0xDC00 && prevChar <= 0xDFFF && cursorPos >= 2) ? 2 : 1;
|
|
565
|
+
input = input.slice(0, cursorPos - deleteCount) + input.slice(cursorPos);
|
|
566
|
+
cursorPos -= deleteCount;
|
|
569
567
|
}
|
|
570
568
|
}
|
|
571
569
|
}
|
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.14 • Terminal-Native AI Assistant</div>
|
|
143
143
|
</div>
|
|
144
144
|
</body>
|
|
145
145
|
</html>`;
|
|
@@ -224,15 +224,18 @@ export async function startServer(preferredPort = 3000) {
|
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
226
226
|
// 4. SPA Fallback: check if root index.html exists
|
|
227
|
-
const
|
|
228
|
-
if (
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
227
|
+
const hasAssetExt = path.extname(reqPath).length > 0;
|
|
228
|
+
if (!hasAssetExt) {
|
|
229
|
+
const rootIndex = path.join(cwd, 'index.html');
|
|
230
|
+
if (fsSync.existsSync(rootIndex)) {
|
|
231
|
+
const content = await fs.readFile(rootIndex);
|
|
232
|
+
res.writeHead(200, {
|
|
233
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
234
|
+
'Access-Control-Allow-Origin': '*'
|
|
235
|
+
});
|
|
236
|
+
res.end(content);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
236
239
|
}
|
|
237
240
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
238
241
|
res.end(`404 Not Found: ${reqPath}`);
|
package/dist/cli/theme.js
CHANGED
|
@@ -8,7 +8,7 @@ export const THEMES = {
|
|
|
8
8
|
colorFn: (s) => pc.cyan(s),
|
|
9
9
|
boldFn: (s) => pc.bold(pc.cyan(s)),
|
|
10
10
|
accentFn: (s) => pc.blue(s),
|
|
11
|
-
badgeFn: (s) => pc.bgCyan(pc.black(
|
|
11
|
+
badgeFn: (s) => pc.bgCyan(pc.black(s)),
|
|
12
12
|
diffAddBg: (s) => pc.bgCyan(pc.black(s)),
|
|
13
13
|
diffRemoveBg: (s) => pc.bgBlue(pc.white(s)),
|
|
14
14
|
hex: '#00f2fe'
|
|
@@ -21,7 +21,7 @@ export const THEMES = {
|
|
|
21
21
|
colorFn: (s) => pc.magenta(s),
|
|
22
22
|
boldFn: (s) => pc.bold(pc.magenta(s)),
|
|
23
23
|
accentFn: (s) => pc.blue(s),
|
|
24
|
-
badgeFn: (s) => pc.bgMagenta(pc.black(
|
|
24
|
+
badgeFn: (s) => pc.bgMagenta(pc.black(s)),
|
|
25
25
|
diffAddBg: (s) => pc.bgMagenta(pc.black(s)),
|
|
26
26
|
diffRemoveBg: (s) => pc.bgBlue(pc.white(s)),
|
|
27
27
|
hex: '#d946ef'
|
|
@@ -34,7 +34,7 @@ export const THEMES = {
|
|
|
34
34
|
colorFn: (s) => pc.green(s),
|
|
35
35
|
boldFn: (s) => pc.bold(pc.green(s)),
|
|
36
36
|
accentFn: (s) => pc.cyan(s),
|
|
37
|
-
badgeFn: (s) => pc.bgGreen(pc.black(
|
|
37
|
+
badgeFn: (s) => pc.bgGreen(pc.black(s)),
|
|
38
38
|
diffAddBg: (s) => pc.bgGreen(pc.black(s)),
|
|
39
39
|
diffRemoveBg: (s) => pc.bgRed(pc.white(s)),
|
|
40
40
|
hex: '#22c55e'
|
|
@@ -47,7 +47,7 @@ export const THEMES = {
|
|
|
47
47
|
colorFn: (s) => pc.yellow(s),
|
|
48
48
|
boldFn: (s) => pc.bold(pc.yellow(s)),
|
|
49
49
|
accentFn: (s) => pc.red(s),
|
|
50
|
-
badgeFn: (s) => pc.bgYellow(pc.black(
|
|
50
|
+
badgeFn: (s) => pc.bgYellow(pc.black(s)),
|
|
51
51
|
diffAddBg: (s) => pc.bgYellow(pc.black(s)),
|
|
52
52
|
diffRemoveBg: (s) => pc.bgRed(pc.white(s)),
|
|
53
53
|
hex: '#f59e0b'
|
|
@@ -60,7 +60,7 @@ export const THEMES = {
|
|
|
60
60
|
colorFn: (s) => pc.red(s),
|
|
61
61
|
boldFn: (s) => pc.bold(pc.red(s)),
|
|
62
62
|
accentFn: (s) => pc.magenta(s),
|
|
63
|
-
badgeFn: (s) => pc.bgRed(pc.white(
|
|
63
|
+
badgeFn: (s) => pc.bgRed(pc.white(s)),
|
|
64
64
|
diffAddBg: (s) => pc.bgRed(pc.white(s)),
|
|
65
65
|
diffRemoveBg: (s) => pc.bgMagenta(pc.white(s)),
|
|
66
66
|
hex: '#ef4444'
|
|
@@ -73,7 +73,7 @@ export const THEMES = {
|
|
|
73
73
|
colorFn: (s) => pc.white(s),
|
|
74
74
|
boldFn: (s) => pc.bold(pc.white(s)),
|
|
75
75
|
accentFn: (s) => pc.dim(s),
|
|
76
|
-
badgeFn: (s) => pc.bgWhite(pc.black(
|
|
76
|
+
badgeFn: (s) => pc.bgWhite(pc.black(s)),
|
|
77
77
|
diffAddBg: (s) => pc.bgWhite(pc.black(s)),
|
|
78
78
|
diffRemoveBg: (s) => pc.bgBlack(pc.white(s)),
|
|
79
79
|
hex: '#ffffff'
|
package/dist/core/commands.js
CHANGED
|
@@ -69,7 +69,7 @@ export class CustomCommandManager {
|
|
|
69
69
|
// Support positional parameters: $1, $2, etc.
|
|
70
70
|
const parts = trimmedArgs.split(/\s+/);
|
|
71
71
|
for (let i = 0; i < parts.length; i++) {
|
|
72
|
-
expanded = expanded.replace(new RegExp(`\\$${i + 1}`, 'g'), () => parts[i]);
|
|
72
|
+
expanded = expanded.replace(new RegExp(`\\$${i + 1}(?!\\d)`, 'g'), () => parts[i]);
|
|
73
73
|
}
|
|
74
74
|
return expanded;
|
|
75
75
|
}
|
package/dist/core/history.js
CHANGED
|
@@ -36,18 +36,36 @@ export class History {
|
|
|
36
36
|
const chatOnly = this.messages.filter(m => m.role !== 'system');
|
|
37
37
|
return this.estimateTokens(chatOnly);
|
|
38
38
|
}
|
|
39
|
-
// Эвристика: кол-во токенов = длина всех строк / 3.5
|
|
40
39
|
estimateTokens(messages) {
|
|
41
|
-
let
|
|
40
|
+
let tokens = 0;
|
|
41
|
+
const countStringTokens = (str) => {
|
|
42
|
+
if (!str)
|
|
43
|
+
return 0;
|
|
44
|
+
let t = 0;
|
|
45
|
+
for (let i = 0; i < str.length; i++) {
|
|
46
|
+
const code = str.charCodeAt(i);
|
|
47
|
+
if (code >= 0x0400 && code <= 0x04FF) {
|
|
48
|
+
t += 0.8;
|
|
49
|
+
}
|
|
50
|
+
else if (code > 0x07FF) {
|
|
51
|
+
t += 1.2;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
t += 0.25;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return t;
|
|
58
|
+
};
|
|
42
59
|
for (const m of messages) {
|
|
43
|
-
|
|
60
|
+
tokens += countStringTokens(m.content || '');
|
|
44
61
|
if (m.tool_calls) {
|
|
45
62
|
for (const tc of m.tool_calls) {
|
|
46
|
-
|
|
63
|
+
tokens += countStringTokens(tc.name);
|
|
64
|
+
tokens += countStringTokens(JSON.stringify(tc.arguments));
|
|
47
65
|
}
|
|
48
66
|
}
|
|
49
67
|
}
|
|
50
|
-
return Math.ceil(
|
|
68
|
+
return Math.ceil(tokens);
|
|
51
69
|
}
|
|
52
70
|
groupIntoBlocks(messages) {
|
|
53
71
|
const blocks = [];
|
package/dist/core/loop.js
CHANGED
|
@@ -159,8 +159,20 @@ export class Agent {
|
|
|
159
159
|
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
160
160
|
break;
|
|
161
161
|
}
|
|
162
|
+
const executedToolCallIds = new Set();
|
|
162
163
|
for (const call of response.toolCalls) {
|
|
163
164
|
if (signal?.aborted) {
|
|
165
|
+
// Add synthetic responses for ALL remaining unexecuted tool calls
|
|
166
|
+
for (const remaining of response.toolCalls) {
|
|
167
|
+
if (!executedToolCallIds.has(remaining.id)) {
|
|
168
|
+
this.history.addMessage({
|
|
169
|
+
role: 'tool',
|
|
170
|
+
content: '[Operation cancelled by user]',
|
|
171
|
+
tool_call_id: remaining.id,
|
|
172
|
+
name: remaining.name,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
164
176
|
return;
|
|
165
177
|
}
|
|
166
178
|
const actionDesc = getActionDescription(call.name, call.arguments);
|
|
@@ -197,6 +209,7 @@ export class Agent {
|
|
|
197
209
|
tool_call_id: call.id,
|
|
198
210
|
name: call.name,
|
|
199
211
|
});
|
|
212
|
+
executedToolCallIds.add(call.id);
|
|
200
213
|
}
|
|
201
214
|
iterations++;
|
|
202
215
|
}
|