termux-dev 1.9.7 → 1.9.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/README.md +1 -1
- package/assets/banner.svg +1 -1
- package/assets/preview.svg +1 -1
- package/dist/cli/index.js +14 -5
- package/dist/cli/keys.js +43 -4
- package/dist/cli/prompt.js +3 -3
- package/dist/cli/typeahead.js +9 -7
- package/dist/cli/update-screen.js +2 -3
- package/dist/cli/updater.js +18 -1
- package/dist/core/types.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -235,7 +235,7 @@ Type `/` in the prompt to open the autocomplete command palette:
|
|
|
235
235
|
- **`@filename`** — Fast fuzzy file autocomplete and full file embedding.
|
|
236
236
|
- **`Backspace`** — Atomically deletes entire badges `[Pasted text #1]` and `[1.png 203kb]` in one press.
|
|
237
237
|
- **`Ctrl+Backspace`** / **`Alt+Backspace`** — Erase the text word by word (also while the agent works).
|
|
238
|
-
- **`Ctrl+Enter`** — While typing: start a new line (a new paragraph) instead of sending. `Alt+Enter` and `Shift+Enter` do the same where the terminal reports them; on a terminal that cannot tell them from `Enter` (a phone keyboard, for one) type `\` before `Enter`.
|
|
238
|
+
- **`Ctrl+Enter`** — While typing: start a new line (a new paragraph) instead of sending. `Alt+Enter` and `Shift+Enter` do the same where the terminal reports them; on a terminal that cannot tell them from `Enter` (a phone keyboard, for one) type `\` before `Enter`. devx asks the terminal to tell them apart only where that is known to work (kitty, Ghostty, WezTerm, iTerm2, foot, Alacritty); Termux and other terminals that read that request as a cursor or style code never get it. `DEVX_EXTENDED_KEYS=1` (or `0`) forces it on (or off).
|
|
239
239
|
- **`Ctrl+P`** / **`Ctrl+O`** — Unfold or fold the reasoning of the model (`Ctrl+P`) and the commands with their output (`Ctrl+O`) in the whole chat, also what is already above. Both are folded by default; works while the agent works and at the prompt.
|
|
240
240
|
- **`Esc`** — Stop the current answer and whatever it started (a running command); what you typed waits in the next prompt. Esc also closes every list and card.
|
|
241
241
|
- **`Ctrl+C`** — Stop current generation immediately or exit.
|
package/assets/banner.svg
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<rect width="1000" height="280" fill="url(#bg)"/>
|
|
17
17
|
</g>
|
|
18
18
|
<rect x="1.5" y="1.5" width="997" height="277" rx="18" fill="none" stroke="#23252c" stroke-width="1.5"/>
|
|
19
|
-
<text x="500.0" y="70" text-anchor="middle" font-family="'Inter',system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" letter-spacing="5" fill="#7d8896">THE TERMINAL-NATIVE AI CODING AGENT<tspan dx="16" fill="#2fe3ee" font-weight="700">v1.9.
|
|
19
|
+
<text x="500.0" y="70" text-anchor="middle" font-family="'Inter',system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" letter-spacing="5" fill="#7d8896">THE TERMINAL-NATIVE AI CODING AGENT<tspan dx="16" fill="#2fe3ee" font-weight="700">v1.9.8</tspan></text>
|
|
20
20
|
<g>
|
|
21
21
|
<text x="500.0" y="178" text-anchor="middle" font-family="'JetBrains Mono','Fira Code','SF Mono',ui-monospace,Menlo,Consolas,'DejaVu Sans Mono',monospace" font-size="112" font-weight="800" letter-spacing="14" fill="#2fe3ee">DEVX</text>
|
|
22
22
|
</g>
|
package/assets/preview.svg
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<rect x="595.2" y="574.0" width="243.2" height="19" fill="#000000"/>
|
|
33
33
|
<text x="44.8" y="62.0" class="t" fill="#b794f6">▌</text>
|
|
34
34
|
<text x="61.6" y="62.0" class="t b" fill="#f4f7fb">devx</text>
|
|
35
|
-
<text x="112.0" y="62.0" class="t" fill="#5f6b7a">v1.9.
|
|
35
|
+
<text x="112.0" y="62.0" class="t" fill="#5f6b7a">v1.9.8</text>
|
|
36
36
|
<text x="61.6" y="106.0" class="t" fill="#5f6b7a">model</text>
|
|
37
37
|
<text x="128.8" y="106.0" class="t" fill="#e5eaf1">deepseek-v4.1-flash · think high</text>
|
|
38
38
|
<text x="406.0" y="106.0" class="t" fill="#5f6b7a">/model</text>
|
package/dist/cli/index.js
CHANGED
|
@@ -69,7 +69,7 @@ import { modelAcceptsImages } from '../core/vision.js';
|
|
|
69
69
|
import { contextBreakdown } from '../core/context-stats.js';
|
|
70
70
|
import { runUsagePanel } from './usage-panel.js';
|
|
71
71
|
import { TurnComposer } from './typeahead.js';
|
|
72
|
-
import {
|
|
72
|
+
import { extendedKeysOff, normalizeKey, splitKeys } from './keys.js';
|
|
73
73
|
import { LANGUAGES, getLanguage, setLanguage, t } from '../core/i18n.js';
|
|
74
74
|
import { anthropicOnlyModels, formatFromBaseUrl, refreshFormats } from '../core/formats.js';
|
|
75
75
|
import { providerFormat } from '../providers/index.js';
|
|
@@ -594,7 +594,7 @@ process.on('exit', () => {
|
|
|
594
594
|
process.stdin.setRawMode(false);
|
|
595
595
|
}
|
|
596
596
|
if (process.stdout.isTTY)
|
|
597
|
-
process.stdout.write(
|
|
597
|
+
process.stdout.write(extendedKeysOff() + '\x1B[?1004l' + RESTORE_TERMINAL_TITLE + '\x1B[?25h');
|
|
598
598
|
resetTerminalTheme();
|
|
599
599
|
});
|
|
600
600
|
process.on('SIGINT', () => {
|
|
@@ -606,11 +606,18 @@ process.on('SIGINT', () => {
|
|
|
606
606
|
process.stdin.setRawMode(false);
|
|
607
607
|
process.stdin.pause();
|
|
608
608
|
}
|
|
609
|
-
process.stdout.write(
|
|
609
|
+
process.stdout.write(extendedKeysOff() + '\x1B[?1004l' + RESTORE_TERMINAL_TITLE + '\x1B[?25h'); // restore cursor
|
|
610
610
|
resetTerminalTheme();
|
|
611
611
|
process.exit(0);
|
|
612
612
|
}
|
|
613
613
|
});
|
|
614
|
+
/**
|
|
615
|
+
* The new version has been started (it owns the terminal now) and this process ends when it ends.
|
|
616
|
+
* Nothing else may run here: a second prompt on the same terminal takes keys from the first.
|
|
617
|
+
*/
|
|
618
|
+
function waitForRestartedDevx() {
|
|
619
|
+
return new Promise(() => { });
|
|
620
|
+
}
|
|
614
621
|
function clearTerminalScreen() {
|
|
615
622
|
if (process.stdout.isTTY) {
|
|
616
623
|
// \x1b[2J: clear screen, \x1b[3J: clear scrollback buffer (crucial for Termux/xterm), \x1b[H: cursor to home
|
|
@@ -933,7 +940,8 @@ export async function main() {
|
|
|
933
940
|
MemoryManager.enabled = config.enableMemory !== false;
|
|
934
941
|
const sessionManager = new SessionManager(config.model, planMode);
|
|
935
942
|
drawLogo(config.banner || 'full');
|
|
936
|
-
await runStartupUpdateCheck(config, { save: saveConfig })
|
|
943
|
+
if ((await runStartupUpdateCheck(config, { save: saveConfig })) === 'updated')
|
|
944
|
+
await waitForRestartedDevx();
|
|
937
945
|
history.addMessage({ role: 'system', content: await buildSystemPrompt(planMode) });
|
|
938
946
|
// The servers start while the prompt is already there (starting them takes seconds: `npx` and the
|
|
939
947
|
// network). What needs them waits for `mcpReady`: the first request, and /mcp. When they are up the
|
|
@@ -1281,7 +1289,8 @@ export async function main() {
|
|
|
1281
1289
|
continue;
|
|
1282
1290
|
}
|
|
1283
1291
|
if (cmd === '/update') {
|
|
1284
|
-
await runManualUpdate()
|
|
1292
|
+
if ((await runManualUpdate()) === 'updated')
|
|
1293
|
+
await waitForRestartedDevx();
|
|
1285
1294
|
continue;
|
|
1286
1295
|
}
|
|
1287
1296
|
if (cmd === '/help') {
|
package/dist/cli/keys.js
CHANGED
|
@@ -28,11 +28,50 @@ export function isNewlineKey(key) {
|
|
|
28
28
|
* Asks the terminal to tell Ctrl+Enter and Shift+Enter apart from Enter (a plain terminal sends
|
|
29
29
|
* the same byte for all of them). Two protocols are requested, a terminal answers the one it knows:
|
|
30
30
|
* the kitty keyboard protocol (kitty, foot, ghostty, wezterm, alacritty, ...) and xterm's
|
|
31
|
-
* modifyOtherKeys.
|
|
32
|
-
*
|
|
31
|
+
* modifyOtherKeys. While on, the keys arrive as `CSI … u`: run them through `normalizeKey`.
|
|
32
|
+
*
|
|
33
|
+
* Only sent to terminals that are known to understand them (`extendedKeysSupported`): a terminal
|
|
34
|
+
* that does not parses `CSI > 1 u` as "restore the cursor" and `CSI > 4 ; 1 m` as "underline, bold",
|
|
35
|
+
* which throws the prompt to the top of the screen and draws lines under every row (Termux, UserLAnd).
|
|
36
|
+
*/
|
|
37
|
+
const EXTENDED_KEYS_ON_SEQ = '\x1b[>1u\x1b[>4;1m';
|
|
38
|
+
const EXTENDED_KEYS_OFF_SEQ = '\x1b[<u\x1b[>4;0m';
|
|
39
|
+
const KEYBOARD_PROTOCOL_PROGRAMS = new Set(['ghostty', 'wezterm', 'iterm.app', 'kitty', 'foot', 'alacritty', 'rio', 'contour']);
|
|
40
|
+
/**
|
|
41
|
+
* Does this terminal understand the extended keyboard modes? Decided from the environment, and
|
|
42
|
+
* only for terminals known to: a wrong guess in the other direction costs Ctrl+Enter (Alt+Enter
|
|
43
|
+
* and a backslash before Enter still break the line), a wrong guess in this one wrecks the screen.
|
|
44
|
+
* `DEVX_EXTENDED_KEYS=1` forces it on, `DEVX_EXTENDED_KEYS=0` off.
|
|
33
45
|
*/
|
|
34
|
-
export
|
|
35
|
-
|
|
46
|
+
export function extendedKeysSupported(env = process.env, platform = process.platform) {
|
|
47
|
+
const forced = (env.DEVX_EXTENDED_KEYS ?? '').trim().toLowerCase();
|
|
48
|
+
if (['1', 'on', 'true', 'yes'].includes(forced))
|
|
49
|
+
return true;
|
|
50
|
+
if (['0', 'off', 'false', 'no'].includes(forced))
|
|
51
|
+
return false;
|
|
52
|
+
// Android: Termux, UserLAnd and other terminals built on the same emulator.
|
|
53
|
+
if (platform === 'android' || env.TERMUX_VERSION || env.PREFIX?.includes('com.termux') || env.ANDROID_ROOT || env.ANDROID_DATA)
|
|
54
|
+
return false;
|
|
55
|
+
// Multiplexers sit between devx and the real terminal: they decide for themselves.
|
|
56
|
+
if (env.TMUX || env.STY)
|
|
57
|
+
return false;
|
|
58
|
+
const term = (env.TERM ?? '').toLowerCase();
|
|
59
|
+
if (term.startsWith('screen') || term.startsWith('tmux') || term === 'linux' || term === 'dumb')
|
|
60
|
+
return false;
|
|
61
|
+
if (env.KITTY_WINDOW_ID || env.GHOSTTY_RESOURCES_DIR || env.WEZTERM_EXECUTABLE)
|
|
62
|
+
return true;
|
|
63
|
+
if (/kitty|ghostty|foot|alacritty|wezterm/.test(term))
|
|
64
|
+
return true;
|
|
65
|
+
return KEYBOARD_PROTOCOL_PROGRAMS.has((env.TERM_PROGRAM ?? '').toLowerCase());
|
|
66
|
+
}
|
|
67
|
+
/** What to write to turn the extended keyboard modes on (nothing on a terminal that does not know them). */
|
|
68
|
+
export function extendedKeysOn() {
|
|
69
|
+
return extendedKeysSupported() ? EXTENDED_KEYS_ON_SEQ : '';
|
|
70
|
+
}
|
|
71
|
+
/** What to write to turn them off again. */
|
|
72
|
+
export function extendedKeysOff() {
|
|
73
|
+
return extendedKeysSupported() ? EXTENDED_KEYS_OFF_SEQ : '';
|
|
74
|
+
}
|
|
36
75
|
/**
|
|
37
76
|
* Turns the extended encodings back into the keys the rest of the code knows:
|
|
38
77
|
* `CSI 99;5u` and `CSI 27;5;99~` become Ctrl+C (0x03), `CSI 27u` becomes Esc, and every way
|
package/dist/cli/prompt.js
CHANGED
|
@@ -8,7 +8,7 @@ import { filterCommandPalette, estimatePromptTokens, formatPromptTokenBadge } fr
|
|
|
8
8
|
import { PromptHistoryNavigator } from './history_search.js';
|
|
9
9
|
import { buildPromptFrame, moveCursorRow, palette, renderUserEcho, resolveMode } from './ui.js';
|
|
10
10
|
import { displayWidth, padEndWidth, rowsToTop, truncateWidth } from './width.js';
|
|
11
|
-
import {
|
|
11
|
+
import { extendedKeysOff, extendedKeysOn, isNewlineKey, normalizeKey } from './keys.js';
|
|
12
12
|
export const SLASH_COMMANDS = [
|
|
13
13
|
{ cmd: '/new', desc: 'Start a new clean chat session' },
|
|
14
14
|
{ cmd: '/resume', desc: 'Resume a previous chat session' },
|
|
@@ -99,7 +99,7 @@ export function askPrompt(opts = {}) {
|
|
|
99
99
|
process.stdin.setRawMode(true);
|
|
100
100
|
process.stdout.write('\x1b[?2004h'); // Enable bracketed paste mode
|
|
101
101
|
// Lets the terminal tell Ctrl+Enter and Shift+Enter (a new paragraph) from Enter (send).
|
|
102
|
-
process.stdout.write(
|
|
102
|
+
process.stdout.write(extendedKeysOn());
|
|
103
103
|
}
|
|
104
104
|
// ── framed prompt (clean layout) ──
|
|
105
105
|
// The frame is a block of rows; the cursor rests on the input row. Redrawing moves up
|
|
@@ -552,7 +552,7 @@ export function askPrompt(opts = {}) {
|
|
|
552
552
|
disposed = true;
|
|
553
553
|
unsubscribeOwner?.();
|
|
554
554
|
if (process.stdout.isTTY) {
|
|
555
|
-
process.stdout.write('\x1b[?2004l' +
|
|
555
|
+
process.stdout.write('\x1b[?2004l' + extendedKeysOff());
|
|
556
556
|
}
|
|
557
557
|
process.stdout.removeListener('resize', onResize);
|
|
558
558
|
process.stdin.removeListener('data', onData);
|
package/dist/cli/typeahead.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import {
|
|
4
|
+
import { extendedKeysOff, extendedKeysOn, isNewlineKey, normalizeKey, splitKeys } from './keys.js';
|
|
5
5
|
import { buildPromptFrame, palette } from './ui.js';
|
|
6
6
|
import { displayWidth, truncateWidth } from './width.js';
|
|
7
7
|
import { t } from '../core/i18n.js';
|
|
@@ -209,7 +209,7 @@ export class TurnComposer {
|
|
|
209
209
|
this.shown = false;
|
|
210
210
|
this.original = this.out.write.bind(this.out);
|
|
211
211
|
this.out.write = ((chunk, encoding, cb) => this.through(chunk, encoding, cb));
|
|
212
|
-
this.write(
|
|
212
|
+
this.write(extendedKeysOn() + this.drawSeq());
|
|
213
213
|
// The working line animates: its time and counter are redrawn while the output is quiet.
|
|
214
214
|
if (this.status) {
|
|
215
215
|
this.timer = setInterval(() => this.tick(), 60);
|
|
@@ -225,7 +225,7 @@ export class TurnComposer {
|
|
|
225
225
|
this.timer = null;
|
|
226
226
|
}
|
|
227
227
|
if (this.active) {
|
|
228
|
-
this.write(this.eraseSeq() +
|
|
228
|
+
this.write(this.eraseSeq() + extendedKeysOff() + '\x1b[?25h');
|
|
229
229
|
if (this.original)
|
|
230
230
|
this.out.write = this.original;
|
|
231
231
|
this.original = null;
|
|
@@ -241,14 +241,14 @@ export class TurnComposer {
|
|
|
241
241
|
if (!this.active || this.suspended)
|
|
242
242
|
return;
|
|
243
243
|
// The card or question that takes over reads plain keys: the extended encodings go off for it.
|
|
244
|
-
this.write(this.eraseSeq() +
|
|
244
|
+
this.write(this.eraseSeq() + extendedKeysOff());
|
|
245
245
|
this.suspended = true;
|
|
246
246
|
}
|
|
247
247
|
resume() {
|
|
248
248
|
if (!this.active || !this.suspended)
|
|
249
249
|
return;
|
|
250
250
|
this.suspended = false;
|
|
251
|
-
this.write(
|
|
251
|
+
this.write(extendedKeysOn() + this.drawSeq());
|
|
252
252
|
}
|
|
253
253
|
/** Builds the frame again (the context percentage or the cost changed) and draws it. */
|
|
254
254
|
refresh() {
|
|
@@ -344,7 +344,9 @@ export class TurnComposer {
|
|
|
344
344
|
if (!this.shown)
|
|
345
345
|
return '';
|
|
346
346
|
this.shown = false;
|
|
347
|
-
|
|
347
|
+
// The erase starts from plain attributes: a style left on by the text above (an underline, a
|
|
348
|
+
// background) would be painted into every row it wipes (stripes across the screen).
|
|
349
|
+
return '\x1b7\x1b[0m\x1b[1B\r\x1b[J\x1b8';
|
|
348
350
|
}
|
|
349
351
|
/** Draws the frame under the cursor row and puts the cursor back where the text goes on. */
|
|
350
352
|
drawSeq() {
|
|
@@ -358,7 +360,7 @@ export class TurnComposer {
|
|
|
358
360
|
// ESC D moves down (scrolling at the bottom edge) and keeps the column; then back up.
|
|
359
361
|
let seq = '\x1b[?25l' + '\x1bD'.repeat(n) + `\x1b[${n}A` + '\x1b7';
|
|
360
362
|
for (const line of lines)
|
|
361
|
-
seq += `\x1b[1B\r\x1b[2K${line}\x1b[0m`;
|
|
363
|
+
seq += `\x1b[0m\x1b[1B\r\x1b[2K${line}\x1b[0m`;
|
|
362
364
|
return seq + '\x1b8';
|
|
363
365
|
}
|
|
364
366
|
}
|
|
@@ -350,14 +350,13 @@ export async function runStartupUpdateCheck(config, deps = {}) {
|
|
|
350
350
|
if (result.updateAvailable) {
|
|
351
351
|
if (config.skippedUpdate === result.latestVersion)
|
|
352
352
|
return;
|
|
353
|
-
|
|
353
|
+
return presentUpdate(result, {
|
|
354
354
|
offerSkip: true,
|
|
355
355
|
save: async (version) => {
|
|
356
356
|
config.skippedUpdate = version;
|
|
357
357
|
await deps.save?.(config);
|
|
358
358
|
},
|
|
359
359
|
}, deps);
|
|
360
|
-
return;
|
|
361
360
|
}
|
|
362
361
|
if (result.error)
|
|
363
362
|
return; // offline at start-up is not worth a word
|
|
@@ -375,7 +374,7 @@ export async function runManualUpdate(deps = {}) {
|
|
|
375
374
|
const result = await withCheckingLine(out, current, () => (deps.check ?? checkForUpdates)(10000));
|
|
376
375
|
const cols = columnsOf(out);
|
|
377
376
|
if (result.updateAvailable) {
|
|
378
|
-
|
|
377
|
+
return presentUpdate(result, { offerSkip: false }, deps);
|
|
379
378
|
}
|
|
380
379
|
else if (result.error) {
|
|
381
380
|
out.write(renderNotice('warn', t('update.offline', { reason: result.error }), cols) + '\n');
|
package/dist/cli/updater.js
CHANGED
|
@@ -280,9 +280,26 @@ export async function runUpdateSteps(steps, events = {}, now = Date.now) {
|
|
|
280
280
|
}
|
|
281
281
|
return { ok: true };
|
|
282
282
|
}
|
|
283
|
-
/**
|
|
283
|
+
/**
|
|
284
|
+
* Starts devx again, with the same arguments, and ends this process when the new one ends.
|
|
285
|
+
*
|
|
286
|
+
* From here on this process only waits: the terminal belongs to the new one. It stops reading the
|
|
287
|
+
* keyboard (two programs reading one terminal take the keys from each other, and a typed line
|
|
288
|
+
* can end up in the shell) and no longer reacts to Ctrl+C itself (the new devx gets it).
|
|
289
|
+
*/
|
|
284
290
|
export function restartDevx() {
|
|
291
|
+
try {
|
|
292
|
+
process.stdin.pause();
|
|
293
|
+
}
|
|
294
|
+
catch { }
|
|
285
295
|
const child = spawn(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
|
|
296
|
+
process.removeAllListeners('SIGINT');
|
|
297
|
+
process.on('SIGINT', () => { });
|
|
298
|
+
process.removeAllListeners('SIGTERM');
|
|
299
|
+
process.on('SIGTERM', () => { try {
|
|
300
|
+
child.kill('SIGTERM');
|
|
301
|
+
}
|
|
302
|
+
catch { } });
|
|
286
303
|
child.on('exit', (code) => process.exit(code ?? 0));
|
|
287
304
|
child.on('error', () => process.exit(0));
|
|
288
305
|
}
|
package/dist/core/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEVX_VERSION = '1.9.
|
|
1
|
+
export const DEVX_VERSION = '1.9.8';
|
package/package.json
CHANGED