termux-dev 1.3.0 → 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 CHANGED
@@ -131,6 +131,7 @@ Type `/` in the prompt to open the autocomplete command palette:
131
131
  | :--- | :--- |
132
132
  | **`/plan`** | Switch to **PLAN** mode (architect & requirements planner) |
133
133
  | **`/agent`** | Switch to **AGENT** mode (coder & autonomous executor) |
134
+ | **`/mcp`** | Manage Model Context Protocol (MCP) servers & external tools (`/mcp reload`) |
134
135
  | **`/usage`** | View live network bandwidth (KB/MB), token costs, and API request counts |
135
136
  | **`/export`** | Export session conversation to a clean GitHub-Flavored Markdown transcript |
136
137
  | **`/theme`** | Switch UI color theme (Cyan, Purple, Matrix, Amber, Crimson, Monochrome) |
@@ -282,6 +283,7 @@ devx
282
283
  | :--- | :--- |
283
284
  | **`/plan`** | Переключиться в режим архитектора (**PLAN**) |
284
285
  | **`/agent`** | Переключиться в режим исполнителя (**AGENT**) |
286
+ | **`/mcp`** | Управление MCP-серверами и внешними инструментами (`/mcp reload`) |
285
287
  | **`/usage`** | Показать сетевой трафик (КБ/МБ), токены, число запросов и расходы ($) |
286
288
  | **`/export`** | Экспортировать сессию диалога в чистый GitHub Markdown файл |
287
289
  | **`/theme`** | Сменить цветовую тему (Cyan, Purple, Matrix, Amber, Crimson, Monochrome) |
package/assets/banner.svg CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  <!-- Tagline & Badge -->
27
27
  <text x="425" y="142" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="600" fill="#8b949e" text-anchor="middle" letter-spacing="3">
28
- THE TERMINAL-NATIVE AI CODING AGENT <tspan fill="#00f2fe" font-weight="bold">v1.3.0</tspan>
28
+ THE TERMINAL-NATIVE AI CODING AGENT <tspan fill="#00f2fe" font-weight="bold">v1.4.0</tspan>
29
29
  </text>
30
30
 
31
31
  <!-- Top Accent Line -->
Binary file
@@ -15,8 +15,14 @@ function checkCmd(cmd) {
15
15
  }
16
16
  async function pingUrl(url) {
17
17
  return new Promise((resolve) => {
18
- const req = https.get(url, { timeout: 3000 }, (res) => {
19
- resolve(res.statusCode !== undefined);
18
+ const req = https.get(url, {
19
+ timeout: 3000,
20
+ headers: {
21
+ 'User-Agent': 'devx-doctor/1.4.0'
22
+ }
23
+ }, (res) => {
24
+ res.resume(); // consume response data to free up memory and release socket
25
+ resolve(res.statusCode !== undefined && res.statusCode < 500);
20
26
  });
21
27
  req.on('error', () => resolve(false));
22
28
  req.on('timeout', () => {
@@ -35,7 +35,7 @@ export async function runHeadlessMode(userPrompt, config, options = {}) {
35
35
  });
36
36
  try {
37
37
  if (!isQuiet && !isJson) {
38
- console.log(pc.bold(pc.cyan(`⚡ devx v1.3.0 (headless) | ${planMode ? 'PLAN' : 'AGENT'} | ${config.model}`)));
38
+ console.log(pc.bold(pc.cyan(`⚡ devx v1.4.0 (headless) | ${planMode ? 'PLAN' : 'AGENT'} | ${config.model}`)));
39
39
  console.log(pc.dim(`Task: ${userPrompt}\n`));
40
40
  }
41
41
  for await (const event of agent.run(abortController.signal)) {
package/dist/cli/index.js CHANGED
@@ -31,6 +31,7 @@ import { runHeadlessMode } from './headless.js';
31
31
  import { CustomCommandManager } from '../core/commands.js';
32
32
  import { UsageTracker } from '../core/usage.js';
33
33
  import { SessionExporter } from './export.js';
34
+ import { MCPManager } from '../mcp/manager.js';
34
35
  const CONFIG_PATH = path.join(os.homedir(), '.devxrc.json');
35
36
  function maskApiKey(key) {
36
37
  if (!key)
@@ -360,40 +361,41 @@ async function loadConfig(interactive = true) {
360
361
  catch { }
361
362
  }
362
363
  const effectiveConfig = { ...globalConfig };
363
- if (localConfig && typeof localConfig === 'object') {
364
- const cwd = process.cwd();
365
- const hasElevatedPermissions = localConfig.autoApprove === true || (Array.isArray(localConfig.bashAllowlist) && localConfig.bashAllowlist.length > 0);
366
- let isTrusted = globalConfig.trustedProjects?.[cwd];
367
- if (hasElevatedPermissions && isTrusted === undefined) {
368
- if (interactive) {
369
- console.log('');
370
- p.log.warn(pc.bold(pc.yellow(`🛡️ [PROJECT TRUST] Local configuration (.devx.json) requests elevated permissions:`)));
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) {
371
373
  if (localConfig.autoApprove === true) {
372
- console.log(pc.yellow(` • Auto-approval (YOLO mode): enabled`));
374
+ console.log(pc.yellow(` • Local .devx.json requests Auto-approval (YOLO mode)`));
373
375
  }
374
376
  if (localConfig.bashAllowlist) {
375
- console.log(pc.yellow(` • Bash allowlist: ${localConfig.bashAllowlist.join(', ')}`));
376
- }
377
- console.log(pc.dim(` Repository: ${cwd}`));
378
- const answer = await p.confirm({
379
- message: 'Do you trust this repository and allow its elevated permissions?',
380
- initialValue: false
381
- });
382
- isTrusted = answer === true;
383
- globalConfig.trustedProjects = globalConfig.trustedProjects || {};
384
- globalConfig.trustedProjects[cwd] = isTrusted;
385
- await saveConfig(globalConfig);
386
- if (isTrusted) {
387
- p.log.success(pc.green('Repository marked as trusted. Elevated permissions applied.'));
388
- }
389
- else {
390
- p.log.info(pc.cyan('Repository not trusted. Elevated permissions ignored.'));
377
+ console.log(pc.yellow(` • Local .devx.json requests Bash allowlist: ${localConfig.bashAllowlist.join(', ')}`));
391
378
  }
392
379
  }
393
- else {
394
- isTrusted = false; // Never auto-trust in non-interactive headless mode
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);
395
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
396
  }
397
+ }
398
+ if (localConfig && typeof localConfig === 'object') {
397
399
  // Apply safe configuration overrides
398
400
  if (localConfig.model)
399
401
  effectiveConfig.model = localConfig.model;
@@ -482,7 +484,7 @@ function drawLogo() {
482
484
  theme.colorFn(' █▀▀▄ █▀▀▀ █ █ █ █'),
483
485
  theme.colorFn(' █ █ █▀▀▀ ▀▄▀ ▀▄▀ '),
484
486
  theme.colorFn(' █▄▄▀ █▄▄▄ ▀ ▀ ▀ '),
485
- ' ' + theme.boldFn('v1.3.0'),
487
+ ' ' + theme.boldFn('v1.4.0'),
486
488
  ''
487
489
  ];
488
490
  for (const line of logo) {
@@ -497,7 +499,7 @@ function drawLogo() {
497
499
  indent + theme.colorFn('▀▀▀█▀▀▀ █▀▀▀ █▀▀█ █▄ ▄█ █ █ ▀▄ ▄▀ █▀▀▄ █▀▀▀ █ █'),
498
500
  indent + theme.colorFn(' █ █▀▀▀ █▄▄▀ █ █ █ █ █ █ ▀▀ █ █ █▀▀▀ █ █'),
499
501
  indent + theme.colorFn(' █ █▄▄▄ █ ▀▄ █ █ ▀▄▄▀ ▄▀ ▀▄ █▄▄▀ █▄▄▄ ▀▄▀ '),
500
- indent + theme.boldFn('v1.3.0'),
502
+ indent + theme.boldFn('v1.4.0'),
501
503
  ''
502
504
  ];
503
505
  for (const line of logo) {
@@ -614,12 +616,162 @@ async function handleSessionDelete() {
614
616
  }
615
617
  }
616
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
+ }
617
769
  export async function main() {
618
770
  const program = new Command();
619
771
  program
620
772
  .name('devx')
621
773
  .description('Terminal-native AI coding assistant and vibe-coding agent')
622
- .version('1.3.0')
774
+ .version('1.4.0')
623
775
  .option('-p, --prompt <task>', 'Run one-shot task non-interactively (headless mode)')
624
776
  .option('-y, --yolo', 'Automatically approve all tool executions without confirmation')
625
777
  .option('-m, --model <model>', 'Specify AI model to use for this execution')
@@ -689,6 +841,7 @@ export async function main() {
689
841
  const sessionManager = new SessionManager(config.model, planMode);
690
842
  drawLogo();
691
843
  await runStartupUpdateCheck(config);
844
+ await MCPManager.getInstance().init();
692
845
  let totalSessionCost = 0;
693
846
  let currentDraft = '';
694
847
  let autoTriggerPrompt = '';
@@ -772,156 +925,6 @@ export async function main() {
772
925
  if (answer.startsWith('/')) {
773
926
  const parts = answer.split(' ');
774
927
  let cmd = parts[0];
775
- async function handleThemeSelect(config) {
776
- const currentTh = getCurrentTheme();
777
- const themeChoices = listThemes().map(t => ({
778
- name: `${t.emoji} ${t.boldFn(t.name.padEnd(18))} ${pc.dim(t.desc)} ${t.id === currentTh.id ? pc.green('(Active)') : ''}`,
779
- value: t.id,
780
- description: `Apply ${t.name} color palette (${t.hex}) to banners, prompts, and actions`
781
- }));
782
- try {
783
- const selected = await select({
784
- message: `${pc.bold('🎨 Select UI Theme / Выберите цветовую тему:')}`,
785
- choices: themeChoices
786
- });
787
- if (selected) {
788
- config.theme = selected;
789
- await saveConfig(config);
790
- const th = setActiveTheme(selected);
791
- drawLogo();
792
- p.log.success(th.boldFn(`🎨 Theme switched to ${th.emoji} ${th.name}!`));
793
- }
794
- }
795
- catch { }
796
- }
797
- async function handleSettings(config) {
798
- while (true) {
799
- try {
800
- const maxIter = config.maxIterations || 100;
801
- const maxIterLabel = maxIter >= 9999 ? 'Unlimited' : `${maxIter} steps`;
802
- const currentTh = getCurrentTheme();
803
- const choice = await select({
804
- message: `${pc.bold('⚙️ Settings')} ${pc.dim(`(devx v1.3.0 • theme: ${currentTh.name})`)}`,
805
- choices: [
806
- {
807
- name: `🎨 Color Theme: ${currentTh.emoji} ${currentTh.name}`,
808
- value: 'change_theme',
809
- description: `Switch UI accent colors (${currentTh.desc})`
810
- },
811
- {
812
- name: `${config.pureBlackTheme !== false ? pc.green('🖤 Pure Black Background: ON') : pc.yellow('🖤 Pure Black Background: OFF')}`,
813
- value: 'toggle_black_theme',
814
- description: config.pureBlackTheme !== false
815
- ? 'Apply deep OLED obsidian black background (#0a0a0c) like OpenCode'
816
- : 'Use standard system terminal background color'
817
- },
818
- {
819
- name: `${config.autoApprove ? pc.green('⚡ Auto-Approve (YOLO Mode): ON') : pc.yellow('🛡️ Auto-Approve (YOLO Mode): OFF')}`,
820
- value: 'toggle_auto_approve',
821
- description: config.autoApprove
822
- ? 'Permissions are automatically granted (no confirmation prompts for commands/files)'
823
- : 'Agent asks for confirmation before executing bash commands or writing files'
824
- },
825
- {
826
- name: `${config.enableMemory !== false ? pc.green('🧠 Project Memory Bank: ON') : pc.yellow('🧠 Project Memory Bank: OFF')}`,
827
- value: 'toggle_memory',
828
- description: config.enableMemory !== false
829
- ? 'Load persistent project rules and preferences from .devx/memory.md into AI context'
830
- : 'Start sessions with a clean state without loading project memory'
831
- },
832
- {
833
- name: `${config.checkUpdates !== false ? pc.green('🔔 Check for Updates on Startup: ON') : pc.yellow('🔔 Check for Updates on Startup: OFF')}`,
834
- value: 'toggle_check_updates',
835
- description: config.checkUpdates !== false
836
- ? 'Automatically check for updates from GitHub repository when launching devx'
837
- : 'Disable update checking on startup (run /update manually instead)'
838
- },
839
- {
840
- name: `🔄 Max Agent Iterations: ${currentTh.colorFn(maxIterLabel)}`,
841
- value: 'change_max_iterations',
842
- description: 'Limit how many tool steps (file edits, terminal commands) agent can do per request'
843
- },
844
- {
845
- name: `${currentTh.colorFn('✨ About devx')} ${pc.dim('(v1.3.0 by ApvCode)')}`,
846
- value: 'about',
847
- description: 'Terminal-Native AI Coding Agent created by ApvCode (https://github.com/apvcode/Termux-Dev)'
848
- },
849
- {
850
- name: '⬅️ Back / Save',
851
- value: 'back',
852
- description: 'Return to chat'
853
- }
854
- ]
855
- });
856
- if (choice === 'change_theme') {
857
- await handleThemeSelect(config);
858
- continue;
859
- }
860
- if (choice === 'about') {
861
- p.note(`⚡ devx v1.3.0 — Terminal-Native AI Coding Agent\n` +
862
- `🎨 Theme: ${currentTh.emoji} ${currentTh.name}\n` +
863
- `👤 Author: ApvCode (https://github.com/apvcode)\n` +
864
- `🌟 Repository: https://github.com/apvcode/Termux-Dev\n` +
865
- `📜 License: MIT License (2026)\n` +
866
- `Built for Android Termux, Windows, macOS, and Linux.`, 'About devx');
867
- continue;
868
- }
869
- if (choice === 'toggle_black_theme') {
870
- config.pureBlackTheme = config.pureBlackTheme === false ? true : false;
871
- await saveConfig(config);
872
- if (config.pureBlackTheme) {
873
- enableDarkTheme(true);
874
- }
875
- else {
876
- resetTerminalTheme();
877
- }
878
- drawLogo();
879
- p.log.success(`Pure Black background: ${config.pureBlackTheme ? pc.bold(pc.green('ON (Deep Black)')) : pc.bold(pc.yellow('OFF (System Default)'))}`);
880
- continue;
881
- }
882
- if (choice === 'toggle_auto_approve') {
883
- config.autoApprove = !config.autoApprove;
884
- await saveConfig(config);
885
- p.log.success(`Auto-approve permissions: ${config.autoApprove ? pc.bold(pc.green('ON (Automatic Yes)')) : pc.bold(pc.yellow('OFF (Ask every time)'))}`);
886
- continue;
887
- }
888
- if (choice === 'toggle_memory') {
889
- config.enableMemory = config.enableMemory === false ? true : false;
890
- await saveConfig(config);
891
- p.log.success(`Project memory bank: ${config.enableMemory !== false ? pc.bold(pc.green('ON (Persistent .devx/memory.md)')) : pc.bold(pc.yellow('OFF'))}`);
892
- continue;
893
- }
894
- if (choice === 'toggle_check_updates') {
895
- config.checkUpdates = config.checkUpdates === false ? true : false;
896
- await saveConfig(config);
897
- p.log.success(`Check for updates: ${config.checkUpdates !== false ? pc.bold(pc.green('ON (Checked on startup)')) : pc.bold(pc.yellow('OFF (Manual only)'))}`);
898
- continue;
899
- }
900
- if (choice === 'change_max_iterations') {
901
- const val = await select({
902
- message: 'Select maximum iterations limit per prompt:',
903
- choices: [
904
- { name: '30 steps (Strict / Safe)', value: 30 },
905
- { name: '50 steps (Moderate)', value: 50 },
906
- { name: '100 steps (Recommended / Default)', value: 100 },
907
- { name: '200 steps (Very large refactors)', value: 200 },
908
- { name: 'Unlimited (No limit)', value: 9999 }
909
- ]
910
- });
911
- config.maxIterations = val;
912
- await saveConfig(config);
913
- p.log.success(`Max iterations updated to: ${pc.bold(val >= 9999 ? 'Unlimited' : `${val} steps`)}`);
914
- continue;
915
- }
916
- break;
917
- }
918
- catch {
919
- break;
920
- }
921
- }
922
- process.stdin.resume();
923
- return config;
924
- }
925
928
  // 1. Check for custom slash commands (.devx/commands/*.md)
926
929
  const customCmd = await CustomCommandManager.findCommand(cmd);
927
930
  if (customCmd) {
@@ -933,7 +936,7 @@ export async function main() {
933
936
  else {
934
937
  const VALID_COMMANDS = [
935
938
  '/new', '/reset', '/resume', '/session', '/sessions', '/history',
936
- '/theme', '/themes', '/usage', '/export',
939
+ '/theme', '/themes', '/usage', '/export', '/mcp',
937
940
  '/settings', '/update', '/model', '/provider', '/providers',
938
941
  '/plan', '/agent', '/image', '/serve', '/memory', '/undo',
939
942
  '/diff', '/commit', '/status', '/compact', '/init', '/doctor',
@@ -951,6 +954,7 @@ export async function main() {
951
954
  { name: '/session del - Select and delete saved sessions', value: '/session del' },
952
955
  { name: '/usage - Show network bandwidth, data saver & token cost', value: '/usage' },
953
956
  { name: '/export - Export session conversation to Markdown', value: '/export' },
957
+ { name: '/mcp - Manage Model Context Protocol (MCP) servers & tools', value: '/mcp' },
954
958
  { name: '/theme - Switch UI color theme', value: '/theme' },
955
959
  { name: '/doctor - Run system & environment health diagnostics', value: '/doctor' },
956
960
  { name: '/settings - Configure permissions & auto-approval', value: '/settings' },
@@ -1189,13 +1193,7 @@ export async function main() {
1189
1193
  p.log.warn('No changes to undo.');
1190
1194
  }
1191
1195
  else {
1192
- const msgs = history.getMessages();
1193
- while (msgs.length > 1 && msgs[msgs.length - 1].role !== 'user') {
1194
- msgs.pop();
1195
- }
1196
- if (msgs.length > 1 && msgs[msgs.length - 1].role === 'user') {
1197
- msgs.pop();
1198
- }
1196
+ history.popLastTurn();
1199
1197
  p.log.success(pc.bold(pc.green(`⏪ Successfully reverted changes in ${count} file(s):`)));
1200
1198
  for (const f of revertedFiles) {
1201
1199
  console.log(pc.cyan(` • ${f}`));
@@ -1472,6 +1470,20 @@ export async function main() {
1472
1470
  }
1473
1471
  continue;
1474
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
+ }
1475
1487
  if (cmd === '/config') {
1476
1488
  const masked = { ...config };
1477
1489
  if (masked.apiKey)
@@ -1916,5 +1928,19 @@ export async function main() {
1916
1928
  }
1917
1929
  }
1918
1930
  }
1931
+ MCPManager.getInstance().stopAll();
1919
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
+ });
1920
1946
  main().catch(console.error);
@@ -10,6 +10,7 @@ export const SLASH_COMMANDS = [
10
10
  { cmd: '/session del', desc: 'Select and delete saved sessions' },
11
11
  { cmd: '/usage', desc: 'Show network bandwidth, data saver & token cost' },
12
12
  { cmd: '/export', desc: 'Export session conversation to Markdown' },
13
+ { cmd: '/mcp', desc: 'Manage Model Context Protocol (MCP) servers & tools' },
13
14
  { cmd: '/theme', desc: 'Switch UI theme (Cyan, Purple, Matrix, Amber, etc.)' },
14
15
  { cmd: '/doctor', desc: 'Run system & environment health diagnostics' },
15
16
  { cmd: '/settings', desc: 'Configure permissions & auto-approval' },
@@ -139,7 +139,7 @@ function renderDirectoryHtml(dirPath, relPath, files, port) {
139
139
  ${parentLink}
140
140
  ${items || '<li style="padding: 20px; text-align: center; color: #6e7681;">No visible files in this directory</li>'}
141
141
  </ul>
142
- <div class="footer">devx v1.3.0 &bull; Terminal-Native AI Assistant</div>
142
+ <div class="footer">devx v1.4.0 &bull; Terminal-Native AI Assistant</div>
143
143
  </div>
144
144
  </body>
145
145
  </html>`;
@@ -85,12 +85,6 @@ export async function checkForUpdates(timeoutMs = 10000) {
85
85
  currentVersion,
86
86
  latestVersion: latestVersion || currentVersion
87
87
  };
88
- return {
89
- updateAvailable: false,
90
- currentVersion,
91
- latestVersion: currentVersion,
92
- error: `HTTP ${res.status}`
93
- };
94
88
  }
95
89
  catch (err) {
96
90
  clearTimeout(timer);
@@ -64,12 +64,12 @@ export class CustomCommandManager {
64
64
  return trimmedArgs ? `${template}\n\nUser Arguments: ${trimmedArgs}` : template;
65
65
  }
66
66
  let expanded = template;
67
- expanded = expanded.replace(/\$ARG/g, trimmedArgs);
68
- expanded = expanded.replace(/\$\*/g, trimmedArgs);
67
+ expanded = expanded.replace(/\$ARG/g, () => trimmedArgs);
68
+ expanded = expanded.replace(/\$\*/g, () => trimmedArgs);
69
69
  // Support positional parameters: $1, $2, etc.
70
70
  const parts = trimmedArgs.split(/\s+/);
71
71
  for (let i = 0; i < parts.length; i++) {
72
- expanded = expanded.replace(new RegExp(`\\$${i + 1}`, 'g'), parts[i]);
72
+ expanded = expanded.replace(new RegExp(`\\$${i + 1}`, 'g'), () => parts[i]);
73
73
  }
74
74
  return expanded;
75
75
  }
@@ -16,6 +16,16 @@ export class History {
16
16
  getMessages() {
17
17
  return [...this.messages];
18
18
  }
19
+ popLastTurn() {
20
+ // Pop assistant and tool messages from the end of history
21
+ while (this.messages.length > 1 && this.messages[this.messages.length - 1].role !== 'user') {
22
+ this.messages.pop();
23
+ }
24
+ // Pop the triggering user message
25
+ if (this.messages.length > 1 && this.messages[this.messages.length - 1].role === 'user') {
26
+ this.messages.pop();
27
+ }
28
+ }
19
29
  clear() {
20
30
  this.messages = [];
21
31
  }
package/dist/core/loop.js CHANGED
@@ -94,9 +94,13 @@ export class Agent {
94
94
  response = chunk.response;
95
95
  }
96
96
  }
97
- if (response || receivedAnyChunk) {
97
+ if (response) {
98
98
  break;
99
99
  }
100
+ if (!receivedAnyChunk) {
101
+ throw new Error('Provider stream ended unexpectedly without receiving any data.');
102
+ }
103
+ break;
100
104
  }
101
105
  else {
102
106
  response = await this.provider.chat(request);
@@ -33,7 +33,7 @@ export function getModelContextLimit(modelName) {
33
33
  return cache[clean];
34
34
  if (cache[short])
35
35
  return cache[short];
36
- const baseName = short.replace(/\-\d {4,8}$/, '').replace(/:latest$/, '');
36
+ const baseName = short.replace(/-\d{4,8}$/, '').replace(/:latest$/, '');
37
37
  if (cache[baseName])
38
38
  return cache[baseName];
39
39
  if (clean.includes('kimi-k2') || clean.includes('kimi'))