termux-dev 1.2.2 → 1.4.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 +48 -0
- package/assets/banner.svg +1 -1
- package/assets/preview.png +0 -0
- package/dist/cli/doctor.js +8 -2
- package/dist/cli/export.js +62 -0
- package/dist/cli/headless.js +110 -0
- package/dist/cli/index.js +411 -224
- package/dist/cli/prompt.js +16 -2
- package/dist/cli/server.js +1 -1
- package/dist/cli/updater.js +0 -6
- package/dist/core/commands.js +100 -0
- package/dist/core/history.js +10 -0
- package/dist/core/loop.js +5 -1
- package/dist/core/models.js +1 -1
- package/dist/core/pricing.js +1 -1
- package/dist/core/repomap.js +135 -0
- package/dist/core/usage.js +119 -0
- package/dist/mcp/client.js +218 -0
- package/dist/mcp/manager.js +196 -0
- package/dist/mcp/types.js +1 -0
- package/dist/permissions/guard.js +69 -15
- package/dist/prompts/builder.js +9 -0
- package/dist/providers/openai.js +37 -8
- package/dist/tools/bash.js +7 -0
- package/dist/tools/fs.js +1 -1
- package/dist/tools/index.js +4 -2
- package/dist/tools/web.js +3 -3
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -27,6 +27,11 @@ import { askPrompt } from './prompt.js';
|
|
|
27
27
|
import { resolveAtMentions } from './files.js';
|
|
28
28
|
import { runStartupUpdateCheck, checkForUpdates, performSelfUpdate } from './updater.js';
|
|
29
29
|
import { setActiveTheme, getCurrentTheme, listThemes, findTheme } from './theme.js';
|
|
30
|
+
import { runHeadlessMode } from './headless.js';
|
|
31
|
+
import { CustomCommandManager } from '../core/commands.js';
|
|
32
|
+
import { UsageTracker } from '../core/usage.js';
|
|
33
|
+
import { SessionExporter } from './export.js';
|
|
34
|
+
import { MCPManager } from '../mcp/manager.js';
|
|
30
35
|
const CONFIG_PATH = path.join(os.homedir(), '.devxrc.json');
|
|
31
36
|
function maskApiKey(key) {
|
|
32
37
|
if (!key)
|
|
@@ -316,23 +321,111 @@ async function runOnboarding() {
|
|
|
316
321
|
p.outro(pc.green('Setup complete! Configuration saved to ~/.devxrc.json'));
|
|
317
322
|
return res;
|
|
318
323
|
}
|
|
319
|
-
async function loadConfig() {
|
|
324
|
+
async function loadConfig(interactive = true) {
|
|
325
|
+
let globalConfig = {};
|
|
320
326
|
try {
|
|
321
327
|
const data = await fs.readFile(CONFIG_PATH, 'utf8');
|
|
322
|
-
|
|
323
|
-
if (!
|
|
328
|
+
globalConfig = JSON.parse(data);
|
|
329
|
+
if (!globalConfig.model || !globalConfig.baseUrl)
|
|
324
330
|
throw new Error('Invalid config');
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
331
|
+
globalConfig.maxContextTokens = globalConfig.maxContextTokens || getModelContextLimit(globalConfig.model);
|
|
332
|
+
globalConfig.apiKeys = globalConfig.apiKeys || {};
|
|
333
|
+
globalConfig.baseUrls = globalConfig.baseUrls || {};
|
|
334
|
+
globalConfig.trustedProjects = globalConfig.trustedProjects || {};
|
|
335
|
+
if (globalConfig.provider && globalConfig.apiKey) {
|
|
336
|
+
globalConfig.apiKeys[globalConfig.provider] = globalConfig.apiKey;
|
|
330
337
|
}
|
|
331
|
-
return parsed;
|
|
332
338
|
}
|
|
333
339
|
catch {
|
|
334
|
-
|
|
340
|
+
if (interactive) {
|
|
341
|
+
globalConfig = await runOnboarding();
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
throw new Error('No configuration found in ~/.devxrc.json. Please run devx interactively first to set up your AI provider.');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// Check for local project config (.devx.json or .devxrc.json)
|
|
348
|
+
const localConfigPaths = [
|
|
349
|
+
path.join(process.cwd(), '.devx.json'),
|
|
350
|
+
path.join(process.cwd(), '.devxrc.json')
|
|
351
|
+
];
|
|
352
|
+
let localConfig = null;
|
|
353
|
+
for (const lp of localConfigPaths) {
|
|
354
|
+
try {
|
|
355
|
+
if (fsSync.existsSync(lp)) {
|
|
356
|
+
const raw = await fs.readFile(lp, 'utf8');
|
|
357
|
+
localConfig = JSON.parse(raw);
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch { }
|
|
335
362
|
}
|
|
363
|
+
const effectiveConfig = { ...globalConfig };
|
|
364
|
+
const cwd = process.cwd();
|
|
365
|
+
let isTrusted = globalConfig.trustedProjects?.[cwd];
|
|
366
|
+
// Workspace & Project Trust Check on first time opening this directory
|
|
367
|
+
if (isTrusted === undefined) {
|
|
368
|
+
if (interactive) {
|
|
369
|
+
console.log('');
|
|
370
|
+
p.log.warn(pc.bold(pc.yellow(`🛡️ [WORKSPACE TRUST] First time opening this workspace:`)));
|
|
371
|
+
console.log(pc.dim(` Directory: ${cwd}`));
|
|
372
|
+
if (localConfig) {
|
|
373
|
+
if (localConfig.autoApprove === true) {
|
|
374
|
+
console.log(pc.yellow(` • Local .devx.json requests Auto-approval (YOLO mode)`));
|
|
375
|
+
}
|
|
376
|
+
if (localConfig.bashAllowlist) {
|
|
377
|
+
console.log(pc.yellow(` • Local .devx.json requests Bash allowlist: ${localConfig.bashAllowlist.join(', ')}`));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const answer = await p.confirm({
|
|
381
|
+
message: 'Do you trust this workspace and allow agent operations?',
|
|
382
|
+
initialValue: true
|
|
383
|
+
});
|
|
384
|
+
if (p.isCancel(answer) || answer !== true) {
|
|
385
|
+
resetTerminalTheme();
|
|
386
|
+
p.outro(pc.yellow('Workspace not trusted. Exiting devx.'));
|
|
387
|
+
process.exit(0);
|
|
388
|
+
}
|
|
389
|
+
globalConfig.trustedProjects = globalConfig.trustedProjects || {};
|
|
390
|
+
globalConfig.trustedProjects[cwd] = true;
|
|
391
|
+
await saveConfig(globalConfig);
|
|
392
|
+
p.log.success(pc.green('Workspace marked as trusted. Full capabilities enabled.'));
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
isTrusted = false; // Never auto-trust in non-interactive headless mode
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (localConfig && typeof localConfig === 'object') {
|
|
399
|
+
// Apply safe configuration overrides
|
|
400
|
+
if (localConfig.model)
|
|
401
|
+
effectiveConfig.model = localConfig.model;
|
|
402
|
+
if (localConfig.provider)
|
|
403
|
+
effectiveConfig.provider = localConfig.provider;
|
|
404
|
+
if (localConfig.apiKey)
|
|
405
|
+
effectiveConfig.apiKey = localConfig.apiKey;
|
|
406
|
+
if (localConfig.baseUrl)
|
|
407
|
+
effectiveConfig.baseUrl = localConfig.baseUrl;
|
|
408
|
+
if (localConfig.maxIterations)
|
|
409
|
+
effectiveConfig.maxIterations = localConfig.maxIterations;
|
|
410
|
+
if (localConfig.maxContextTokens)
|
|
411
|
+
effectiveConfig.maxContextTokens = localConfig.maxContextTokens;
|
|
412
|
+
if (localConfig.dataSaverLimitMB)
|
|
413
|
+
effectiveConfig.dataSaverLimitMB = localConfig.dataSaverLimitMB;
|
|
414
|
+
if (localConfig.pureBlackTheme !== undefined)
|
|
415
|
+
effectiveConfig.pureBlackTheme = localConfig.pureBlackTheme;
|
|
416
|
+
// Apply elevated permissions only if explicitly trusted
|
|
417
|
+
if (isTrusted) {
|
|
418
|
+
if (localConfig.autoApprove !== undefined)
|
|
419
|
+
effectiveConfig.autoApprove = localConfig.autoApprove;
|
|
420
|
+
if (Array.isArray(localConfig.bashAllowlist))
|
|
421
|
+
effectiveConfig.bashAllowlist = localConfig.bashAllowlist;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Configure Data Saver threshold
|
|
425
|
+
if (effectiveConfig.dataSaverLimitMB) {
|
|
426
|
+
UsageTracker.getInstance().setLimit(effectiveConfig.dataSaverLimitMB);
|
|
427
|
+
}
|
|
428
|
+
return effectiveConfig;
|
|
336
429
|
}
|
|
337
430
|
function enableDarkTheme(enabled = true) {
|
|
338
431
|
if (!enabled)
|
|
@@ -391,7 +484,7 @@ function drawLogo() {
|
|
|
391
484
|
theme.colorFn(' █▀▀▄ █▀▀▀ █ █ █ █'),
|
|
392
485
|
theme.colorFn(' █ █ █▀▀▀ ▀▄▀ ▀▄▀ '),
|
|
393
486
|
theme.colorFn(' █▄▄▀ █▄▄▄ ▀ ▀ ▀ '),
|
|
394
|
-
' ' + theme.boldFn('v1.
|
|
487
|
+
' ' + theme.boldFn('v1.4.0'),
|
|
395
488
|
''
|
|
396
489
|
];
|
|
397
490
|
for (const line of logo) {
|
|
@@ -406,7 +499,7 @@ function drawLogo() {
|
|
|
406
499
|
indent + theme.colorFn('▀▀▀█▀▀▀ █▀▀▀ █▀▀█ █▄ ▄█ █ █ ▀▄ ▄▀ █▀▀▄ █▀▀▀ █ █'),
|
|
407
500
|
indent + theme.colorFn(' █ █▀▀▀ █▄▄▀ █ █ █ █ █ █ ▀▀ █ █ █▀▀▀ █ █'),
|
|
408
501
|
indent + theme.colorFn(' █ █▄▄▄ █ ▀▄ █ █ ▀▄▄▀ ▄▀ ▀▄ █▄▄▀ █▄▄▄ ▀▄▀ '),
|
|
409
|
-
indent + theme.boldFn('v1.
|
|
502
|
+
indent + theme.boldFn('v1.4.0'),
|
|
410
503
|
''
|
|
411
504
|
];
|
|
412
505
|
for (const line of logo) {
|
|
@@ -523,16 +616,191 @@ async function handleSessionDelete() {
|
|
|
523
616
|
}
|
|
524
617
|
}
|
|
525
618
|
}
|
|
619
|
+
async function handleThemeSelect(config) {
|
|
620
|
+
const currentTh = getCurrentTheme();
|
|
621
|
+
const themeChoices = listThemes().map(t => ({
|
|
622
|
+
name: `${t.emoji} ${t.boldFn(t.name.padEnd(18))} ${pc.dim(t.desc)} ${t.id === currentTh.id ? pc.green('(Active)') : ''}`,
|
|
623
|
+
value: t.id,
|
|
624
|
+
description: `Apply ${t.name} color palette (${t.hex}) to banners, prompts, and actions`
|
|
625
|
+
}));
|
|
626
|
+
try {
|
|
627
|
+
const selected = await select({
|
|
628
|
+
message: `${pc.bold('🎨 Select UI Theme / Выберите цветовую тему:')}`,
|
|
629
|
+
choices: themeChoices
|
|
630
|
+
});
|
|
631
|
+
if (selected) {
|
|
632
|
+
config.theme = selected;
|
|
633
|
+
await saveConfig(config);
|
|
634
|
+
const th = setActiveTheme(selected);
|
|
635
|
+
drawLogo();
|
|
636
|
+
p.log.success(th.boldFn(`🎨 Theme switched to ${th.emoji} ${th.name}!`));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
catch { }
|
|
640
|
+
}
|
|
641
|
+
async function handleSettings(config) {
|
|
642
|
+
while (true) {
|
|
643
|
+
try {
|
|
644
|
+
const maxIter = config.maxIterations || 100;
|
|
645
|
+
const maxIterLabel = maxIter >= 9999 ? 'Unlimited' : `${maxIter} steps`;
|
|
646
|
+
const currentTh = getCurrentTheme();
|
|
647
|
+
const choice = await select({
|
|
648
|
+
message: `${pc.bold('⚙️ Settings')} ${pc.dim(`(devx v1.4.0 • theme: ${currentTh.name})`)}`,
|
|
649
|
+
choices: [
|
|
650
|
+
{
|
|
651
|
+
name: `🎨 Color Theme: ${currentTh.emoji} ${currentTh.name}`,
|
|
652
|
+
value: 'change_theme',
|
|
653
|
+
description: `Switch UI accent colors (${currentTh.desc})`
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
name: `${config.pureBlackTheme !== false ? pc.green('🖤 Pure Black Background: ON') : pc.yellow('🖤 Pure Black Background: OFF')}`,
|
|
657
|
+
value: 'toggle_black_theme',
|
|
658
|
+
description: config.pureBlackTheme !== false
|
|
659
|
+
? 'Apply deep OLED obsidian black background (#0a0a0c) like OpenCode'
|
|
660
|
+
: 'Use standard system terminal background color'
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
name: `${config.autoApprove ? pc.green('⚡ Auto-Approve (YOLO Mode): ON') : pc.yellow('🛡️ Auto-Approve (YOLO Mode): OFF')}`,
|
|
664
|
+
value: 'toggle_auto_approve',
|
|
665
|
+
description: config.autoApprove
|
|
666
|
+
? 'Permissions are automatically granted (no confirmation prompts for commands/files)'
|
|
667
|
+
: 'Agent asks for confirmation before executing bash commands or writing files'
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: `${config.enableMemory !== false ? pc.green('🧠 Project Memory Bank: ON') : pc.yellow('🧠 Project Memory Bank: OFF')}`,
|
|
671
|
+
value: 'toggle_memory',
|
|
672
|
+
description: config.enableMemory !== false
|
|
673
|
+
? 'Load persistent project rules and preferences from .devx/memory.md into AI context'
|
|
674
|
+
: 'Start sessions with a clean state without loading project memory'
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
name: `${config.checkUpdates !== false ? pc.green('🔔 Check for Updates on Startup: ON') : pc.yellow('🔔 Check for Updates on Startup: OFF')}`,
|
|
678
|
+
value: 'toggle_check_updates',
|
|
679
|
+
description: config.checkUpdates !== false
|
|
680
|
+
? 'Automatically check for updates from GitHub repository when launching devx'
|
|
681
|
+
: 'Disable update checking on startup (run /update manually instead)'
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
name: `🔄 Max Agent Iterations: ${currentTh.colorFn(maxIterLabel)}`,
|
|
685
|
+
value: 'change_max_iterations',
|
|
686
|
+
description: 'Limit how many tool steps (file edits, terminal commands) agent can do per request'
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
name: `${currentTh.colorFn('✨ About devx')} ${pc.dim('(v1.4.0 by ApvCode)')}`,
|
|
690
|
+
value: 'about',
|
|
691
|
+
description: 'Terminal-Native AI Coding Agent created by ApvCode (https://github.com/apvcode/Termux-Dev)'
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
name: '⬅️ Back / Save',
|
|
695
|
+
value: 'back',
|
|
696
|
+
description: 'Return to chat'
|
|
697
|
+
}
|
|
698
|
+
]
|
|
699
|
+
});
|
|
700
|
+
if (choice === 'change_theme') {
|
|
701
|
+
await handleThemeSelect(config);
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (choice === 'about') {
|
|
705
|
+
p.note(`⚡ devx v1.4.0 — Terminal-Native AI Coding Agent\n` +
|
|
706
|
+
`🎨 Theme: ${currentTh.emoji} ${currentTh.name}\n` +
|
|
707
|
+
`👤 Author: ApvCode (https://github.com/apvcode)\n` +
|
|
708
|
+
`🌟 Repository: https://github.com/apvcode/Termux-Dev\n` +
|
|
709
|
+
`📜 License: MIT License (2026)\n` +
|
|
710
|
+
`Built for Android Termux, Windows, macOS, and Linux.`, 'About devx');
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (choice === 'toggle_black_theme') {
|
|
714
|
+
config.pureBlackTheme = config.pureBlackTheme === false ? true : false;
|
|
715
|
+
await saveConfig(config);
|
|
716
|
+
if (config.pureBlackTheme) {
|
|
717
|
+
enableDarkTheme(true);
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
resetTerminalTheme();
|
|
721
|
+
}
|
|
722
|
+
drawLogo();
|
|
723
|
+
p.log.success(`Pure Black background: ${config.pureBlackTheme ? pc.bold(pc.green('ON (Deep Black)')) : pc.bold(pc.yellow('OFF (System Default)'))}`);
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (choice === 'toggle_auto_approve') {
|
|
727
|
+
config.autoApprove = !config.autoApprove;
|
|
728
|
+
await saveConfig(config);
|
|
729
|
+
p.log.success(`Auto-approve permissions: ${config.autoApprove ? pc.bold(pc.green('ON (Automatic Yes)')) : pc.bold(pc.yellow('OFF (Ask every time)'))}`);
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (choice === 'toggle_memory') {
|
|
733
|
+
config.enableMemory = config.enableMemory === false ? true : false;
|
|
734
|
+
await saveConfig(config);
|
|
735
|
+
p.log.success(`Project memory bank: ${config.enableMemory !== false ? pc.bold(pc.green('ON (Persistent .devx/memory.md)')) : pc.bold(pc.yellow('OFF'))}`);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (choice === 'toggle_check_updates') {
|
|
739
|
+
config.checkUpdates = config.checkUpdates === false ? true : false;
|
|
740
|
+
await saveConfig(config);
|
|
741
|
+
p.log.success(`Check for updates: ${config.checkUpdates !== false ? pc.bold(pc.green('ON (Checked on startup)')) : pc.bold(pc.yellow('OFF (Manual only)'))}`);
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (choice === 'change_max_iterations') {
|
|
745
|
+
const val = await select({
|
|
746
|
+
message: 'Select maximum iterations limit per prompt:',
|
|
747
|
+
choices: [
|
|
748
|
+
{ name: '30 steps (Strict / Safe)', value: 30 },
|
|
749
|
+
{ name: '50 steps (Moderate)', value: 50 },
|
|
750
|
+
{ name: '100 steps (Recommended / Default)', value: 100 },
|
|
751
|
+
{ name: '200 steps (Very large refactors)', value: 200 },
|
|
752
|
+
{ name: 'Unlimited (No limit)', value: 9999 }
|
|
753
|
+
]
|
|
754
|
+
});
|
|
755
|
+
config.maxIterations = val;
|
|
756
|
+
await saveConfig(config);
|
|
757
|
+
p.log.success(`Max iterations updated to: ${pc.bold(val >= 9999 ? 'Unlimited' : `${val} steps`)}`);
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
catch {
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
process.stdin.resume();
|
|
767
|
+
return config;
|
|
768
|
+
}
|
|
526
769
|
export async function main() {
|
|
527
770
|
const program = new Command();
|
|
528
771
|
program
|
|
529
772
|
.name('devx')
|
|
530
|
-
.description('
|
|
531
|
-
.
|
|
773
|
+
.description('Terminal-native AI coding assistant and vibe-coding agent')
|
|
774
|
+
.version('1.4.0')
|
|
775
|
+
.option('-p, --prompt <task>', 'Run one-shot task non-interactively (headless mode)')
|
|
776
|
+
.option('-y, --yolo', 'Automatically approve all tool executions without confirmation')
|
|
777
|
+
.option('-m, --model <model>', 'Specify AI model to use for this execution')
|
|
778
|
+
.option('-q, --quiet', 'Quiet output in headless mode (suppress banners and tool logs)')
|
|
779
|
+
.option('--json', 'Output structured JSON in headless mode')
|
|
780
|
+
.option('--plan', 'Start in plan mode (architect & planner)')
|
|
532
781
|
.parse(process.argv);
|
|
533
782
|
const options = program.opts();
|
|
534
|
-
|
|
535
|
-
|
|
783
|
+
const planModeInitial = !!options.plan;
|
|
784
|
+
const isHeadless = !!options.prompt;
|
|
785
|
+
let config = await loadConfig(!isHeadless);
|
|
786
|
+
if (options.model) {
|
|
787
|
+
config.model = options.model;
|
|
788
|
+
config.maxContextTokens = getModelContextLimit(options.model);
|
|
789
|
+
}
|
|
790
|
+
if (options.yolo) {
|
|
791
|
+
config.autoApprove = true;
|
|
792
|
+
}
|
|
793
|
+
// If -p / --prompt is provided, execute one-shot headless mode and exit!
|
|
794
|
+
if (isHeadless) {
|
|
795
|
+
const exitCode = await runHeadlessMode(options.prompt, config, {
|
|
796
|
+
planMode: planModeInitial,
|
|
797
|
+
yolo: !!options.yolo,
|
|
798
|
+
quiet: !!options.quiet,
|
|
799
|
+
json: !!options.json
|
|
800
|
+
});
|
|
801
|
+
process.exit(exitCode);
|
|
802
|
+
}
|
|
803
|
+
let planMode = planModeInitial;
|
|
536
804
|
if (!config.onboarded) {
|
|
537
805
|
const cols = Math.min(process.stdout.columns || 40, 42);
|
|
538
806
|
const fill = Math.max(2, cols - 16);
|
|
@@ -573,6 +841,7 @@ export async function main() {
|
|
|
573
841
|
const sessionManager = new SessionManager(config.model, planMode);
|
|
574
842
|
drawLogo();
|
|
575
843
|
await runStartupUpdateCheck(config);
|
|
844
|
+
await MCPManager.getInstance().init();
|
|
576
845
|
let totalSessionCost = 0;
|
|
577
846
|
let currentDraft = '';
|
|
578
847
|
let autoTriggerPrompt = '';
|
|
@@ -656,215 +925,84 @@ export async function main() {
|
|
|
656
925
|
if (answer.startsWith('/')) {
|
|
657
926
|
const parts = answer.split(' ');
|
|
658
927
|
let cmd = parts[0];
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}));
|
|
666
|
-
try {
|
|
667
|
-
const selected = await select({
|
|
668
|
-
message: `${pc.bold('🎨 Select UI Theme / Выберите цветовую тему:')}`,
|
|
669
|
-
choices: themeChoices
|
|
670
|
-
});
|
|
671
|
-
if (selected) {
|
|
672
|
-
config.theme = selected;
|
|
673
|
-
await saveConfig(config);
|
|
674
|
-
const th = setActiveTheme(selected);
|
|
675
|
-
drawLogo();
|
|
676
|
-
p.log.success(th.boldFn(`🎨 Theme switched to ${th.emoji} ${th.name}!`));
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
catch { }
|
|
928
|
+
// 1. Check for custom slash commands (.devx/commands/*.md)
|
|
929
|
+
const customCmd = await CustomCommandManager.findCommand(cmd);
|
|
930
|
+
if (customCmd) {
|
|
931
|
+
const cmdArgs = answer.slice(cmd.length).trim();
|
|
932
|
+
const expandedPrompt = CustomCommandManager.expandTemplate(customCmd.promptTemplate, cmdArgs);
|
|
933
|
+
answer = expandedPrompt;
|
|
934
|
+
p.log.info(pc.cyan(`⚡ Executing custom command: ${pc.bold(customCmd.cmd)} (${customCmd.desc})`));
|
|
680
935
|
}
|
|
681
|
-
|
|
682
|
-
|
|
936
|
+
else {
|
|
937
|
+
const VALID_COMMANDS = [
|
|
938
|
+
'/new', '/reset', '/resume', '/session', '/sessions', '/history',
|
|
939
|
+
'/theme', '/themes', '/usage', '/export', '/mcp',
|
|
940
|
+
'/settings', '/update', '/model', '/provider', '/providers',
|
|
941
|
+
'/plan', '/agent', '/image', '/serve', '/memory', '/undo',
|
|
942
|
+
'/diff', '/commit', '/status', '/compact', '/init', '/doctor',
|
|
943
|
+
'/config', '/clear', '/exit', '/quit', '/help'
|
|
944
|
+
];
|
|
945
|
+
if (!VALID_COMMANDS.includes(cmd)) {
|
|
946
|
+
const customList = await CustomCommandManager.listCommands();
|
|
947
|
+
const customSlashItems = customList.map(c => ({
|
|
948
|
+
name: `${c.cmd.padEnd(14)} - ${c.desc}`,
|
|
949
|
+
value: c.cmd
|
|
950
|
+
}));
|
|
951
|
+
const SLASH_COMMANDS = [
|
|
952
|
+
{ name: '/new - Start a new clean chat session', value: '/new' },
|
|
953
|
+
{ name: '/resume - Resume a previous chat session', value: '/resume' },
|
|
954
|
+
{ name: '/session del - Select and delete saved sessions', value: '/session del' },
|
|
955
|
+
{ name: '/usage - Show network bandwidth, data saver & token cost', value: '/usage' },
|
|
956
|
+
{ name: '/export - Export session conversation to Markdown', value: '/export' },
|
|
957
|
+
{ name: '/mcp - Manage Model Context Protocol (MCP) servers & tools', value: '/mcp' },
|
|
958
|
+
{ name: '/theme - Switch UI color theme', value: '/theme' },
|
|
959
|
+
{ name: '/doctor - Run system & environment health diagnostics', value: '/doctor' },
|
|
960
|
+
{ name: '/settings - Configure permissions & auto-approval', value: '/settings' },
|
|
961
|
+
{ name: '/update - Check and install updates from GitHub', value: '/update' },
|
|
962
|
+
{ name: '/model - Switch model for current provider', value: '/model' },
|
|
963
|
+
{ name: '/provider - Change AI provider (Google, OpenRouter...)', value: '/provider' },
|
|
964
|
+
{ name: '/plan - Switch to PLAN mode (architect)', value: '/plan' },
|
|
965
|
+
{ name: '/agent - Switch to AGENT mode (coder)', value: '/agent' },
|
|
966
|
+
{ name: '/serve - Start local web server for web preview', value: '/serve' },
|
|
967
|
+
{ name: '/memory - View or edit project memory bank', value: '/memory' },
|
|
968
|
+
{ name: '/undo - Revert last file changes made by AI', value: '/undo' },
|
|
969
|
+
{ name: '/diff - Show git diff of modified files', value: '/diff' },
|
|
970
|
+
{ name: '/commit - AI-generated git commit message', value: '/commit' },
|
|
971
|
+
{ name: '/status - Show git repository status', value: '/status' },
|
|
972
|
+
{ name: '/compact - Compact conversation context', value: '/compact' },
|
|
973
|
+
{ name: '/config - View current configuration', value: '/config' },
|
|
974
|
+
{ name: '/clear - Clear message history', value: '/clear' },
|
|
975
|
+
{ name: '/help - Show commands overview', value: '/help' },
|
|
976
|
+
{ name: '/exit - Exit devx', value: '/exit' },
|
|
977
|
+
...customSlashItems
|
|
978
|
+
];
|
|
683
979
|
try {
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
value: 'toggle_black_theme',
|
|
698
|
-
description: config.pureBlackTheme !== false
|
|
699
|
-
? 'Apply deep OLED obsidian black background (#0a0a0c) like OpenCode'
|
|
700
|
-
: 'Use standard system terminal background color'
|
|
701
|
-
},
|
|
702
|
-
{
|
|
703
|
-
name: `${config.autoApprove ? pc.green('⚡ Auto-Approve (YOLO Mode): ON') : pc.yellow('🛡️ Auto-Approve (YOLO Mode): OFF')}`,
|
|
704
|
-
value: 'toggle_auto_approve',
|
|
705
|
-
description: config.autoApprove
|
|
706
|
-
? 'Permissions are automatically granted (no confirmation prompts for commands/files)'
|
|
707
|
-
: 'Agent asks for confirmation before executing bash commands or writing files'
|
|
708
|
-
},
|
|
709
|
-
{
|
|
710
|
-
name: `${config.enableMemory !== false ? pc.green('🧠 Project Memory Bank: ON') : pc.yellow('🧠 Project Memory Bank: OFF')}`,
|
|
711
|
-
value: 'toggle_memory',
|
|
712
|
-
description: config.enableMemory !== false
|
|
713
|
-
? 'Load persistent project rules and preferences from .devx/memory.md into AI context'
|
|
714
|
-
: 'Start sessions with a clean state without loading project memory'
|
|
715
|
-
},
|
|
716
|
-
{
|
|
717
|
-
name: `${config.checkUpdates !== false ? pc.green('🔔 Check for Updates on Startup: ON') : pc.yellow('🔔 Check for Updates on Startup: OFF')}`,
|
|
718
|
-
value: 'toggle_check_updates',
|
|
719
|
-
description: config.checkUpdates !== false
|
|
720
|
-
? 'Automatically check for updates from GitHub repository when launching devx'
|
|
721
|
-
: 'Disable update checking on startup (run /update manually instead)'
|
|
722
|
-
},
|
|
723
|
-
{
|
|
724
|
-
name: `🔄 Max Agent Iterations: ${currentTh.colorFn(maxIterLabel)}`,
|
|
725
|
-
value: 'change_max_iterations',
|
|
726
|
-
description: 'Limit how many tool steps (file edits, terminal commands) agent can do per request'
|
|
727
|
-
},
|
|
728
|
-
{
|
|
729
|
-
name: `${currentTh.colorFn('✨ About devx')} ${pc.dim('(v1.2.2 by ApvCode)')}`,
|
|
730
|
-
value: 'about',
|
|
731
|
-
description: 'Terminal-Native AI Coding Agent created by ApvCode (https://github.com/apvcode/Termux-Dev)'
|
|
732
|
-
},
|
|
733
|
-
{
|
|
734
|
-
name: '⬅️ Back / Save',
|
|
735
|
-
value: 'back',
|
|
736
|
-
description: 'Return to chat'
|
|
737
|
-
}
|
|
738
|
-
]
|
|
980
|
+
const picked = await search({
|
|
981
|
+
message: 'Commands (type to search or select):',
|
|
982
|
+
source: async (term) => {
|
|
983
|
+
const q = (term || '').trim().toLowerCase();
|
|
984
|
+
const list = [
|
|
985
|
+
{ name: pc.yellow('⬅️ Cancel'), value: '__cancel__' },
|
|
986
|
+
...SLASH_COMMANDS
|
|
987
|
+
];
|
|
988
|
+
if (!q)
|
|
989
|
+
return list;
|
|
990
|
+
return list.filter(item => item.name.toLowerCase().includes(q) || item.value.toLowerCase().includes(q));
|
|
991
|
+
},
|
|
992
|
+
pageSize: 12
|
|
739
993
|
});
|
|
740
|
-
if (
|
|
741
|
-
await handleThemeSelect(config);
|
|
994
|
+
if (!picked || picked === '__cancel__') {
|
|
742
995
|
continue;
|
|
743
996
|
}
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
`🎨 Theme: ${currentTh.emoji} ${currentTh.name}\n` +
|
|
747
|
-
`👤 Author: ApvCode (https://github.com/apvcode)\n` +
|
|
748
|
-
`🌟 Repository: https://github.com/apvcode/Termux-Dev\n` +
|
|
749
|
-
`📜 License: MIT License (2026)\n` +
|
|
750
|
-
`Built for Android Termux, Windows, macOS, and Linux.`, 'About devx');
|
|
997
|
+
if (picked === '/session del') {
|
|
998
|
+
await handleSessionDelete();
|
|
751
999
|
continue;
|
|
752
1000
|
}
|
|
753
|
-
|
|
754
|
-
config.pureBlackTheme = config.pureBlackTheme === false ? true : false;
|
|
755
|
-
await saveConfig(config);
|
|
756
|
-
if (config.pureBlackTheme) {
|
|
757
|
-
enableDarkTheme(true);
|
|
758
|
-
}
|
|
759
|
-
else {
|
|
760
|
-
resetTerminalTheme();
|
|
761
|
-
}
|
|
762
|
-
drawLogo();
|
|
763
|
-
p.log.success(`Pure Black background: ${config.pureBlackTheme ? pc.bold(pc.green('ON (Deep Black)')) : pc.bold(pc.yellow('OFF (System Default)'))}`);
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
if (choice === 'toggle_auto_approve') {
|
|
767
|
-
config.autoApprove = !config.autoApprove;
|
|
768
|
-
await saveConfig(config);
|
|
769
|
-
p.log.success(`Auto-approve permissions: ${config.autoApprove ? pc.bold(pc.green('ON (Automatic Yes)')) : pc.bold(pc.yellow('OFF (Ask every time)'))}`);
|
|
770
|
-
continue;
|
|
771
|
-
}
|
|
772
|
-
if (choice === 'toggle_memory') {
|
|
773
|
-
config.enableMemory = config.enableMemory === false ? true : false;
|
|
774
|
-
await saveConfig(config);
|
|
775
|
-
p.log.success(`Project memory bank: ${config.enableMemory !== false ? pc.bold(pc.green('ON (Persistent .devx/memory.md)')) : pc.bold(pc.yellow('OFF'))}`);
|
|
776
|
-
continue;
|
|
777
|
-
}
|
|
778
|
-
if (choice === 'toggle_check_updates') {
|
|
779
|
-
config.checkUpdates = config.checkUpdates === false ? true : false;
|
|
780
|
-
await saveConfig(config);
|
|
781
|
-
p.log.success(`Check for updates: ${config.checkUpdates !== false ? pc.bold(pc.green('ON (Checked on startup)')) : pc.bold(pc.yellow('OFF (Manual only)'))}`);
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
if (choice === 'change_max_iterations') {
|
|
785
|
-
const val = await select({
|
|
786
|
-
message: 'Select maximum iterations limit per prompt:',
|
|
787
|
-
choices: [
|
|
788
|
-
{ name: '30 steps (Strict / Safe)', value: 30 },
|
|
789
|
-
{ name: '50 steps (Moderate)', value: 50 },
|
|
790
|
-
{ name: '100 steps (Recommended / Default)', value: 100 },
|
|
791
|
-
{ name: '200 steps (Very large refactors)', value: 200 },
|
|
792
|
-
{ name: 'Unlimited (No limit)', value: 9999 }
|
|
793
|
-
]
|
|
794
|
-
});
|
|
795
|
-
config.maxIterations = val;
|
|
796
|
-
await saveConfig(config);
|
|
797
|
-
p.log.success(`Max iterations updated to: ${pc.bold(val >= 9999 ? 'Unlimited' : `${val} steps`)}`);
|
|
798
|
-
continue;
|
|
799
|
-
}
|
|
800
|
-
break;
|
|
1001
|
+
cmd = picked;
|
|
801
1002
|
}
|
|
802
1003
|
catch {
|
|
803
|
-
break;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
process.stdin.resume();
|
|
807
|
-
return config;
|
|
808
|
-
}
|
|
809
|
-
const VALID_COMMANDS = [
|
|
810
|
-
'/new', '/reset', '/resume', '/session', '/sessions', '/history',
|
|
811
|
-
'/theme', '/themes',
|
|
812
|
-
'/settings', '/update', '/model', '/provider', '/providers',
|
|
813
|
-
'/plan', '/agent', '/image', '/serve', '/memory', '/undo',
|
|
814
|
-
'/diff', '/commit', '/status', '/compact', '/init', '/doctor',
|
|
815
|
-
'/config', '/clear', '/exit', '/quit', '/help'
|
|
816
|
-
];
|
|
817
|
-
if (!VALID_COMMANDS.includes(cmd)) {
|
|
818
|
-
const SLASH_COMMANDS = [
|
|
819
|
-
{ name: '/new - Start a new clean chat session', value: '/new' },
|
|
820
|
-
{ name: '/resume - Resume a previous chat session', value: '/resume' },
|
|
821
|
-
{ name: '/session del - Select and delete saved sessions', value: '/session del' },
|
|
822
|
-
{ name: '/theme - Switch UI color theme', value: '/theme' },
|
|
823
|
-
{ name: '/doctor - Run system & environment health diagnostics', value: '/doctor' },
|
|
824
|
-
{ name: '/settings - Configure permissions & auto-approval', value: '/settings' },
|
|
825
|
-
{ name: '/update - Check and install updates from GitHub', value: '/update' },
|
|
826
|
-
{ name: '/model - Switch model for current provider', value: '/model' },
|
|
827
|
-
{ name: '/provider - Change AI provider (Google, OpenRouter...)', value: '/provider' },
|
|
828
|
-
{ name: '/plan - Switch to PLAN mode (architect)', value: '/plan' },
|
|
829
|
-
{ name: '/agent - Switch to AGENT mode (coder)', value: '/agent' },
|
|
830
|
-
{ name: '/serve - Start local web server for web preview', value: '/serve' },
|
|
831
|
-
{ name: '/memory - View or edit project memory bank', value: '/memory' },
|
|
832
|
-
{ name: '/undo - Revert last file changes made by AI', value: '/undo' },
|
|
833
|
-
{ name: '/diff - Show git diff of modified files', value: '/diff' },
|
|
834
|
-
{ name: '/commit - AI-generated git commit message', value: '/commit' },
|
|
835
|
-
{ name: '/status - Show git repository status', value: '/status' },
|
|
836
|
-
{ name: '/compact - Compact conversation context', value: '/compact' },
|
|
837
|
-
{ name: '/config - View current configuration', value: '/config' },
|
|
838
|
-
{ name: '/clear - Clear message history', value: '/clear' },
|
|
839
|
-
{ name: '/help - Show commands overview', value: '/help' },
|
|
840
|
-
{ name: '/exit - Exit devx', value: '/exit' },
|
|
841
|
-
];
|
|
842
|
-
try {
|
|
843
|
-
const picked = await search({
|
|
844
|
-
message: 'Commands (type to search or select):',
|
|
845
|
-
source: async (term) => {
|
|
846
|
-
const q = (term || '').trim().toLowerCase();
|
|
847
|
-
const list = [
|
|
848
|
-
{ name: pc.yellow('⬅️ Cancel'), value: '__cancel__' },
|
|
849
|
-
...SLASH_COMMANDS
|
|
850
|
-
];
|
|
851
|
-
if (!q)
|
|
852
|
-
return list;
|
|
853
|
-
return list.filter(item => item.name.toLowerCase().includes(q) || item.value.toLowerCase().includes(q));
|
|
854
|
-
},
|
|
855
|
-
pageSize: 12
|
|
856
|
-
});
|
|
857
|
-
if (!picked || picked === '__cancel__') {
|
|
858
|
-
continue;
|
|
859
|
-
}
|
|
860
|
-
if (picked === '/session del') {
|
|
861
|
-
await handleSessionDelete();
|
|
862
1004
|
continue;
|
|
863
1005
|
}
|
|
864
|
-
cmd = picked;
|
|
865
|
-
}
|
|
866
|
-
catch {
|
|
867
|
-
continue;
|
|
868
1006
|
}
|
|
869
1007
|
}
|
|
870
1008
|
if (cmd === '/settings') {
|
|
@@ -1055,13 +1193,7 @@ export async function main() {
|
|
|
1055
1193
|
p.log.warn('No changes to undo.');
|
|
1056
1194
|
}
|
|
1057
1195
|
else {
|
|
1058
|
-
|
|
1059
|
-
while (msgs.length > 1 && msgs[msgs.length - 1].role !== 'user') {
|
|
1060
|
-
msgs.pop();
|
|
1061
|
-
}
|
|
1062
|
-
if (msgs.length > 1 && msgs[msgs.length - 1].role === 'user') {
|
|
1063
|
-
msgs.pop();
|
|
1064
|
-
}
|
|
1196
|
+
history.popLastTurn();
|
|
1065
1197
|
p.log.success(pc.bold(pc.green(`⏪ Successfully reverted changes in ${count} file(s):`)));
|
|
1066
1198
|
for (const f of revertedFiles) {
|
|
1067
1199
|
console.log(pc.cyan(` • ${f}`));
|
|
@@ -1322,8 +1454,47 @@ export async function main() {
|
|
|
1322
1454
|
}
|
|
1323
1455
|
continue;
|
|
1324
1456
|
}
|
|
1457
|
+
if (cmd === '/usage') {
|
|
1458
|
+
const card = UsageTracker.getInstance().renderUsageCard();
|
|
1459
|
+
console.log('\n' + card);
|
|
1460
|
+
continue;
|
|
1461
|
+
}
|
|
1462
|
+
if (cmd === '/export') {
|
|
1463
|
+
const targetName = parts.slice(1).join(' ').trim();
|
|
1464
|
+
const res = await SessionExporter.exportToMarkdown(history.getMessages(), config.model, targetName || undefined);
|
|
1465
|
+
if (res.success) {
|
|
1466
|
+
p.log.success(pc.bold(pc.green(`📄 Session exported to: ${res.filePath}`)));
|
|
1467
|
+
}
|
|
1468
|
+
else {
|
|
1469
|
+
p.log.error(`Failed to export session: ${res.error}`);
|
|
1470
|
+
}
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
if (cmd === '/mcp') {
|
|
1474
|
+
const sub = (parts[1] || '').toLowerCase();
|
|
1475
|
+
if (sub === 'reload' || sub === 'restart' || sub === 'r') {
|
|
1476
|
+
const s = p.spinner();
|
|
1477
|
+
s.start('Reloading MCP servers...');
|
|
1478
|
+
await MCPManager.getInstance().reload();
|
|
1479
|
+
s.stop();
|
|
1480
|
+
console.log('\n' + MCPManager.getInstance().renderStatusCard());
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
console.log('\n' + MCPManager.getInstance().renderStatusCard());
|
|
1484
|
+
}
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1325
1487
|
if (cmd === '/config') {
|
|
1326
|
-
|
|
1488
|
+
const masked = { ...config };
|
|
1489
|
+
if (masked.apiKey)
|
|
1490
|
+
masked.apiKey = maskApiKey(masked.apiKey);
|
|
1491
|
+
if (masked.apiKeys) {
|
|
1492
|
+
masked.apiKeys = { ...masked.apiKeys };
|
|
1493
|
+
for (const k of Object.keys(masked.apiKeys)) {
|
|
1494
|
+
masked.apiKeys[k] = maskApiKey(masked.apiKeys[k]);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
p.note(JSON.stringify(masked, null, 2), 'Configuration (Secrets Masked)');
|
|
1327
1498
|
continue;
|
|
1328
1499
|
}
|
|
1329
1500
|
if (cmd === '/model') {
|
|
@@ -1408,10 +1579,12 @@ export async function main() {
|
|
|
1408
1579
|
});
|
|
1409
1580
|
const provider = createProvider(config);
|
|
1410
1581
|
const tools = getTools(planMode);
|
|
1411
|
-
const guard = new CLIConsoleGuard(config.autoApprove);
|
|
1582
|
+
const guard = new CLIConsoleGuard(config.autoApprove, config.bashAllowlist || []);
|
|
1412
1583
|
const agentConfig = {
|
|
1413
1584
|
maxContextTokens: config.maxContextTokens || 100000,
|
|
1414
|
-
maxIterations: config.maxIterations || 100
|
|
1585
|
+
maxIterations: config.maxIterations || 100,
|
|
1586
|
+
autoApprove: config.autoApprove,
|
|
1587
|
+
bashAllowlist: config.bashAllowlist
|
|
1415
1588
|
};
|
|
1416
1589
|
const agent = new Agent(agentConfig, provider, tools, history, guard);
|
|
1417
1590
|
const taskStartTime = Date.now();
|
|
@@ -1755,5 +1928,19 @@ export async function main() {
|
|
|
1755
1928
|
}
|
|
1756
1929
|
}
|
|
1757
1930
|
}
|
|
1931
|
+
MCPManager.getInstance().stopAll();
|
|
1758
1932
|
}
|
|
1933
|
+
process.on('exit', () => {
|
|
1934
|
+
try {
|
|
1935
|
+
MCPManager.getInstance().stopAll();
|
|
1936
|
+
}
|
|
1937
|
+
catch { }
|
|
1938
|
+
});
|
|
1939
|
+
process.on('SIGINT', () => {
|
|
1940
|
+
try {
|
|
1941
|
+
MCPManager.getInstance().stopAll();
|
|
1942
|
+
}
|
|
1943
|
+
catch { }
|
|
1944
|
+
process.exit(130);
|
|
1945
|
+
});
|
|
1759
1946
|
main().catch(console.error);
|