termux-dev 1.4.19 → 1.5.0
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 +8 -0
- package/assets/banner.svg +1 -1
- package/assets/preview.png +0 -0
- package/bin/devx.js +0 -0
- package/dist/cli/banner.js +41 -0
- package/dist/cli/diff.js +64 -0
- package/dist/cli/doctor.js +2 -1
- package/dist/cli/headless.js +8 -5
- package/dist/cli/history_search.js +111 -0
- package/dist/cli/index.js +86 -106
- package/dist/cli/modeborder.js +31 -0
- package/dist/cli/prompt.js +66 -37
- package/dist/cli/prompt_palette.js +37 -0
- package/dist/cli/server.js +2 -1
- package/dist/cli/statusline.js +107 -0
- package/dist/cli/telemetry.js +105 -0
- package/dist/core/types.js +1 -1
- package/dist/mcp/client.js +2 -1
- package/dist/permissions/guard.js +9 -4
- package/dist/providers/openai.js +5 -4
- package/dist/tools/fs.js +41 -7
- package/dist/tools/packages.js +35 -11
- package/dist/tools/search.js +24 -5
- package/package.json +5 -2
- package/dist/cli/index (# Edit conflict 2026-08-29 pe1jzcC #).js +0 -1966
package/dist/cli/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import path from 'path';
|
|
|
7
7
|
import fs from 'fs/promises';
|
|
8
8
|
import fsSync from 'fs';
|
|
9
9
|
import { execSync, execFileSync } from 'child_process';
|
|
10
|
+
import { DEVX_VERSION } from '../core/types.js';
|
|
10
11
|
import { History } from '../core/history.js';
|
|
11
12
|
import { Agent } from '../core/loop.js';
|
|
12
13
|
import { buildSystemPrompt } from '../prompts/builder.js';
|
|
@@ -32,6 +33,10 @@ import { CustomCommandManager } from '../core/commands.js';
|
|
|
32
33
|
import { UsageTracker } from '../core/usage.js';
|
|
33
34
|
import { SessionExporter } from './export.js';
|
|
34
35
|
import { MCPManager } from '../mcp/manager.js';
|
|
36
|
+
import { LiveTelemetryTracker } from './telemetry.js';
|
|
37
|
+
import { renderPowerlineStatus, GitStatusCache } from './statusline.js';
|
|
38
|
+
import { formatDiffBox } from './diff.js';
|
|
39
|
+
import { drawBanner } from './banner.js';
|
|
35
40
|
const CONFIG_PATH = path.join(os.homedir(), '.devxrc.json');
|
|
36
41
|
function maskApiKey(key) {
|
|
37
42
|
if (!key)
|
|
@@ -473,40 +478,10 @@ function clearTerminalScreen() {
|
|
|
473
478
|
console.clear();
|
|
474
479
|
}
|
|
475
480
|
}
|
|
476
|
-
function drawLogo() {
|
|
481
|
+
function drawLogo(bannerMode = 'full') {
|
|
477
482
|
const cols = process.stdout.columns || 80;
|
|
478
483
|
clearTerminalScreen();
|
|
479
|
-
|
|
480
|
-
if (cols < 56) {
|
|
481
|
-
// Ultra-clean compact ASCII for small mobile screens (width: ~26 chars)
|
|
482
|
-
const logo = [
|
|
483
|
-
'',
|
|
484
|
-
theme.colorFn(' █▀▀▄ █▀▀▀ █ █ █ █'),
|
|
485
|
-
theme.colorFn(' █ █ █▀▀▀ ▀▄▀ ▀▄▀ '),
|
|
486
|
-
theme.colorFn(' █▄▄▀ █▄▄▄ ▀ ▀ ▀ '),
|
|
487
|
-
' ' + theme.boldFn('v1.4.19'),
|
|
488
|
-
''
|
|
489
|
-
];
|
|
490
|
-
for (const line of logo) {
|
|
491
|
-
console.log(line);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
else {
|
|
495
|
-
// Full TERMUX-DEV banner (width: 53 chars)
|
|
496
|
-
const indent = cols < 68 ? ' ' : ' ';
|
|
497
|
-
const logo = [
|
|
498
|
-
'',
|
|
499
|
-
indent + theme.colorFn('▀▀▀█▀▀▀ █▀▀▀ █▀▀█ █▄ ▄█ █ █ ▀▄ ▄▀ █▀▀▄ █▀▀▀ █ █'),
|
|
500
|
-
indent + theme.colorFn(' █ █▀▀▀ █▄▄▀ █ █ █ █ █ █ ▀▀ █ █ █▀▀▀ █ █'),
|
|
501
|
-
indent + theme.colorFn(' █ █▄▄▄ █ ▀▄ █ █ ▀▄▄▀ ▄▀ ▀▄ █▄▄▀ █▄▄▄ ▀▄▀ '),
|
|
502
|
-
indent + theme.boldFn('v1.4.19'),
|
|
503
|
-
''
|
|
504
|
-
];
|
|
505
|
-
for (const line of logo) {
|
|
506
|
-
console.log(line);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
console.log();
|
|
484
|
+
drawBanner(bannerMode, DEVX_VERSION, cols);
|
|
510
485
|
}
|
|
511
486
|
function formatSessionTime(timestamp) {
|
|
512
487
|
const diff = Date.now() - timestamp;
|
|
@@ -645,7 +620,7 @@ async function handleSettings(config) {
|
|
|
645
620
|
const maxIterLabel = maxIter >= 9999 ? 'Unlimited' : `${maxIter} steps`;
|
|
646
621
|
const currentTh = getCurrentTheme();
|
|
647
622
|
const choice = await select({
|
|
648
|
-
message: `${pc.bold('⚙️ Settings')} ${pc.dim(`(devx
|
|
623
|
+
message: `${pc.bold('⚙️ Settings')} ${pc.dim(`(devx v${DEVX_VERSION} • theme: ${currentTh.name})`)}`,
|
|
649
624
|
choices: [
|
|
650
625
|
{
|
|
651
626
|
name: `🎨 Color Theme: ${currentTh.emoji} ${currentTh.name}`,
|
|
@@ -686,7 +661,7 @@ async function handleSettings(config) {
|
|
|
686
661
|
description: 'Limit how many tool steps (file edits, terminal commands) agent can do per request'
|
|
687
662
|
},
|
|
688
663
|
{
|
|
689
|
-
name: `${currentTh.colorFn('✨ About devx')} ${pc.dim(
|
|
664
|
+
name: `${currentTh.colorFn('✨ About devx')} ${pc.dim(`(v${DEVX_VERSION} by ApvCode)`)}`,
|
|
690
665
|
value: 'about',
|
|
691
666
|
description: 'Terminal-Native AI Coding Agent created by ApvCode (https://github.com/apvcode/Termux-Dev)'
|
|
692
667
|
},
|
|
@@ -702,7 +677,7 @@ async function handleSettings(config) {
|
|
|
702
677
|
continue;
|
|
703
678
|
}
|
|
704
679
|
if (choice === 'about') {
|
|
705
|
-
p.note(`⚡ devx
|
|
680
|
+
p.note(`⚡ devx v${DEVX_VERSION} — Terminal-Native AI Coding Agent\n` +
|
|
706
681
|
`🎨 Theme: ${currentTh.emoji} ${currentTh.name}\n` +
|
|
707
682
|
`👤 Author: ApvCode (https://github.com/apvcode)\n` +
|
|
708
683
|
`🌟 Repository: https://github.com/apvcode/Termux-Dev\n` +
|
|
@@ -798,7 +773,7 @@ export async function main() {
|
|
|
798
773
|
program
|
|
799
774
|
.name('devx')
|
|
800
775
|
.description('Terminal-native AI coding assistant and vibe-coding agent')
|
|
801
|
-
.version(
|
|
776
|
+
.version(DEVX_VERSION)
|
|
802
777
|
.option('-p, --prompt <task>', 'Run one-shot task non-interactively (headless mode)')
|
|
803
778
|
.option('-y, --yolo', 'Automatically approve all tool executions without confirmation')
|
|
804
779
|
.option('-m, --model <model>', 'Specify AI model to use for this execution')
|
|
@@ -866,7 +841,7 @@ export async function main() {
|
|
|
866
841
|
const sysPrompt = await buildSystemPrompt(planMode);
|
|
867
842
|
history.addMessage({ role: 'system', content: sysPrompt });
|
|
868
843
|
const sessionManager = new SessionManager(config.model, planMode);
|
|
869
|
-
drawLogo();
|
|
844
|
+
drawLogo(config.banner || 'full');
|
|
870
845
|
await runStartupUpdateCheck(config);
|
|
871
846
|
await MCPManager.getInstance().init();
|
|
872
847
|
let totalSessionCost = 0;
|
|
@@ -889,23 +864,38 @@ export async function main() {
|
|
|
889
864
|
const cols = process.stdout.columns || 80;
|
|
890
865
|
const modeName = planMode ? 'PLAN' : 'AGENT';
|
|
891
866
|
const theme = getCurrentTheme();
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
p.intro(`${badge}\n${pc.dim('│')} ${pc.dim(tokenStats)}`);
|
|
867
|
+
if (config.ui?.powerlineStatus) {
|
|
868
|
+
const statusLine = renderPowerlineStatus({
|
|
869
|
+
mode: modeName,
|
|
870
|
+
model: config.model,
|
|
871
|
+
currentTokens,
|
|
872
|
+
maxTokens,
|
|
873
|
+
cost: totalSessionCost,
|
|
874
|
+
cols,
|
|
875
|
+
maxCostUSD: config.maxCostUSD,
|
|
876
|
+
isYolo: !!config.autoApprove
|
|
877
|
+
});
|
|
878
|
+
p.intro(statusLine);
|
|
905
879
|
}
|
|
906
880
|
else {
|
|
907
|
-
//
|
|
908
|
-
|
|
881
|
+
// Shorten model name if too long on narrow mobile screens
|
|
882
|
+
let displayModel = config.model;
|
|
883
|
+
if (cols < 75 && displayModel.length > 20) {
|
|
884
|
+
const parts = displayModel.split('/');
|
|
885
|
+
displayModel = parts.length > 1 ? parts.slice(1).join('/') : displayModel;
|
|
886
|
+
if (displayModel.length > 20) {
|
|
887
|
+
displayModel = displayModel.slice(0, 17) + '...';
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
const badge = theme.badgeFn(`devx | ${modeName} | ${displayModel}`);
|
|
891
|
+
if (cols < 75) {
|
|
892
|
+
// 2-line layout for mobile screens: perfectly aligned with clack box borders
|
|
893
|
+
p.intro(`${badge}\n${pc.dim('│')} ${pc.dim(tokenStats)}`);
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
// 1-line layout for wider desktop screens
|
|
897
|
+
p.intro(`${badge} ${pc.dim(tokenStats)}`);
|
|
898
|
+
}
|
|
909
899
|
}
|
|
910
900
|
let answer = '';
|
|
911
901
|
if (autoTriggerPrompt) {
|
|
@@ -918,7 +908,9 @@ export async function main() {
|
|
|
918
908
|
message: 'Ask anything...',
|
|
919
909
|
placeholder: 'Fix a TODO, type /help, or press Tab to switch mode',
|
|
920
910
|
initialValue: currentDraft,
|
|
921
|
-
planMode
|
|
911
|
+
planMode,
|
|
912
|
+
isYolo: !!config.autoApprove,
|
|
913
|
+
ui: config.ui
|
|
922
914
|
});
|
|
923
915
|
if (inputStr.startsWith('__TOGGLE_MODE__:')) {
|
|
924
916
|
currentDraft = inputStr.slice('__TOGGLE_MODE__:'.length);
|
|
@@ -1615,30 +1607,35 @@ export async function main() {
|
|
|
1615
1607
|
};
|
|
1616
1608
|
const agent = new Agent(agentConfig, provider, tools, history, guard);
|
|
1617
1609
|
const taskStartTime = Date.now();
|
|
1618
|
-
const
|
|
1619
|
-
|
|
1620
|
-
let
|
|
1610
|
+
const abortController = new AbortController();
|
|
1611
|
+
let aborted = false;
|
|
1612
|
+
let isConnecting = true;
|
|
1613
|
+
let spinnerActive = false;
|
|
1621
1614
|
let thinkingActive = false;
|
|
1622
1615
|
let thinkingStartTime = 0;
|
|
1623
1616
|
let textActive = false;
|
|
1617
|
+
const s = p.spinner();
|
|
1624
1618
|
const streamer = new MarkdownStreamer();
|
|
1625
1619
|
const thoughtStreamer = new SmoothStreamer((chunk) => {
|
|
1626
1620
|
process.stdout.write(pc.dim(chunk));
|
|
1627
1621
|
});
|
|
1622
|
+
const telemetry = new LiveTelemetryTracker();
|
|
1623
|
+
telemetry.start('connecting');
|
|
1628
1624
|
const finishThinking = (extraNewline = true) => {
|
|
1629
1625
|
if (!thinkingActive)
|
|
1630
1626
|
return;
|
|
1631
1627
|
thoughtStreamer.flush();
|
|
1632
1628
|
const elapsedSec = thinkingStartTime > 0 ? ((Date.now() - thinkingStartTime) / 1000).toFixed(1) : '0.0';
|
|
1629
|
+
const teleStats = config.ui?.liveTelemetry && telemetry.getTotalTokens() > 0
|
|
1630
|
+
? ` · ${LiveTelemetryTracker.formatTokenCount(telemetry.getTotalTokens())} tok · ${telemetry.getTokensPerSecond()} tok/s`
|
|
1631
|
+
: '';
|
|
1633
1632
|
const width = Math.min(process.stdout.columns || 40, 52);
|
|
1634
|
-
const label = `─── Thought for ${elapsedSec}s `;
|
|
1633
|
+
const label = `─── Thought for ${elapsedSec}s${teleStats} `;
|
|
1635
1634
|
const fillLen = Math.max(2, width - label.length);
|
|
1636
1635
|
process.stdout.write('\n\n' + pc.dim(label + '─'.repeat(fillLen)) + (extraNewline ? '\n\n' : '\n'));
|
|
1637
1636
|
thinkingActive = false;
|
|
1638
1637
|
thinkingStartTime = 0;
|
|
1639
1638
|
};
|
|
1640
|
-
const abortController = new AbortController();
|
|
1641
|
-
let aborted = false;
|
|
1642
1639
|
const stopGeneration = () => {
|
|
1643
1640
|
if (aborted)
|
|
1644
1641
|
return;
|
|
@@ -1648,13 +1645,19 @@ export async function main() {
|
|
|
1648
1645
|
s.stop();
|
|
1649
1646
|
spinnerActive = false;
|
|
1650
1647
|
}
|
|
1648
|
+
telemetry.reset();
|
|
1651
1649
|
thoughtStreamer.reset();
|
|
1652
1650
|
finishThinking(true);
|
|
1653
1651
|
streamer.finish();
|
|
1654
1652
|
if (textActive) {
|
|
1655
1653
|
process.stdout.write('\n');
|
|
1656
1654
|
}
|
|
1657
|
-
|
|
1655
|
+
if (isConnecting) {
|
|
1656
|
+
console.log(pc.yellow('\n⏹ Подключение отменено / Connection cancelled (Ctrl+C).'));
|
|
1657
|
+
}
|
|
1658
|
+
else {
|
|
1659
|
+
console.log(pc.yellow('\n⏹ Generation stopped / Ответ остановлен (Ctrl+C).'));
|
|
1660
|
+
}
|
|
1658
1661
|
};
|
|
1659
1662
|
const onSigInt = () => {
|
|
1660
1663
|
stopGeneration();
|
|
@@ -1675,6 +1678,8 @@ export async function main() {
|
|
|
1675
1678
|
}
|
|
1676
1679
|
catch { }
|
|
1677
1680
|
}
|
|
1681
|
+
s.start('Connecting...');
|
|
1682
|
+
spinnerActive = true;
|
|
1678
1683
|
const turnStartTime = Date.now();
|
|
1679
1684
|
let toolsExecutedCount = 0;
|
|
1680
1685
|
try {
|
|
@@ -1682,6 +1687,9 @@ export async function main() {
|
|
|
1682
1687
|
if (aborted)
|
|
1683
1688
|
break;
|
|
1684
1689
|
if (event.type === 'reasoning_delta') {
|
|
1690
|
+
isConnecting = false;
|
|
1691
|
+
telemetry.setPhase('thinking');
|
|
1692
|
+
telemetry.recordTokens(telemetry.estimateTokensFromText(event.delta));
|
|
1685
1693
|
if (spinnerActive) {
|
|
1686
1694
|
s.stop();
|
|
1687
1695
|
spinnerActive = false;
|
|
@@ -1694,6 +1702,9 @@ export async function main() {
|
|
|
1694
1702
|
thoughtStreamer.push(event.delta);
|
|
1695
1703
|
}
|
|
1696
1704
|
else if (event.type === 'text_delta') {
|
|
1705
|
+
isConnecting = false;
|
|
1706
|
+
telemetry.setPhase('generating');
|
|
1707
|
+
telemetry.recordTokens(telemetry.estimateTokensFromText(event.delta));
|
|
1697
1708
|
if (spinnerActive) {
|
|
1698
1709
|
s.stop();
|
|
1699
1710
|
spinnerActive = false;
|
|
@@ -1705,6 +1716,7 @@ export async function main() {
|
|
|
1705
1716
|
streamer.push(event.delta);
|
|
1706
1717
|
}
|
|
1707
1718
|
else if (event.type === 'text') {
|
|
1719
|
+
isConnecting = false;
|
|
1708
1720
|
if (spinnerActive) {
|
|
1709
1721
|
s.stop();
|
|
1710
1722
|
spinnerActive = false;
|
|
@@ -1715,19 +1727,27 @@ export async function main() {
|
|
|
1715
1727
|
}
|
|
1716
1728
|
}
|
|
1717
1729
|
else if (event.type === 'tool_generating') {
|
|
1730
|
+
isConnecting = false;
|
|
1731
|
+
telemetry.setPhase('tool');
|
|
1718
1732
|
const theme = getCurrentTheme();
|
|
1719
1733
|
const label = formatToolGeneratingLabel(event.name, event.targetHint);
|
|
1734
|
+
const teleStats = telemetry.getTotalTokens() > 0
|
|
1735
|
+
? ` · ${LiveTelemetryTracker.formatTokenCount(telemetry.getTotalTokens())} tok · ${telemetry.getTokensPerSecond()} tok/s`
|
|
1736
|
+
: '';
|
|
1737
|
+
const telemetrySuffix = config.ui?.liveTelemetry ? ` ${pc.dim(`(${telemetry.getElapsedSeconds()}s${teleStats})`)}` : '';
|
|
1720
1738
|
if (!spinnerActive) {
|
|
1721
1739
|
streamer.finish();
|
|
1722
1740
|
finishThinking(false);
|
|
1723
|
-
s.start(theme.boldFn(label));
|
|
1741
|
+
s.start(theme.boldFn(label) + telemetrySuffix);
|
|
1724
1742
|
spinnerActive = true;
|
|
1725
1743
|
}
|
|
1726
1744
|
else {
|
|
1727
|
-
s.message(theme.boldFn(label));
|
|
1745
|
+
s.message(theme.boldFn(label) + telemetrySuffix);
|
|
1728
1746
|
}
|
|
1729
1747
|
}
|
|
1730
1748
|
else if (event.type === 'tool_start') {
|
|
1749
|
+
isConnecting = false;
|
|
1750
|
+
telemetry.reset();
|
|
1731
1751
|
if (spinnerActive) {
|
|
1732
1752
|
s.stop();
|
|
1733
1753
|
spinnerActive = false;
|
|
@@ -1746,6 +1766,7 @@ export async function main() {
|
|
|
1746
1766
|
}
|
|
1747
1767
|
else if (event.type === 'tool_end') {
|
|
1748
1768
|
toolsExecutedCount++;
|
|
1769
|
+
GitStatusCache.invalidate();
|
|
1749
1770
|
if (spinnerActive) {
|
|
1750
1771
|
s.stop();
|
|
1751
1772
|
spinnerActive = false;
|
|
@@ -1757,50 +1778,8 @@ export async function main() {
|
|
|
1757
1778
|
process.stdout.write(parsed.displayCard);
|
|
1758
1779
|
}
|
|
1759
1780
|
else if (parsed.action === 'edit' && parsed.diffLines) {
|
|
1760
|
-
const theme = getCurrentTheme();
|
|
1761
1781
|
const cols = Math.min(process.stdout.columns || 80, 80);
|
|
1762
|
-
|
|
1763
|
-
const fileName = typeof parsed.path === 'string' ? parsed.path.split(/[\/\\]/).pop() || 'file' : 'file';
|
|
1764
|
-
const fillCount = Math.max(2, boxWidth - 5 - fileName.length);
|
|
1765
|
-
console.log(theme.colorFn('┌─ ') + pc.bold(fileName) + ' ' + theme.colorFn('─'.repeat(fillCount) + '┐'));
|
|
1766
|
-
const maxShown = Math.min(parsed.diffLines.length, 30);
|
|
1767
|
-
for (let i = 0; i < maxShown; i++) {
|
|
1768
|
-
const line = parsed.diffLines[i];
|
|
1769
|
-
const match = line.match(/^(\d+)(\s+[+\- ]\s+)(.*)$/);
|
|
1770
|
-
if (match) {
|
|
1771
|
-
const lineNum = match[1].padStart(4, ' ');
|
|
1772
|
-
const symbol = match[2];
|
|
1773
|
-
const contentStr = match[3] || '';
|
|
1774
|
-
const prefix = symbol.includes('-') ? '-' : symbol.includes('+') ? '+' : ' ';
|
|
1775
|
-
const maxCodeLen = Math.max(10, boxWidth - 11);
|
|
1776
|
-
const paddedCode = contentStr.length > maxCodeLen
|
|
1777
|
-
? contentStr.substring(0, maxCodeLen - 1) + '…'
|
|
1778
|
-
: contentStr.padEnd(maxCodeLen, ' ');
|
|
1779
|
-
const innerRow = ` ${lineNum} ${prefix} ${paddedCode} `;
|
|
1780
|
-
if (prefix === '-') {
|
|
1781
|
-
console.log(theme.colorFn('│') + theme.diffRemoveBg(innerRow) + theme.colorFn('│'));
|
|
1782
|
-
}
|
|
1783
|
-
else if (prefix === '+') {
|
|
1784
|
-
console.log(theme.colorFn('│') + theme.diffAddBg(innerRow) + theme.colorFn('│'));
|
|
1785
|
-
}
|
|
1786
|
-
else {
|
|
1787
|
-
console.log(theme.colorFn('│') + pc.dim(innerRow) + theme.colorFn('│'));
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1790
|
-
else {
|
|
1791
|
-
const maxLen = Math.max(10, boxWidth - 4);
|
|
1792
|
-
const padded = line.length > maxLen ? line.substring(0, maxLen - 1) + '…' : line.padEnd(maxLen, ' ');
|
|
1793
|
-
console.log(theme.colorFn('│ ') + pc.white(padded) + theme.colorFn(' │'));
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
if (parsed.diffLines.length > maxShown) {
|
|
1797
|
-
const dots = `... +${parsed.diffLines.length - maxShown} more lines`;
|
|
1798
|
-
const maxLen = Math.max(10, boxWidth - 4);
|
|
1799
|
-
const paddedDots = dots.length > maxLen ? dots.substring(0, maxLen - 1) + '…' : dots.padEnd(maxLen, ' ');
|
|
1800
|
-
console.log(theme.colorFn('│ ') + pc.dim(paddedDots) + theme.colorFn(' │'));
|
|
1801
|
-
}
|
|
1802
|
-
console.log(theme.colorFn('└' + '─'.repeat(boxWidth - 2) + '┘'));
|
|
1803
|
-
console.log(theme.boldFn(` └─ ${parsed.summary}`));
|
|
1782
|
+
console.log(formatDiffBox(parsed.path, parsed.diffLines, cols, parsed.summary, !!config.ui?.diffColors));
|
|
1804
1783
|
}
|
|
1805
1784
|
else if (parsed.summary) {
|
|
1806
1785
|
console.log(pc.green(` └─ ${parsed.summary}`));
|
|
@@ -1824,6 +1803,7 @@ export async function main() {
|
|
|
1824
1803
|
totalSessionCost += event.usage.cost || 0;
|
|
1825
1804
|
}
|
|
1826
1805
|
else if (event.type === 'reconnecting') {
|
|
1806
|
+
telemetry.reset();
|
|
1827
1807
|
if (spinnerActive) {
|
|
1828
1808
|
s.stop();
|
|
1829
1809
|
spinnerActive = false;
|
|
@@ -1878,7 +1858,6 @@ export async function main() {
|
|
|
1878
1858
|
if (textActive) {
|
|
1879
1859
|
process.stdout.write('\n');
|
|
1880
1860
|
}
|
|
1881
|
-
globalSnapshotManager.finishTurn();
|
|
1882
1861
|
const turnElapsedMs = Date.now() - turnStartTime;
|
|
1883
1862
|
const shouldNotify = (turnElapsedMs >= 15000) || (toolsExecutedCount > 0) || (planMode && lastPlanReady !== null);
|
|
1884
1863
|
if (shouldNotify && !aborted) {
|
|
@@ -1947,6 +1926,7 @@ export async function main() {
|
|
|
1947
1926
|
}
|
|
1948
1927
|
}
|
|
1949
1928
|
finally {
|
|
1929
|
+
globalSnapshotManager.finishTurn();
|
|
1950
1930
|
activeAbortHandler = null;
|
|
1951
1931
|
process.removeListener('SIGINT', onSigInt);
|
|
1952
1932
|
if (process.stdin.isTTY) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
export function getModeIndicator(planMode, isYolo = false) {
|
|
3
|
+
if (isYolo) {
|
|
4
|
+
return {
|
|
5
|
+
mode: 'YOLO',
|
|
6
|
+
icon: pc.red('◆'),
|
|
7
|
+
badge: pc.bgRed(pc.bold(pc.white(' YOLO '))),
|
|
8
|
+
accentFn: pc.red
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
if (planMode) {
|
|
12
|
+
return {
|
|
13
|
+
mode: 'PLAN',
|
|
14
|
+
icon: pc.cyan('◆'),
|
|
15
|
+
badge: pc.bgCyan(pc.bold(pc.black(' PLAN '))),
|
|
16
|
+
accentFn: pc.cyan
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
mode: 'AGENT',
|
|
21
|
+
icon: pc.green('◆'),
|
|
22
|
+
badge: pc.bgGreen(pc.bold(pc.black(' AGENT '))),
|
|
23
|
+
accentFn: pc.green
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function formatPromptHeader(planMode, isYolo = false, customMessage) {
|
|
27
|
+
const indicator = getModeIndicator(planMode, isYolo);
|
|
28
|
+
const targetMode = planMode ? 'AGENT' : 'PLAN';
|
|
29
|
+
const msg = customMessage || 'Ask anything...';
|
|
30
|
+
return `${indicator.icon} ${pc.bold(msg)} ${pc.dim(`(Tab = ${targetMode})`)}`;
|
|
31
|
+
}
|
package/dist/cli/prompt.js
CHANGED
|
@@ -3,6 +3,9 @@ import { scanProjectFiles } from './files.js';
|
|
|
3
3
|
import { saveClipboardImage, processPastedFilePath } from './clipboard.js';
|
|
4
4
|
import { getCurrentTheme } from './theme.js';
|
|
5
5
|
import { CustomCommandManager } from '../core/commands.js';
|
|
6
|
+
import { formatPromptHeader } from './modeborder.js';
|
|
7
|
+
import { filterCommandPalette, estimatePromptTokens, formatPromptTokenBadge } from './prompt_palette.js';
|
|
8
|
+
import { PromptHistoryNavigator } from './history_search.js';
|
|
6
9
|
export const SLASH_COMMANDS = [
|
|
7
10
|
{ cmd: '/new', desc: 'Start a new clean chat session' },
|
|
8
11
|
{ cmd: '/resume', desc: 'Resume a previous chat session' },
|
|
@@ -39,11 +42,17 @@ function stripAnsi(str) {
|
|
|
39
42
|
}
|
|
40
43
|
export function askPrompt(opts = {}) {
|
|
41
44
|
return new Promise((resolve) => {
|
|
42
|
-
const msg = opts.message
|
|
43
|
-
? `${opts.message} ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`
|
|
44
|
-
: `Ask anything... ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`;
|
|
45
45
|
const theme = getCurrentTheme();
|
|
46
|
-
|
|
46
|
+
if (opts.ui?.modeBorder) {
|
|
47
|
+
const header = formatPromptHeader(!!opts.planMode, !!opts.isYolo, opts.message);
|
|
48
|
+
console.log(header);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const msg = opts.message
|
|
52
|
+
? `${opts.message} ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`
|
|
53
|
+
: `Ask anything... ${pc.dim(`(Tab = ${opts.planMode ? 'AGENT' : 'PLAN'})`)}`;
|
|
54
|
+
console.log(theme.colorFn('◆') + ' ' + pc.bold(msg));
|
|
55
|
+
}
|
|
47
56
|
let availableFiles = [];
|
|
48
57
|
let customCommandsList = [];
|
|
49
58
|
scanProjectFiles().then((f) => (availableFiles = f)).catch(() => { });
|
|
@@ -54,8 +63,14 @@ export function askPrompt(opts = {}) {
|
|
|
54
63
|
let cursorPos = input.length;
|
|
55
64
|
let selectedIndex = 0;
|
|
56
65
|
let lastRenderedDropdownLines = 0;
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
const historyNav = new PromptHistoryNavigator(GLOBAL_PROMPT_HISTORY, !!opts.ui?.promptHistoryFuzzy);
|
|
67
|
+
PromptHistoryNavigator.loadPersistent().then((persisted) => {
|
|
68
|
+
for (const p of persisted) {
|
|
69
|
+
if (!GLOBAL_PROMPT_HISTORY.includes(p)) {
|
|
70
|
+
GLOBAL_PROMPT_HISTORY.push(p);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}).catch(() => { });
|
|
59
74
|
const imageAttachments = [];
|
|
60
75
|
const usedImageNames = new Set();
|
|
61
76
|
const pastedBlocks = [];
|
|
@@ -133,18 +148,34 @@ export function askPrompt(opts = {}) {
|
|
|
133
148
|
function getDropdownItems() {
|
|
134
149
|
if (input.startsWith('/')) {
|
|
135
150
|
const q = input.trim().toLowerCase();
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
151
|
+
if (opts.ui?.commandPalette) {
|
|
152
|
+
const baseList = [
|
|
153
|
+
...SLASH_COMMANDS.map((c) => ({ cmd: c.cmd, desc: c.desc })),
|
|
154
|
+
...customCommandsList.map((c) => ({ cmd: c.cmd, desc: `(custom) ${c.desc}` }))
|
|
155
|
+
];
|
|
156
|
+
const filtered = filterCommandPalette(input, baseList).slice(0, 15);
|
|
157
|
+
return filtered.map((item) => ({
|
|
158
|
+
label: item.cmd,
|
|
159
|
+
desc: item.desc,
|
|
160
|
+
replacement: `${item.cmd} `,
|
|
161
|
+
replaceStart: 0,
|
|
162
|
+
replaceLen: input.length
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const baseList = [
|
|
167
|
+
...SLASH_COMMANDS.map((c) => ({ label: c.cmd, desc: c.desc, replacement: c.cmd })),
|
|
168
|
+
...customCommandsList.map((c) => ({ label: c.cmd, desc: `(custom) ${c.desc}`, replacement: c.cmd }))
|
|
169
|
+
];
|
|
170
|
+
const filtered = baseList.filter((c) => c.label.toLowerCase().startsWith(q) || q === '/');
|
|
171
|
+
return filtered.map((item) => ({
|
|
172
|
+
label: item.label,
|
|
173
|
+
desc: item.desc,
|
|
174
|
+
replacement: item.replacement,
|
|
175
|
+
replaceStart: 0,
|
|
176
|
+
replaceLen: input.length
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
148
179
|
}
|
|
149
180
|
const beforeCursor = input.slice(0, cursorPos);
|
|
150
181
|
const atMatch = beforeCursor.match(/@([a-zA-Z0-9_\-./\u0400-\u04FF]*)$/);
|
|
@@ -252,9 +283,11 @@ export function askPrompt(opts = {}) {
|
|
|
252
283
|
renderCursorCol = 3;
|
|
253
284
|
}
|
|
254
285
|
else {
|
|
255
|
-
const
|
|
286
|
+
const tokenBadge = opts.ui?.inputTokenCounter ? formatPromptTokenBadge(estimatePromptTokens(input), 40) : '';
|
|
287
|
+
const badgeSuffix = tokenBadge ? ` ${tokenBadge}` : '';
|
|
288
|
+
const maxInputLen = Math.max(10, cols - 6 - (tokenBadge ? 12 : 0));
|
|
256
289
|
if (input.length <= maxInputLen) {
|
|
257
|
-
inputDisplay += formatInputWithBadges(input) + '\x1b[0m';
|
|
290
|
+
inputDisplay += formatInputWithBadges(input) + badgeSuffix + '\x1b[0m';
|
|
258
291
|
renderCursorCol = 3 + cursorPos;
|
|
259
292
|
}
|
|
260
293
|
else {
|
|
@@ -263,7 +296,7 @@ export function askPrompt(opts = {}) {
|
|
|
263
296
|
const visibleChunk = input.slice(start, start + maxInputLen);
|
|
264
297
|
const prefix = start > 0 ? '…' : '';
|
|
265
298
|
const suffix = start + maxInputLen < input.length ? '…' : '';
|
|
266
|
-
inputDisplay += pc.dim(prefix) + formatInputWithBadges(visibleChunk) + pc.dim(suffix) + '\x1b[0m';
|
|
299
|
+
inputDisplay += pc.dim(prefix) + formatInputWithBadges(visibleChunk) + pc.dim(suffix) + badgeSuffix + '\x1b[0m';
|
|
267
300
|
renderCursorCol = 3 + (prefix ? 1 : 0) + (cursorPos - start);
|
|
268
301
|
}
|
|
269
302
|
}
|
|
@@ -473,14 +506,13 @@ export function askPrompt(opts = {}) {
|
|
|
473
506
|
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
474
507
|
render();
|
|
475
508
|
}
|
|
476
|
-
else
|
|
477
|
-
|
|
478
|
-
|
|
509
|
+
else {
|
|
510
|
+
const prev = historyNav.navigateUp(input);
|
|
511
|
+
if (prev !== null) {
|
|
512
|
+
input = prev;
|
|
513
|
+
cursorPos = input.length;
|
|
514
|
+
render();
|
|
479
515
|
}
|
|
480
|
-
historyIndex--;
|
|
481
|
-
input = GLOBAL_PROMPT_HISTORY[historyIndex];
|
|
482
|
-
cursorPos = input.length;
|
|
483
|
-
render();
|
|
484
516
|
}
|
|
485
517
|
return;
|
|
486
518
|
}
|
|
@@ -490,16 +522,13 @@ export function askPrompt(opts = {}) {
|
|
|
490
522
|
selectedIndex = (selectedIndex + 1) % items.length;
|
|
491
523
|
render();
|
|
492
524
|
}
|
|
493
|
-
else
|
|
494
|
-
|
|
495
|
-
if (
|
|
496
|
-
input =
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
input = GLOBAL_PROMPT_HISTORY[historyIndex];
|
|
525
|
+
else {
|
|
526
|
+
const next = historyNav.navigateDown();
|
|
527
|
+
if (next !== null) {
|
|
528
|
+
input = next;
|
|
529
|
+
cursorPos = input.length;
|
|
530
|
+
render();
|
|
500
531
|
}
|
|
501
|
-
cursorPos = input.length;
|
|
502
|
-
render();
|
|
503
532
|
}
|
|
504
533
|
return;
|
|
505
534
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
export function filterCommandPalette(query, commands) {
|
|
3
|
+
if (!query || query === '/') {
|
|
4
|
+
return [...commands];
|
|
5
|
+
}
|
|
6
|
+
const q = query.toLowerCase().replace(/^\//, '');
|
|
7
|
+
return commands.filter((c) => {
|
|
8
|
+
const name = c.cmd.toLowerCase().replace(/^\//, '');
|
|
9
|
+
return name.includes(q) || c.desc.toLowerCase().includes(q);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function estimatePromptTokens(text) {
|
|
13
|
+
if (!text)
|
|
14
|
+
return 0;
|
|
15
|
+
let tokens = 0;
|
|
16
|
+
for (let i = 0; i < text.length; i++) {
|
|
17
|
+
const code = text.charCodeAt(i);
|
|
18
|
+
if (code >= 0x0400 && code <= 0x04ff) {
|
|
19
|
+
tokens += 0.8;
|
|
20
|
+
}
|
|
21
|
+
else if (code > 0x07ff) {
|
|
22
|
+
tokens += 1.2;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
tokens += 0.25;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return Math.max(1, Math.round(tokens));
|
|
29
|
+
}
|
|
30
|
+
export function formatPromptTokenBadge(tokens, threshold = 30) {
|
|
31
|
+
if (tokens < threshold)
|
|
32
|
+
return '';
|
|
33
|
+
if (tokens >= 1000) {
|
|
34
|
+
return pc.dim(`(~${(tokens / 1000).toFixed(1)}k tok)`);
|
|
35
|
+
}
|
|
36
|
+
return pc.dim(`(~${tokens} tok)`);
|
|
37
|
+
}
|
package/dist/cli/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { spawn } from 'child_process';
|
|
|
7
7
|
import pc from 'picocolors';
|
|
8
8
|
import qrcode from 'qrcode-terminal';
|
|
9
9
|
import { getTheme } from './theme.js';
|
|
10
|
+
import { DEVX_VERSION } from '../core/types.js';
|
|
10
11
|
const MIME_TYPES = {
|
|
11
12
|
'.html': 'text/html; charset=utf-8',
|
|
12
13
|
'.htm': 'text/html; charset=utf-8',
|
|
@@ -139,7 +140,7 @@ function renderDirectoryHtml(dirPath, relPath, files, port) {
|
|
|
139
140
|
${parentLink}
|
|
140
141
|
${items || '<li style="padding: 20px; text-align: center; color: #6e7681;">No visible files in this directory</li>'}
|
|
141
142
|
</ul>
|
|
142
|
-
<div class="footer">devx
|
|
143
|
+
<div class="footer">devx v${DEVX_VERSION} • Terminal-Native AI Assistant</div>
|
|
143
144
|
</div>
|
|
144
145
|
</body>
|
|
145
146
|
</html>`;
|