termux-dev 1.4.19 → 1.6.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 +15 -3
- package/assets/banner.svg +1 -1
- package/assets/preview.png +0 -0
- package/assets/preview.svg +12 -0
- package/bin/devx.js +0 -0
- package/dist/cli/banner.js +45 -0
- package/dist/cli/diff.js +64 -0
- package/dist/cli/doctor.js +2 -1
- package/dist/cli/headless.js +14 -8
- package/dist/cli/history_search.js +111 -0
- package/dist/cli/index.js +137 -133
- package/dist/cli/modeborder.js +31 -0
- package/dist/cli/prompt.js +138 -92
- package/dist/cli/prompt_palette.js +43 -0
- package/dist/cli/server.js +2 -1
- package/dist/cli/statusline.js +133 -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 +8 -3
- package/assets/preview_work1.png +0 -0
- 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 { renderCleanStatus, 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;
|
|
@@ -548,11 +523,11 @@ function formatToolGeneratingLabel(name, targetHint) {
|
|
|
548
523
|
return `→ Clarifying questions...`;
|
|
549
524
|
return `→ ${name}${target}...`;
|
|
550
525
|
}
|
|
551
|
-
async function handleSessionDelete() {
|
|
526
|
+
async function handleSessionDelete(bannerMode) {
|
|
552
527
|
while (true) {
|
|
553
528
|
const sessions = await SessionManager.listSessions();
|
|
554
529
|
if (sessions.length === 0) {
|
|
555
|
-
drawLogo();
|
|
530
|
+
drawLogo(bannerMode);
|
|
556
531
|
p.log.warn('No saved sessions found.');
|
|
557
532
|
break;
|
|
558
533
|
}
|
|
@@ -587,7 +562,7 @@ async function handleSessionDelete() {
|
|
|
587
562
|
break;
|
|
588
563
|
}
|
|
589
564
|
if (!chosenSessionId || chosenSessionId === '__cancel__') {
|
|
590
|
-
drawLogo();
|
|
565
|
+
drawLogo(bannerMode);
|
|
591
566
|
p.log.info('Session deletion finished.');
|
|
592
567
|
break;
|
|
593
568
|
}
|
|
@@ -598,7 +573,7 @@ async function handleSessionDelete() {
|
|
|
598
573
|
});
|
|
599
574
|
if (!p.isCancel(confirmAll) && confirmAll) {
|
|
600
575
|
const count = await SessionManager.deleteAllSessions();
|
|
601
|
-
drawLogo();
|
|
576
|
+
drawLogo(bannerMode);
|
|
602
577
|
p.log.success(`Deleted all ${count} sessions.`);
|
|
603
578
|
break;
|
|
604
579
|
}
|
|
@@ -607,7 +582,7 @@ async function handleSessionDelete() {
|
|
|
607
582
|
const target = sessions.find(s => s.id === chosenSessionId);
|
|
608
583
|
const title = target ? target.title : chosenSessionId;
|
|
609
584
|
const ok = await SessionManager.deleteSession(chosenSessionId);
|
|
610
|
-
drawLogo();
|
|
585
|
+
drawLogo(bannerMode);
|
|
611
586
|
if (ok) {
|
|
612
587
|
p.log.success(`Deleted session: "${title}"`);
|
|
613
588
|
}
|
|
@@ -632,7 +607,7 @@ async function handleThemeSelect(config) {
|
|
|
632
607
|
config.theme = selected;
|
|
633
608
|
await saveConfig(config);
|
|
634
609
|
const th = setActiveTheme(selected);
|
|
635
|
-
drawLogo();
|
|
610
|
+
drawLogo(config.banner || 'full');
|
|
636
611
|
p.log.success(th.boldFn(`🎨 Theme switched to ${th.emoji} ${th.name}!`));
|
|
637
612
|
}
|
|
638
613
|
}
|
|
@@ -645,13 +620,18 @@ 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}`,
|
|
652
627
|
value: 'change_theme',
|
|
653
628
|
description: `Switch UI accent colors (${currentTh.desc})`
|
|
654
629
|
},
|
|
630
|
+
{
|
|
631
|
+
name: `Interface: ${config.ui?.layout === 'classic' ? 'Classic' : 'Clean'}`,
|
|
632
|
+
value: 'toggle_layout',
|
|
633
|
+
description: 'Switch between the compact layout and the original terminal UI'
|
|
634
|
+
},
|
|
655
635
|
{
|
|
656
636
|
name: `${config.pureBlackTheme !== false ? pc.green('🖤 Pure Black Background: ON') : pc.yellow('🖤 Pure Black Background: OFF')}`,
|
|
657
637
|
value: 'toggle_black_theme',
|
|
@@ -686,7 +666,7 @@ async function handleSettings(config) {
|
|
|
686
666
|
description: 'Limit how many tool steps (file edits, terminal commands) agent can do per request'
|
|
687
667
|
},
|
|
688
668
|
{
|
|
689
|
-
name: `${currentTh.colorFn('✨ About devx')} ${pc.dim(
|
|
669
|
+
name: `${currentTh.colorFn('✨ About devx')} ${pc.dim(`(v${DEVX_VERSION} by ApvCode)`)}`,
|
|
690
670
|
value: 'about',
|
|
691
671
|
description: 'Terminal-Native AI Coding Agent created by ApvCode (https://github.com/apvcode/Termux-Dev)'
|
|
692
672
|
},
|
|
@@ -701,8 +681,16 @@ async function handleSettings(config) {
|
|
|
701
681
|
await handleThemeSelect(config);
|
|
702
682
|
continue;
|
|
703
683
|
}
|
|
684
|
+
if (choice === 'toggle_layout') {
|
|
685
|
+
config.ui = config.ui || {};
|
|
686
|
+
config.ui.layout = config.ui.layout === 'classic' ? 'clean' : 'classic';
|
|
687
|
+
config.banner = config.ui.layout === 'clean' ? 'clean' : 'full';
|
|
688
|
+
await saveConfig(config);
|
|
689
|
+
drawLogo(config.banner);
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
704
692
|
if (choice === 'about') {
|
|
705
|
-
p.note(`⚡ devx
|
|
693
|
+
p.note(`⚡ devx v${DEVX_VERSION} — Terminal-Native AI Coding Agent\n` +
|
|
706
694
|
`🎨 Theme: ${currentTh.emoji} ${currentTh.name}\n` +
|
|
707
695
|
`👤 Author: ApvCode (https://github.com/apvcode)\n` +
|
|
708
696
|
`🌟 Repository: https://github.com/apvcode/Termux-Dev\n` +
|
|
@@ -719,7 +707,7 @@ async function handleSettings(config) {
|
|
|
719
707
|
else {
|
|
720
708
|
resetTerminalTheme();
|
|
721
709
|
}
|
|
722
|
-
drawLogo();
|
|
710
|
+
drawLogo(config.banner || 'full');
|
|
723
711
|
p.log.success(`Pure Black background: ${config.pureBlackTheme ? pc.bold(pc.green('ON (Deep Black)')) : pc.bold(pc.yellow('OFF (System Default)'))}`);
|
|
724
712
|
continue;
|
|
725
713
|
}
|
|
@@ -798,7 +786,7 @@ export async function main() {
|
|
|
798
786
|
program
|
|
799
787
|
.name('devx')
|
|
800
788
|
.description('Terminal-native AI coding assistant and vibe-coding agent')
|
|
801
|
-
.version(
|
|
789
|
+
.version(DEVX_VERSION)
|
|
802
790
|
.option('-p, --prompt <task>', 'Run one-shot task non-interactively (headless mode)')
|
|
803
791
|
.option('-y, --yolo', 'Automatically approve all tool executions without confirmation')
|
|
804
792
|
.option('-m, --model <model>', 'Specify AI model to use for this execution')
|
|
@@ -810,6 +798,9 @@ export async function main() {
|
|
|
810
798
|
const planModeInitial = !!options.plan;
|
|
811
799
|
const isHeadless = !!options.prompt;
|
|
812
800
|
let config = await loadConfig(!isHeadless);
|
|
801
|
+
config.banner = config.ui?.layout === 'classic'
|
|
802
|
+
? config.banner || 'full'
|
|
803
|
+
: config.banner === 'off' ? 'off' : 'clean';
|
|
813
804
|
if (options.model) {
|
|
814
805
|
config.model = options.model;
|
|
815
806
|
config.maxContextTokens = getModelContextLimit(options.model);
|
|
@@ -866,7 +857,7 @@ export async function main() {
|
|
|
866
857
|
const sysPrompt = await buildSystemPrompt(planMode);
|
|
867
858
|
history.addMessage({ role: 'system', content: sysPrompt });
|
|
868
859
|
const sessionManager = new SessionManager(config.model, planMode);
|
|
869
|
-
drawLogo();
|
|
860
|
+
drawLogo(config.banner || 'full');
|
|
870
861
|
await runStartupUpdateCheck(config);
|
|
871
862
|
await MCPManager.getInstance().init();
|
|
872
863
|
let totalSessionCost = 0;
|
|
@@ -889,36 +880,68 @@ export async function main() {
|
|
|
889
880
|
const cols = process.stdout.columns || 80;
|
|
890
881
|
const modeName = planMode ? 'PLAN' : 'AGENT';
|
|
891
882
|
const theme = getCurrentTheme();
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
883
|
+
if (config.ui?.layout !== 'classic') {
|
|
884
|
+
console.log(renderCleanStatus({
|
|
885
|
+
mode: modeName,
|
|
886
|
+
model: config.model,
|
|
887
|
+
currentTokens,
|
|
888
|
+
maxTokens,
|
|
889
|
+
cost: totalSessionCost,
|
|
890
|
+
cols,
|
|
891
|
+
isYolo: !!config.autoApprove
|
|
892
|
+
}) + '\n');
|
|
900
893
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
894
|
+
else if (config.ui?.powerlineStatus) {
|
|
895
|
+
const statusLine = renderPowerlineStatus({
|
|
896
|
+
mode: modeName,
|
|
897
|
+
model: config.model,
|
|
898
|
+
currentTokens,
|
|
899
|
+
maxTokens,
|
|
900
|
+
cost: totalSessionCost,
|
|
901
|
+
cols,
|
|
902
|
+
maxCostUSD: config.maxCostUSD,
|
|
903
|
+
isYolo: !!config.autoApprove
|
|
904
|
+
});
|
|
905
|
+
p.intro(statusLine);
|
|
905
906
|
}
|
|
906
907
|
else {
|
|
907
|
-
//
|
|
908
|
-
|
|
908
|
+
// Shorten model name if too long on narrow mobile screens
|
|
909
|
+
let displayModel = config.model;
|
|
910
|
+
if (cols < 75 && displayModel.length > 20) {
|
|
911
|
+
const parts = displayModel.split('/');
|
|
912
|
+
displayModel = parts.length > 1 ? parts.slice(1).join('/') : displayModel;
|
|
913
|
+
if (displayModel.length > 20) {
|
|
914
|
+
displayModel = displayModel.slice(0, 17) + '...';
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
const badge = theme.badgeFn(`devx | ${modeName} | ${displayModel}`);
|
|
918
|
+
if (cols < 75) {
|
|
919
|
+
// 2-line layout for mobile screens: perfectly aligned with clack box borders
|
|
920
|
+
p.intro(`${badge}\n${pc.dim('│')} ${pc.dim(tokenStats)}`);
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
// 1-line layout for wider desktop screens
|
|
924
|
+
p.intro(`${badge} ${pc.dim(tokenStats)}`);
|
|
925
|
+
}
|
|
909
926
|
}
|
|
910
927
|
let answer = '';
|
|
911
928
|
if (autoTriggerPrompt) {
|
|
912
929
|
answer = autoTriggerPrompt;
|
|
913
930
|
autoTriggerPrompt = '';
|
|
914
|
-
console.log(
|
|
931
|
+
console.log(config.ui?.layout === 'classic'
|
|
932
|
+
? theme.colorFn('◆') + ' ' + pc.bold(pc.white(answer))
|
|
933
|
+
: pc.cyan('›') + ' ' + answer);
|
|
915
934
|
}
|
|
916
935
|
else {
|
|
917
936
|
const inputStr = await askPrompt({
|
|
918
937
|
message: 'Ask anything...',
|
|
919
|
-
placeholder:
|
|
938
|
+
placeholder: config.ui?.layout === 'classic'
|
|
939
|
+
? 'Fix a TODO, type /help, or press Tab to switch mode'
|
|
940
|
+
: 'Ask or describe a task…',
|
|
920
941
|
initialValue: currentDraft,
|
|
921
|
-
planMode
|
|
942
|
+
planMode,
|
|
943
|
+
isYolo: !!config.autoApprove,
|
|
944
|
+
ui: config.ui
|
|
922
945
|
});
|
|
923
946
|
if (inputStr.startsWith('__TOGGLE_MODE__:')) {
|
|
924
947
|
currentDraft = inputStr.slice('__TOGGLE_MODE__:'.length);
|
|
@@ -1022,7 +1045,7 @@ export async function main() {
|
|
|
1022
1045
|
continue;
|
|
1023
1046
|
}
|
|
1024
1047
|
if (picked === '/session del') {
|
|
1025
|
-
await handleSessionDelete();
|
|
1048
|
+
await handleSessionDelete(config.banner || 'full');
|
|
1026
1049
|
continue;
|
|
1027
1050
|
}
|
|
1028
1051
|
cmd = picked;
|
|
@@ -1034,7 +1057,7 @@ export async function main() {
|
|
|
1034
1057
|
}
|
|
1035
1058
|
if (cmd === '/settings') {
|
|
1036
1059
|
config = await handleSettings(config);
|
|
1037
|
-
drawLogo();
|
|
1060
|
+
drawLogo(config.banner || 'full');
|
|
1038
1061
|
continue;
|
|
1039
1062
|
}
|
|
1040
1063
|
if (cmd === '/update') {
|
|
@@ -1066,9 +1089,7 @@ export async function main() {
|
|
|
1066
1089
|
break;
|
|
1067
1090
|
}
|
|
1068
1091
|
if (cmd === '/clear') {
|
|
1069
|
-
|
|
1070
|
-
history.addMessage({ role: 'system', content: await buildSystemPrompt(planMode) });
|
|
1071
|
-
p.log.success('History cleared.');
|
|
1092
|
+
drawLogo(config.banner || 'full');
|
|
1072
1093
|
continue;
|
|
1073
1094
|
}
|
|
1074
1095
|
if (cmd === '/new' || cmd === '/reset') {
|
|
@@ -1076,7 +1097,7 @@ export async function main() {
|
|
|
1076
1097
|
history = new History();
|
|
1077
1098
|
history.addMessage({ role: 'system', content: await buildSystemPrompt(planMode) });
|
|
1078
1099
|
totalSessionCost = 0;
|
|
1079
|
-
drawLogo();
|
|
1100
|
+
drawLogo(config.banner || 'full');
|
|
1080
1101
|
const initialPrompt = parts.slice(1).join(' ').trim();
|
|
1081
1102
|
if (initialPrompt) {
|
|
1082
1103
|
p.log.success('Started new session with prompt.');
|
|
@@ -1090,7 +1111,7 @@ export async function main() {
|
|
|
1090
1111
|
else if (cmd === '/session' || cmd === '/sessions' || cmd === '/resume' || cmd === '/history') {
|
|
1091
1112
|
const sub = (parts[1] || '').toLowerCase();
|
|
1092
1113
|
if (sub === 'del' || sub === 'delete' || sub === 'rm') {
|
|
1093
|
-
await handleSessionDelete();
|
|
1114
|
+
await handleSessionDelete(config.banner || 'full');
|
|
1094
1115
|
continue;
|
|
1095
1116
|
}
|
|
1096
1117
|
const sessions = await SessionManager.listSessions();
|
|
@@ -1132,7 +1153,7 @@ export async function main() {
|
|
|
1132
1153
|
continue;
|
|
1133
1154
|
}
|
|
1134
1155
|
if (chosenSessionId === '__delete_mode__') {
|
|
1135
|
-
await handleSessionDelete();
|
|
1156
|
+
await handleSessionDelete(config.banner || 'full');
|
|
1136
1157
|
continue;
|
|
1137
1158
|
}
|
|
1138
1159
|
const loaded = await SessionManager.loadSession(chosenSessionId);
|
|
@@ -1150,7 +1171,7 @@ export async function main() {
|
|
|
1150
1171
|
planMode = loaded.planMode;
|
|
1151
1172
|
}
|
|
1152
1173
|
totalSessionCost = loaded.totalCost || 0;
|
|
1153
|
-
drawLogo();
|
|
1174
|
+
drawLogo(config.banner || 'full');
|
|
1154
1175
|
const nonSysMessages = loaded.messages.filter(m => m.role !== 'system');
|
|
1155
1176
|
const totalMsgs = nonSysMessages.length;
|
|
1156
1177
|
const displayLimit = 20;
|
|
@@ -1202,7 +1223,7 @@ export async function main() {
|
|
|
1202
1223
|
planMode = true;
|
|
1203
1224
|
const newSys = await buildSystemPrompt(true);
|
|
1204
1225
|
history.updateSystemPrompt(newSys);
|
|
1205
|
-
drawLogo();
|
|
1226
|
+
drawLogo(config.banner || 'full');
|
|
1206
1227
|
p.log.success(pc.cyan('Switched to PLAN mode (Architect & Planner). Modifying tools are disabled.'));
|
|
1207
1228
|
continue;
|
|
1208
1229
|
}
|
|
@@ -1210,7 +1231,7 @@ export async function main() {
|
|
|
1210
1231
|
planMode = false;
|
|
1211
1232
|
const newSys = await buildSystemPrompt(false);
|
|
1212
1233
|
history.updateSystemPrompt(newSys);
|
|
1213
|
-
drawLogo();
|
|
1234
|
+
drawLogo(config.banner || 'full');
|
|
1214
1235
|
p.log.success(pc.green('Switched to AGENT mode (Coder & Executor). Full tools enabled.'));
|
|
1215
1236
|
continue;
|
|
1216
1237
|
}
|
|
@@ -1374,11 +1395,6 @@ export async function main() {
|
|
|
1374
1395
|
}
|
|
1375
1396
|
continue;
|
|
1376
1397
|
}
|
|
1377
|
-
if (cmd === '/clear') {
|
|
1378
|
-
console.clear();
|
|
1379
|
-
drawLogo();
|
|
1380
|
-
continue;
|
|
1381
|
-
}
|
|
1382
1398
|
if (cmd === '/session' && parts[1]?.toLowerCase() !== 'del') {
|
|
1383
1399
|
const cur = sessionManager.getSession();
|
|
1384
1400
|
const nonSys = history.getMessages().filter(m => m.role !== 'system');
|
|
@@ -1440,7 +1456,7 @@ export async function main() {
|
|
|
1440
1456
|
config.theme = matched.id;
|
|
1441
1457
|
await saveConfig(config);
|
|
1442
1458
|
const th = setActiveTheme(matched.id);
|
|
1443
|
-
drawLogo();
|
|
1459
|
+
drawLogo(config.banner || 'full');
|
|
1444
1460
|
p.log.success(th.boldFn(`🎨 Theme switched to ${th.emoji} ${th.name}!`));
|
|
1445
1461
|
}
|
|
1446
1462
|
else {
|
|
@@ -1526,7 +1542,7 @@ export async function main() {
|
|
|
1526
1542
|
}
|
|
1527
1543
|
if (cmd === '/model') {
|
|
1528
1544
|
const res = await selectModel(config.baseUrl, config.apiKey, config.model, config.provider || '', true);
|
|
1529
|
-
drawLogo();
|
|
1545
|
+
drawLogo(config.banner || 'full');
|
|
1530
1546
|
if (res) {
|
|
1531
1547
|
config.model = res.model;
|
|
1532
1548
|
config.maxContextTokens = res.contextLimit;
|
|
@@ -1540,7 +1556,7 @@ export async function main() {
|
|
|
1540
1556
|
}
|
|
1541
1557
|
if (cmd === '/provider' || cmd === '/providers') {
|
|
1542
1558
|
const newConfig = await selectProvider(config, true);
|
|
1543
|
-
drawLogo();
|
|
1559
|
+
drawLogo(config.banner || 'full');
|
|
1544
1560
|
if (newConfig) {
|
|
1545
1561
|
config = newConfig;
|
|
1546
1562
|
p.log.success(`Switched provider to: ${pc.bold(config.provider || 'custom')} (${config.model})`);
|
|
@@ -1615,30 +1631,35 @@ export async function main() {
|
|
|
1615
1631
|
};
|
|
1616
1632
|
const agent = new Agent(agentConfig, provider, tools, history, guard);
|
|
1617
1633
|
const taskStartTime = Date.now();
|
|
1618
|
-
const
|
|
1619
|
-
|
|
1620
|
-
let
|
|
1634
|
+
const abortController = new AbortController();
|
|
1635
|
+
let aborted = false;
|
|
1636
|
+
let isConnecting = true;
|
|
1637
|
+
let spinnerActive = false;
|
|
1621
1638
|
let thinkingActive = false;
|
|
1622
1639
|
let thinkingStartTime = 0;
|
|
1623
1640
|
let textActive = false;
|
|
1641
|
+
const s = p.spinner();
|
|
1624
1642
|
const streamer = new MarkdownStreamer();
|
|
1625
1643
|
const thoughtStreamer = new SmoothStreamer((chunk) => {
|
|
1626
1644
|
process.stdout.write(pc.dim(chunk));
|
|
1627
1645
|
});
|
|
1646
|
+
const telemetry = new LiveTelemetryTracker();
|
|
1647
|
+
telemetry.start('connecting');
|
|
1628
1648
|
const finishThinking = (extraNewline = true) => {
|
|
1629
1649
|
if (!thinkingActive)
|
|
1630
1650
|
return;
|
|
1631
1651
|
thoughtStreamer.flush();
|
|
1632
1652
|
const elapsedSec = thinkingStartTime > 0 ? ((Date.now() - thinkingStartTime) / 1000).toFixed(1) : '0.0';
|
|
1653
|
+
const teleStats = config.ui?.liveTelemetry && telemetry.getTotalTokens() > 0
|
|
1654
|
+
? ` · ${LiveTelemetryTracker.formatTokenCount(telemetry.getTotalTokens())} tok · ${telemetry.getTokensPerSecond()} tok/s`
|
|
1655
|
+
: '';
|
|
1633
1656
|
const width = Math.min(process.stdout.columns || 40, 52);
|
|
1634
|
-
const label = `─── Thought for ${elapsedSec}s `;
|
|
1657
|
+
const label = `─── Thought for ${elapsedSec}s${teleStats} `;
|
|
1635
1658
|
const fillLen = Math.max(2, width - label.length);
|
|
1636
1659
|
process.stdout.write('\n\n' + pc.dim(label + '─'.repeat(fillLen)) + (extraNewline ? '\n\n' : '\n'));
|
|
1637
1660
|
thinkingActive = false;
|
|
1638
1661
|
thinkingStartTime = 0;
|
|
1639
1662
|
};
|
|
1640
|
-
const abortController = new AbortController();
|
|
1641
|
-
let aborted = false;
|
|
1642
1663
|
const stopGeneration = () => {
|
|
1643
1664
|
if (aborted)
|
|
1644
1665
|
return;
|
|
@@ -1648,13 +1669,19 @@ export async function main() {
|
|
|
1648
1669
|
s.stop();
|
|
1649
1670
|
spinnerActive = false;
|
|
1650
1671
|
}
|
|
1672
|
+
telemetry.reset();
|
|
1651
1673
|
thoughtStreamer.reset();
|
|
1652
1674
|
finishThinking(true);
|
|
1653
1675
|
streamer.finish();
|
|
1654
1676
|
if (textActive) {
|
|
1655
1677
|
process.stdout.write('\n');
|
|
1656
1678
|
}
|
|
1657
|
-
|
|
1679
|
+
if (isConnecting) {
|
|
1680
|
+
console.log(pc.yellow('\n⏹ Подключение отменено / Connection cancelled (Ctrl+C).'));
|
|
1681
|
+
}
|
|
1682
|
+
else {
|
|
1683
|
+
console.log(pc.yellow('\n⏹ Generation stopped / Ответ остановлен (Ctrl+C).'));
|
|
1684
|
+
}
|
|
1658
1685
|
};
|
|
1659
1686
|
const onSigInt = () => {
|
|
1660
1687
|
stopGeneration();
|
|
@@ -1675,6 +1702,8 @@ export async function main() {
|
|
|
1675
1702
|
}
|
|
1676
1703
|
catch { }
|
|
1677
1704
|
}
|
|
1705
|
+
s.start('Connecting...');
|
|
1706
|
+
spinnerActive = true;
|
|
1678
1707
|
const turnStartTime = Date.now();
|
|
1679
1708
|
let toolsExecutedCount = 0;
|
|
1680
1709
|
try {
|
|
@@ -1682,6 +1711,9 @@ export async function main() {
|
|
|
1682
1711
|
if (aborted)
|
|
1683
1712
|
break;
|
|
1684
1713
|
if (event.type === 'reasoning_delta') {
|
|
1714
|
+
isConnecting = false;
|
|
1715
|
+
telemetry.setPhase('thinking');
|
|
1716
|
+
telemetry.recordTokens(telemetry.estimateTokensFromText(event.delta));
|
|
1685
1717
|
if (spinnerActive) {
|
|
1686
1718
|
s.stop();
|
|
1687
1719
|
spinnerActive = false;
|
|
@@ -1694,6 +1726,9 @@ export async function main() {
|
|
|
1694
1726
|
thoughtStreamer.push(event.delta);
|
|
1695
1727
|
}
|
|
1696
1728
|
else if (event.type === 'text_delta') {
|
|
1729
|
+
isConnecting = false;
|
|
1730
|
+
telemetry.setPhase('generating');
|
|
1731
|
+
telemetry.recordTokens(telemetry.estimateTokensFromText(event.delta));
|
|
1697
1732
|
if (spinnerActive) {
|
|
1698
1733
|
s.stop();
|
|
1699
1734
|
spinnerActive = false;
|
|
@@ -1705,6 +1740,7 @@ export async function main() {
|
|
|
1705
1740
|
streamer.push(event.delta);
|
|
1706
1741
|
}
|
|
1707
1742
|
else if (event.type === 'text') {
|
|
1743
|
+
isConnecting = false;
|
|
1708
1744
|
if (spinnerActive) {
|
|
1709
1745
|
s.stop();
|
|
1710
1746
|
spinnerActive = false;
|
|
@@ -1715,19 +1751,27 @@ export async function main() {
|
|
|
1715
1751
|
}
|
|
1716
1752
|
}
|
|
1717
1753
|
else if (event.type === 'tool_generating') {
|
|
1754
|
+
isConnecting = false;
|
|
1755
|
+
telemetry.setPhase('tool');
|
|
1718
1756
|
const theme = getCurrentTheme();
|
|
1719
1757
|
const label = formatToolGeneratingLabel(event.name, event.targetHint);
|
|
1758
|
+
const teleStats = telemetry.getTotalTokens() > 0
|
|
1759
|
+
? ` · ${LiveTelemetryTracker.formatTokenCount(telemetry.getTotalTokens())} tok · ${telemetry.getTokensPerSecond()} tok/s`
|
|
1760
|
+
: '';
|
|
1761
|
+
const telemetrySuffix = config.ui?.liveTelemetry ? ` ${pc.dim(`(${telemetry.getElapsedSeconds()}s${teleStats})`)}` : '';
|
|
1720
1762
|
if (!spinnerActive) {
|
|
1721
1763
|
streamer.finish();
|
|
1722
1764
|
finishThinking(false);
|
|
1723
|
-
s.start(theme.boldFn(label));
|
|
1765
|
+
s.start(theme.boldFn(label) + telemetrySuffix);
|
|
1724
1766
|
spinnerActive = true;
|
|
1725
1767
|
}
|
|
1726
1768
|
else {
|
|
1727
|
-
s.message(theme.boldFn(label));
|
|
1769
|
+
s.message(theme.boldFn(label) + telemetrySuffix);
|
|
1728
1770
|
}
|
|
1729
1771
|
}
|
|
1730
1772
|
else if (event.type === 'tool_start') {
|
|
1773
|
+
isConnecting = false;
|
|
1774
|
+
telemetry.reset();
|
|
1731
1775
|
if (spinnerActive) {
|
|
1732
1776
|
s.stop();
|
|
1733
1777
|
spinnerActive = false;
|
|
@@ -1746,6 +1790,7 @@ export async function main() {
|
|
|
1746
1790
|
}
|
|
1747
1791
|
else if (event.type === 'tool_end') {
|
|
1748
1792
|
toolsExecutedCount++;
|
|
1793
|
+
GitStatusCache.invalidate();
|
|
1749
1794
|
if (spinnerActive) {
|
|
1750
1795
|
s.stop();
|
|
1751
1796
|
spinnerActive = false;
|
|
@@ -1757,50 +1802,8 @@ export async function main() {
|
|
|
1757
1802
|
process.stdout.write(parsed.displayCard);
|
|
1758
1803
|
}
|
|
1759
1804
|
else if (parsed.action === 'edit' && parsed.diffLines) {
|
|
1760
|
-
const theme = getCurrentTheme();
|
|
1761
1805
|
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}`));
|
|
1806
|
+
console.log(formatDiffBox(parsed.path, parsed.diffLines, cols, parsed.summary, !!config.ui?.diffColors));
|
|
1804
1807
|
}
|
|
1805
1808
|
else if (parsed.summary) {
|
|
1806
1809
|
console.log(pc.green(` └─ ${parsed.summary}`));
|
|
@@ -1824,6 +1827,7 @@ export async function main() {
|
|
|
1824
1827
|
totalSessionCost += event.usage.cost || 0;
|
|
1825
1828
|
}
|
|
1826
1829
|
else if (event.type === 'reconnecting') {
|
|
1830
|
+
telemetry.reset();
|
|
1827
1831
|
if (spinnerActive) {
|
|
1828
1832
|
s.stop();
|
|
1829
1833
|
spinnerActive = false;
|
|
@@ -1878,7 +1882,6 @@ export async function main() {
|
|
|
1878
1882
|
if (textActive) {
|
|
1879
1883
|
process.stdout.write('\n');
|
|
1880
1884
|
}
|
|
1881
|
-
globalSnapshotManager.finishTurn();
|
|
1882
1885
|
const turnElapsedMs = Date.now() - turnStartTime;
|
|
1883
1886
|
const shouldNotify = (turnElapsedMs >= 15000) || (toolsExecutedCount > 0) || (planMode && lastPlanReady !== null);
|
|
1884
1887
|
if (shouldNotify && !aborted) {
|
|
@@ -1947,6 +1950,7 @@ export async function main() {
|
|
|
1947
1950
|
}
|
|
1948
1951
|
}
|
|
1949
1952
|
finally {
|
|
1953
|
+
globalSnapshotManager.finishTurn();
|
|
1950
1954
|
activeAbortHandler = null;
|
|
1951
1955
|
process.removeListener('SIGINT', onSigInt);
|
|
1952
1956
|
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
|
+
}
|