@taj-special/dravix-code 1.4.7 → 1.4.9

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/dist/cli/index.js CHANGED
File without changes
package/dist/cli/repl.js CHANGED
@@ -749,16 +749,6 @@ async function askPermission(label, key, alwaysAllowed, noAlways, diffShown, pre
749
749
  if (!noAlways && alwaysAllowed.has(key))
750
750
  return true;
751
751
  return new Promise((resolve) => {
752
- // Auto-resolve after 5 min — prevent hanging process
753
- const timeout = setTimeout(() => {
754
- cleanup();
755
- clearMenu(menuLines);
756
- for (let i = 0; i < previewLineCount - 1; i++) {
757
- process.stdout.write('\x1b[1A\x1b[2K');
758
- }
759
- process.stdout.write(` ${colors.muted('○')} ${colors.muted('Timeout — skipped: ' + label)}\n`);
760
- resolve(false);
761
- }, 300_000);
762
752
  let sel = 0;
763
753
  let drawn = false;
764
754
  const ci = label.indexOf(': ');
@@ -812,11 +802,9 @@ async function askPermission(label, key, alwaysAllowed, noAlways, diffShown, pre
812
802
  }
813
803
  printMenu();
814
804
  function cleanup() {
815
- clearTimeout(timeout);
816
805
  process.stdin.removeListener('data', onData);
817
806
  }
818
807
  function confirm(idx) {
819
- clearTimeout(timeout);
820
808
  cleanup();
821
809
  clearMenu(menuLines);
822
810
  // Clear the diff preview lines (all except the first blank separator)
@@ -877,12 +865,17 @@ async function askPermission(label, key, alwaysAllowed, noAlways, diffShown, pre
877
865
  });
878
866
  }
879
867
  async function readLine(prompt, cwd) {
880
- const sepRow = (process.stdout.rows ?? 24) - 2;
881
- const inpRow = (process.stdout.rows ?? 24) - 1;
882
- let SEP = colors.dim(' ' + '─'.repeat(Math.max((process.stdout.columns ?? 80) - 4, 40)));
883
- // Draw fixed bottom separator + input
884
- process.stdout.write(`\x1b[${sepRow};1H\x1b[2K${SEP}`);
885
- process.stdout.write(`\x1b[${inpRow};1H\x1b[2K${prompt}`);
868
+ const rows = process.stdout.rows ?? 24;
869
+ const cols = process.stdout.columns ?? 80;
870
+ const sR = rows - 2;
871
+ const iR = rows - 1;
872
+ let SEP = colors.dim(' ' + '─'.repeat(Math.max(cols - 4, 40)));
873
+ // Ensure scroll region is active for chat history (top rows)
874
+ process.stdout.write(`\x1b[1;${rows - 3}r`);
875
+ // Render fixed bottom container (separator + input bar)
876
+ process.stdout.write(`\x1b[${sR};1H\x1b[2K${SEP}`);
877
+ process.stdout.write(`\x1b[${iR};1H\x1b[2K${prompt}`);
878
+ process.stdout.write(`\x1b[${iR};5H`);
886
879
  return new Promise((resolve) => {
887
880
  let prefix = '';
888
881
  let pasteBlock = null;
@@ -908,37 +901,19 @@ async function readLine(prompt, cwd) {
908
901
  let tabPreWord = '';
909
902
  function onResize() {
910
903
  const newCols = process.stdout.columns ?? 80;
904
+ const newRows = process.stdout.rows ?? 24;
905
+ process.stdout.write(`\x1b[1;${newRows - 3}r`);
911
906
  SEP = colors.dim(' ' + '─'.repeat(Math.max(newCols - 4, 40)));
912
907
  if (atMode) {
913
- for (let i = 0; i < atBoxLines + 3; i++)
914
- process.stdout.write('\x1b[1A');
915
- process.stdout.write('\r\x1b[0J');
916
- atDrawn = false;
917
- atBoxLines = 0;
918
908
  drawPicker();
919
909
  }
920
910
  else if (slashMode) {
921
- for (let i = 0; i < slashBoxLines + 3; i++)
922
- process.stdout.write('\x1b[1A');
923
- process.stdout.write('\r\x1b[0J');
924
- slashDrawn = false;
925
- slashBoxLines = 0;
926
911
  drawSlashPicker();
927
912
  }
928
913
  else if (tabMode) {
929
- for (let i = 0; i < tabBoxLines + 3; i++)
930
- process.stdout.write('\x1b[1A');
931
- process.stdout.write('\r\x1b[0J');
932
- tabDrawn = false;
933
- tabBoxLines = 0;
934
914
  drawTabPicker();
935
915
  }
936
916
  else {
937
- // No mode active — just redraw the fixed bottom separator + input
938
- const sepR = (process.stdout.rows ?? 24) - 2;
939
- const inpR = (process.stdout.rows ?? 24) - 1;
940
- process.stdout.write(`\x1b[${sepR};1H\x1b[2K${SEP}`);
941
- process.stdout.write(`\x1b[${inpR};1H`);
942
917
  redraw();
943
918
  }
944
919
  }
@@ -1003,233 +978,284 @@ async function readLine(prompt, cwd) {
1003
978
  const lq = q.toLowerCase();
1004
979
  return SLASH_COMMANDS.filter(c => c.name.slice(1).toLowerCase().startsWith(lq));
1005
980
  }
1006
- // Picker layout: input (A) | bot-sep (A+1) | box (A+2 .. A+1+atBoxLines)
1007
- // Clearing box: atBoxLines × \x1b[1A\x1b[2K → cursor at A+2
1008
- // then \x1b[2A\x1b[2K to skip bot-sep and clear input
1009
981
  let atDrawn = false;
1010
982
  function drawPicker() {
1011
- if (atDrawn) {
1012
- for (let i = 0; i < atBoxLines; i++)
1013
- process.stdout.write('\x1b[1A\x1b[2K');
1014
- process.stdout.write('\x1b[2A\x1b[2K');
1015
- }
1016
- else {
1017
- process.stdout.write('\r\x1b[K');
1018
- atDrawn = true;
1019
- }
1020
- atBoxLines = 0;
1021
- const inputLine = colors.primary(' › ') + colors.text(prefix) +
1022
- (pasteBlock !== null ? colors.muted(`[paste: ${pasteCount} lines]`) + colors.text(suffix) : '') +
1023
- colors.primary('@') + colors.text(atQuery);
1024
- process.stdout.write(inputLine + '\r\n' + SEP + '\r\n');
1025
- const DW = Math.min(Math.max((process.stdout.columns ?? 80) - 6, 44), 72);
1026
- const IW = DW - 6;
983
+ const cols = process.stdout.columns ?? 80;
984
+ const rows = process.stdout.rows ?? 24;
985
+ const sR = rows - 2;
986
+ const iR = rows - 1;
1027
987
  const filtered = filterFiles(atQuery);
1028
988
  if (atSel >= filtered.length && filtered.length > 0)
1029
989
  atSel = filtered.length - 1;
1030
- const shown = filtered.slice(0, 10);
990
+ if (atSel < 0)
991
+ atSel = 0;
992
+ const maxVisible = Math.min(8, Math.max(3, rows - 6));
993
+ const shown = filtered.slice(0, maxVisible);
994
+ const DW = Math.min(Math.max(cols - 6, 44), 74);
995
+ const IW = DW - 6;
996
+ const boxLines = [];
1031
997
  const qLabel = atQuery ? `@${atQuery}` : 'files';
1032
- const hDashes = Math.max(DW - 5 - qLabel.length, 1);
1033
- process.stdout.write(colors.dim(' ╭─ ') + colors.primary(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮') + '\n');
1034
- atBoxLines++;
998
+ const hDashes = Math.max(DW - 4 - qLabel.length, 1);
999
+ boxLines.push(colors.dim(' ╭─ ') + colors.primary(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮'));
1035
1000
  if (shown.length === 0) {
1036
1001
  const msg = 'no matches';
1037
1002
  const pad = ' '.repeat(Math.max(IW - msg.length, 0));
1038
- process.stdout.write(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│') + '\n');
1039
- atBoxLines++;
1003
+ boxLines.push(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│'));
1040
1004
  }
1041
1005
  else {
1042
1006
  for (let i = 0; i < shown.length; i++) {
1043
1007
  const f = shown[i];
1044
1008
  const isDir = f.endsWith('/');
1045
1009
  const sel = i === atSel;
1046
- const mark = sel ? colors.primary('>') : ' ';
1010
+ const mark = sel ? colors.primary('') : ' ';
1047
1011
  const clip = f.length > IW ? f.slice(0, IW - 1) + '…' : f;
1048
1012
  const pad = ' '.repeat(Math.max(IW - clip.length, 0));
1049
1013
  const name = sel && isDir ? colors.primary.bold(clip) + pad
1050
1014
  : sel ? colors.primary.bold(clip) + pad
1051
1015
  : isDir ? chalk.hex('#74b9ff')(clip) + pad
1052
1016
  : colors.subtext(clip + pad);
1053
- process.stdout.write(colors.dim(' │') + ` ${mark} ` + name + colors.dim('│') + '\n');
1054
- atBoxLines++;
1017
+ boxLines.push(colors.dim(' │') + ` ${mark} ` + name + colors.dim('│'));
1055
1018
  }
1056
1019
  }
1057
- const hint = ' ↑↓ Enter Esc ';
1020
+ const hint = ' ↑↓ Navigate Enter Select Esc Close ';
1058
1021
  const fInner = DW - 2 - hint.length;
1059
- const fLeft = Math.floor(fInner / 2);
1060
- const fRight = fInner - fLeft;
1061
- process.stdout.write(colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) + colors.muted(hint) + colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯') + '\n');
1062
- atBoxLines++;
1022
+ const fLeft = Math.max(0, Math.floor(fInner / 2));
1023
+ const fRight = Math.max(0, fInner - fLeft);
1024
+ boxLines.push(colors.dim(' ╰' + '─'.repeat(fLeft)) + colors.muted(hint) + colors.dim('─'.repeat(fRight) + '╯'));
1025
+ const newLinesCount = boxLines.length;
1026
+ // Clear any leftover lines from previous taller popup
1027
+ if (atBoxLines > newLinesCount) {
1028
+ for (let r = sR - atBoxLines; r < sR - newLinesCount; r++) {
1029
+ if (r >= 1)
1030
+ process.stdout.write(`\x1b[${r};1H\x1b[2K`);
1031
+ }
1032
+ }
1033
+ // Render popup above separator
1034
+ for (let i = 0; i < newLinesCount; i++) {
1035
+ const targetRow = sR - newLinesCount + i;
1036
+ if (targetRow >= 1) {
1037
+ process.stdout.write(`\x1b[${targetRow};1H\x1b[2K${boxLines[i]}`);
1038
+ }
1039
+ }
1040
+ // Re-draw separator and input
1041
+ process.stdout.write(`\x1b[${sR};1H\x1b[2K${SEP}`);
1042
+ const inputLine = colors.primary(' › ') + colors.text(prefix) +
1043
+ (pasteBlock !== null ? colors.muted(`[paste: ${pasteCount} lines]`) + colors.text(suffix) : '') +
1044
+ colors.primary('@') + colors.text(atQuery);
1045
+ process.stdout.write(`\x1b[${iR};1H\x1b[2K${inputLine}`);
1046
+ const cursorCol = 5 + prefix.length + (pasteBlock !== null ? `[paste: ${pasteCount} lines]`.length + suffix.length : 0) + 1 + atQuery.length;
1047
+ process.stdout.write(`\x1b[${iR};${cursorCol}H`);
1048
+ atBoxLines = newLinesCount;
1049
+ atDrawn = true;
1063
1050
  }
1064
1051
  function closePicker() {
1065
- if (atDrawn) {
1066
- for (let i = 0; i < atBoxLines; i++)
1067
- process.stdout.write('\x1b[1A\x1b[2K');
1068
- process.stdout.write('\x1b[2A\x1b[2K');
1069
- atDrawn = false;
1070
- }
1071
- else {
1072
- process.stdout.write('\r\x1b[K');
1052
+ const rows = process.stdout.rows ?? 24;
1053
+ const sR = rows - 2;
1054
+ if (atDrawn && atBoxLines > 0) {
1055
+ for (let r = sR - atBoxLines; r < sR; r++) {
1056
+ if (r >= 1)
1057
+ process.stdout.write(`\x1b[${r};1H\x1b[2K`);
1058
+ }
1073
1059
  }
1074
1060
  atBoxLines = 0;
1075
1061
  atMode = false;
1076
1062
  atQuery = '';
1077
1063
  atSel = 0;
1064
+ atDrawn = false;
1078
1065
  }
1079
1066
  function drawSlashPicker() {
1080
- if (slashDrawn) {
1081
- for (let i = 0; i < slashBoxLines; i++)
1082
- process.stdout.write('\x1b[1A\x1b[2K');
1083
- process.stdout.write('\x1b[2A\x1b[2K');
1084
- }
1085
- else {
1086
- process.stdout.write('\r\x1b[K');
1087
- slashDrawn = true;
1088
- }
1089
- slashBoxLines = 0;
1090
- const inputLine = colors.primary(' › ') + colors.primary('/') + colors.text(slashQuery);
1091
- process.stdout.write(inputLine + '\r\n' + SEP + '\r\n');
1092
- const DW = Math.min(Math.max((process.stdout.columns ?? 80) - 6, 44), 72);
1093
- const nameW = 10;
1094
- const descW = DW - nameW - 8;
1067
+ const cols = process.stdout.columns ?? 80;
1068
+ const rows = process.stdout.rows ?? 24;
1069
+ const sR = rows - 2;
1070
+ const iR = rows - 1;
1095
1071
  const filtered = filterSlash(slashQuery);
1096
1072
  if (slashSel >= filtered.length && filtered.length > 0)
1097
1073
  slashSel = filtered.length - 1;
1074
+ if (slashSel < 0)
1075
+ slashSel = 0;
1076
+ const maxVisible = Math.min(8, Math.max(3, rows - 6));
1077
+ const shown = filtered.slice(0, maxVisible);
1078
+ const DW = Math.min(Math.max(cols - 6, 44), 74);
1079
+ const nameW = 11;
1080
+ const descW = Math.max(DW - nameW - 9, 10);
1081
+ const boxLines = [];
1098
1082
  const qLabel = slashQuery ? `/${slashQuery}` : 'commands';
1099
- const hDashes = Math.max(DW - 5 - qLabel.length, 1);
1100
- process.stdout.write(colors.dim(' ╭─ ') + colors.primary(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮') + '\n');
1101
- slashBoxLines++;
1083
+ const hDashes = Math.max(DW - 4 - qLabel.length, 1);
1084
+ boxLines.push(colors.dim(' ╭─ ') + colors.primary(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮'));
1102
1085
  if (filtered.length === 0) {
1103
1086
  const msg = 'no matching commands';
1104
- const pad = ' '.repeat(Math.max(DW - 4 - msg.length, 0));
1105
- process.stdout.write(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│') + '\n');
1106
- slashBoxLines++;
1087
+ const pad = ' '.repeat(Math.max(DW - 4 - msg.length - 2, 0));
1088
+ boxLines.push(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│'));
1107
1089
  }
1108
1090
  else {
1109
- for (let i = 0; i < filtered.length; i++) {
1110
- const cmd = filtered[i];
1091
+ for (let i = 0; i < shown.length; i++) {
1092
+ const cmd = shown[i];
1111
1093
  const sel = i === slashSel;
1112
- const mark = sel ? colors.primary('>') : ' ';
1094
+ const mark = sel ? colors.primary('') : ' ';
1113
1095
  const nameStr = cmd.name.padEnd(nameW);
1114
1096
  const descStr = cmd.desc.length > descW ? cmd.desc.slice(0, descW - 1) + '…' : cmd.desc;
1115
1097
  const descPad = ' '.repeat(Math.max(descW - descStr.length, 0));
1116
1098
  const namePaint = sel ? colors.primary.bold(nameStr) : colors.primary(nameStr);
1117
- const descPaint = sel ? colors.muted(descStr) : colors.subtext(descStr);
1118
- process.stdout.write(colors.dim(' │') + ` ${mark} ` + namePaint + ' ' + descPaint + descPad + colors.dim('│') + '\n');
1119
- slashBoxLines++;
1099
+ const descPaint = sel ? chalk.hex('#f1f5f9')(descStr) : colors.subtext(descStr);
1100
+ boxLines.push(colors.dim(' │') + ` ${mark} ` + namePaint + ' ' + descPaint + descPad + colors.dim('│'));
1120
1101
  }
1121
1102
  }
1122
- const hint = ' ↑↓ Enter Esc ';
1103
+ const hint = ' ↑↓ Navigate Tab Complete ↵ Run Esc Close ';
1123
1104
  const fInner = DW - 2 - hint.length;
1124
- const fLeft = Math.floor(fInner / 2);
1125
- const fRight = fInner - fLeft;
1126
- process.stdout.write(colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) + colors.muted(hint) + colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯') + '\n');
1127
- slashBoxLines++;
1105
+ const fLeft = Math.max(0, Math.floor(fInner / 2));
1106
+ const fRight = Math.max(0, fInner - fLeft);
1107
+ boxLines.push(colors.dim(' ╰' + '─'.repeat(fLeft)) + colors.muted(hint) + colors.dim('─'.repeat(fRight) + '╯'));
1108
+ const newLinesCount = boxLines.length;
1109
+ // Clear any leftover lines from previous taller popup
1110
+ if (slashBoxLines > newLinesCount) {
1111
+ for (let r = sR - slashBoxLines; r < sR - newLinesCount; r++) {
1112
+ if (r >= 1)
1113
+ process.stdout.write(`\x1b[${r};1H\x1b[2K`);
1114
+ }
1115
+ }
1116
+ // Render popup above separator
1117
+ for (let i = 0; i < newLinesCount; i++) {
1118
+ const targetRow = sR - newLinesCount + i;
1119
+ if (targetRow >= 1) {
1120
+ process.stdout.write(`\x1b[${targetRow};1H\x1b[2K${boxLines[i]}`);
1121
+ }
1122
+ }
1123
+ // Re-draw separator and input line
1124
+ process.stdout.write(`\x1b[${sR};1H\x1b[2K${SEP}`);
1125
+ const inputLine = colors.primary(' › ') + colors.primary('/') + colors.text(slashQuery);
1126
+ process.stdout.write(`\x1b[${iR};1H\x1b[2K${inputLine}`);
1127
+ process.stdout.write(`\x1b[${iR};${5 + 1 + slashQuery.length}H`);
1128
+ slashBoxLines = newLinesCount;
1129
+ slashDrawn = true;
1128
1130
  }
1129
1131
  function closeSlashPicker() {
1130
- if (slashDrawn) {
1131
- for (let i = 0; i < slashBoxLines; i++)
1132
- process.stdout.write('\x1b[1A\x1b[2K');
1133
- process.stdout.write('\x1b[2A\x1b[2K');
1134
- slashDrawn = false;
1135
- }
1136
- else {
1137
- process.stdout.write('\r\x1b[K');
1132
+ const rows = process.stdout.rows ?? 24;
1133
+ const sR = rows - 2;
1134
+ if (slashDrawn && slashBoxLines > 0) {
1135
+ for (let r = sR - slashBoxLines; r < sR; r++) {
1136
+ if (r >= 1)
1137
+ process.stdout.write(`\x1b[${r};1H\x1b[2K`);
1138
+ }
1138
1139
  }
1139
1140
  slashBoxLines = 0;
1140
1141
  slashMode = false;
1141
1142
  slashQuery = '';
1142
1143
  slashSel = 0;
1144
+ slashDrawn = false;
1143
1145
  }
1144
1146
  function drawTabPicker() {
1145
- if (tabDrawn) {
1146
- for (let i = 0; i < tabBoxLines; i++)
1147
- process.stdout.write('\x1b[1A\x1b[2K');
1148
- process.stdout.write('\x1b[2A\x1b[2K');
1149
- }
1150
- else {
1151
- process.stdout.write('\r\x1b[K');
1152
- tabDrawn = true;
1153
- }
1154
- tabBoxLines = 0;
1147
+ const cols = process.stdout.columns ?? 80;
1148
+ const rows = process.stdout.rows ?? 24;
1149
+ const sR = rows - 2;
1150
+ const iR = rows - 1;
1155
1151
  const TC = colors.success;
1156
- const inputLine = colors.primary(' › ') + colors.text(tabPreWord) + TC(tabQuery);
1157
- process.stdout.write(inputLine + '\r\n' + SEP + '\r\n');
1158
- const DW = Math.min(Math.max((process.stdout.columns ?? 80) - 6, 44), 72);
1152
+ const DW = Math.min(Math.max(cols - 6, 44), 74);
1159
1153
  const IW = DW - 6;
1160
1154
  const filtered = filterFiles(tabQuery);
1161
1155
  if (tabSel >= filtered.length && filtered.length > 0)
1162
1156
  tabSel = filtered.length - 1;
1163
- const shown = filtered.slice(0, 10);
1157
+ if (tabSel < 0)
1158
+ tabSel = 0;
1159
+ const maxVisible = Math.min(8, Math.max(3, rows - 6));
1160
+ const shown = filtered.slice(0, maxVisible);
1161
+ const boxLines = [];
1164
1162
  const qLabel = tabQuery || 'tab complete';
1165
- const hDashes = Math.max(DW - 5 - qLabel.length, 1);
1166
- process.stdout.write(colors.dim(' ╭─ ') + TC(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮') + '\n');
1167
- tabBoxLines++;
1163
+ const hDashes = Math.max(DW - 4 - qLabel.length, 1);
1164
+ boxLines.push(colors.dim(' ╭─ ') + TC(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮'));
1168
1165
  if (shown.length === 0) {
1169
1166
  const msg = 'no matches';
1170
1167
  const pad = ' '.repeat(Math.max(IW - msg.length, 0));
1171
- process.stdout.write(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│') + '\n');
1172
- tabBoxLines++;
1168
+ boxLines.push(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│'));
1173
1169
  }
1174
1170
  else {
1175
1171
  for (let i = 0; i < shown.length; i++) {
1176
1172
  const f = shown[i];
1177
1173
  const isDir = f.endsWith('/');
1178
1174
  const sel = i === tabSel;
1179
- const mark = sel ? TC('>') : ' ';
1175
+ const mark = sel ? TC('') : ' ';
1180
1176
  const clip = f.length > IW ? f.slice(0, IW - 1) + '…' : f;
1181
1177
  const pad = ' '.repeat(Math.max(IW - clip.length, 0));
1182
1178
  const name = sel ? TC.bold(clip) + pad
1183
1179
  : isDir ? chalk.hex('#74b9ff')(clip) + pad
1184
1180
  : colors.subtext(clip + pad);
1185
- process.stdout.write(colors.dim(' │') + ` ${mark} ` + name + colors.dim('│') + '\n');
1186
- tabBoxLines++;
1181
+ boxLines.push(colors.dim(' │') + ` ${mark} ` + name + colors.dim('│'));
1187
1182
  }
1188
1183
  }
1189
- const hint = ' Tab/↑↓ Enter Esc ';
1184
+ const hint = ' Tab/↑↓ Navigate Enter Select Esc Close ';
1190
1185
  const fInner = DW - 2 - hint.length;
1191
1186
  const fLeft = Math.floor(fInner / 2);
1192
1187
  const fRight = fInner - fLeft;
1193
- process.stdout.write(colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) + colors.muted(hint) + colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯') + '\n');
1194
- tabBoxLines++;
1188
+ boxLines.push(colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) + colors.muted(hint) + colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯'));
1189
+ const newLinesCount = boxLines.length;
1190
+ // Clear any leftover lines from previous taller popup
1191
+ if (tabBoxLines > newLinesCount) {
1192
+ for (let r = sR - tabBoxLines; r < sR - newLinesCount; r++) {
1193
+ if (r >= 1)
1194
+ process.stdout.write(`\x1b[${r};1H\x1b[2K`);
1195
+ }
1196
+ }
1197
+ // Render popup above separator
1198
+ for (let i = 0; i < newLinesCount; i++) {
1199
+ const targetRow = sR - newLinesCount + i;
1200
+ if (targetRow >= 1) {
1201
+ process.stdout.write(`\x1b[${targetRow};1H\x1b[2K${boxLines[i]}`);
1202
+ }
1203
+ }
1204
+ // Re-draw separator and input line
1205
+ process.stdout.write(`\x1b[${sR};1H\x1b[2K${SEP}`);
1206
+ const inputLine = colors.primary(' › ') + colors.text(tabPreWord) + TC(tabQuery);
1207
+ process.stdout.write(`\x1b[${iR};1H\x1b[2K${inputLine}`);
1208
+ const cursorCol = 5 + tabPreWord.length + tabQuery.length;
1209
+ process.stdout.write(`\x1b[${iR};${cursorCol}H`);
1210
+ tabBoxLines = newLinesCount;
1211
+ tabDrawn = true;
1195
1212
  }
1196
1213
  function closeTabPicker() {
1197
- if (tabDrawn) {
1198
- for (let i = 0; i < tabBoxLines; i++)
1199
- process.stdout.write('\x1b[1A\x1b[2K');
1200
- process.stdout.write('\x1b[2A\x1b[2K');
1201
- tabDrawn = false;
1202
- }
1203
- else {
1204
- process.stdout.write('\r\x1b[K');
1214
+ const rows = process.stdout.rows ?? 24;
1215
+ const sR = rows - 2;
1216
+ if (tabDrawn && tabBoxLines > 0) {
1217
+ for (let r = sR - tabBoxLines; r < sR; r++) {
1218
+ if (r >= 1)
1219
+ process.stdout.write(`\x1b[${r};1H\x1b[2K`);
1220
+ }
1205
1221
  }
1206
1222
  tabBoxLines = 0;
1207
1223
  tabMode = false;
1208
1224
  tabQuery = '';
1209
1225
  tabSel = 0;
1210
1226
  tabPreWord = '';
1227
+ tabDrawn = false;
1211
1228
  }
1212
1229
  function redraw() {
1213
1230
  const cols = process.stdout.columns ?? 80;
1214
- const inpRow = (process.stdout.rows ?? 24) - 1;
1215
- const maxLen = Math.max(cols - 6, 10);
1231
+ const rows = process.stdout.rows ?? 24;
1232
+ const sR = rows - 2;
1233
+ const iR = rows - 1;
1234
+ const maxLen = Math.max(cols - 8, 10);
1216
1235
  let displayPre = prefix;
1217
1236
  if (displayPre.length > maxLen)
1218
1237
  displayPre = '…' + displayPre.slice(-(maxLen - 1));
1219
1238
  let inp;
1239
+ let cursorCol = 5 + displayPre.length;
1220
1240
  if (pasteBlock !== null) {
1241
+ const pTag = `[paste: ${pasteCount} lines]`;
1221
1242
  inp = colors.primary(' › ') + colors.text(displayPre) +
1222
- colors.muted(`[paste: ${pasteCount} lines]`) + colors.text(suffix);
1243
+ colors.muted(pTag) + colors.text(suffix);
1244
+ cursorCol = 5 + displayPre.length + pTag.length + suffix.length;
1223
1245
  }
1224
1246
  else {
1225
- inp = colors.primary(' › ') + colors.text(displayPre);
1247
+ inp = colors.primary(' › ') + colors.text(displayPre) + colors.text(suffix);
1248
+ cursorCol = 5 + displayPre.length;
1226
1249
  }
1227
- process.stdout.write(`\x1b[${inpRow};1H\x1b[2K` + inp);
1228
- process.stdout.write('\r\n' + SEP + '\x1b[1A\r' + inp);
1250
+ process.stdout.write(`\x1b[${sR};1H\x1b[2K${SEP}`);
1251
+ process.stdout.write(`\x1b[${iR};1H\x1b[2K${inp}`);
1252
+ process.stdout.write(`\x1b[${iR};${cursorCol}H`);
1229
1253
  }
1230
1254
  function submit() {
1231
1255
  if (atMode)
1232
1256
  closePicker();
1257
+ if (slashMode)
1258
+ closeSlashPicker();
1233
1259
  if (tabMode) {
1234
1260
  prefix = tabPreWord + tabQuery;
1235
1261
  closeTabPicker();
@@ -1244,19 +1270,24 @@ async function readLine(prompt, cwd) {
1244
1270
  const text = parts.join('\n');
1245
1271
  const lns = parts.length > 0 ? parts : (text ? [text] : []);
1246
1272
  cleanup();
1247
- process.stdout.write('\r\x1b[K');
1248
- process.stdout.write('\x1b[1B\x1b[2K');
1249
- process.stdout.write('\x1b[2A\x1b[2K');
1250
- // Move cursor to bottom of scroll region for next AI output
1251
- const scrEnd = (process.stdout.rows ?? 24) - 3;
1252
- process.stdout.write(`\x1b[${scrEnd};1H`);
1273
+ const rows = process.stdout.rows ?? 24;
1274
+ const cols = process.stdout.columns ?? 80;
1275
+ const sR = rows - 2;
1276
+ const iR = rows - 1;
1277
+ const scrBottom = rows - 3;
1278
+ // Clear fixed bottom input line
1279
+ process.stdout.write(`\x1b[${iR};1H\x1b[2K`);
1253
1280
  if (!text) {
1281
+ process.stdout.write(`\x1b[${sR};1H\x1b[2K${SEP}`);
1282
+ process.stdout.write(`\x1b[${iR};1H\x1b[2K${prompt}`);
1283
+ process.stdout.write(`\x1b[${iR};5H`);
1254
1284
  resolve(null);
1255
1285
  return;
1256
1286
  }
1257
- const cols = process.stdout.columns ?? 80;
1258
1287
  const msgBg = chalk.bgHex('#334155').hex('#f1f5f9');
1259
1288
  const fillMsg = (raw) => (' > ' + raw + ' ').padEnd(cols);
1289
+ // Move to bottom of scroll region to append user message into chat
1290
+ process.stdout.write(`\x1b[${scrBottom};1H\n`);
1260
1291
  if (pasteBlock !== null) {
1261
1292
  const disp = [
1262
1293
  prefix ? prefix : '',
@@ -1268,6 +1299,7 @@ async function readLine(prompt, cwd) {
1268
1299
  else {
1269
1300
  process.stdout.write(msgBg(fillMsg(text)) + '\n');
1270
1301
  }
1302
+ process.stdout.write(`\x1b[${scrBottom};1H`);
1271
1303
  resolve({ text, lines: lns });
1272
1304
  }
1273
1305
  function onData(data) {
@@ -1327,9 +1359,14 @@ async function readLine(prompt, cwd) {
1327
1359
  closeSlashPicker();
1328
1360
  if (tabMode)
1329
1361
  closeTabPicker();
1362
+ const curRows = process.stdout.rows ?? 24;
1363
+ const curSR = curRows - 2;
1364
+ const curIR = curRows - 1;
1330
1365
  console.clear();
1331
- process.stdout.write('\n' + SEP + '\n' + prompt);
1332
- process.stdout.write('\r\n' + SEP + '\x1b[1A\r' + prompt);
1366
+ process.stdout.write(`\x1b[1;${curRows - 3}r`);
1367
+ process.stdout.write(`\x1b[${curSR};1H\x1b[2K${SEP}`);
1368
+ process.stdout.write(`\x1b[${curIR};1H\x1b[2K${prompt}`);
1369
+ process.stdout.write(`\x1b[${curIR};5H`);
1333
1370
  return;
1334
1371
  }
1335
1372
  // ── @ picker mode ─────────────────────────────────────────
@@ -1461,7 +1498,22 @@ async function readLine(prompt, cwd) {
1461
1498
  }
1462
1499
  return;
1463
1500
  }
1464
- if (data === '\r' || data === '\n' || data === '\t') {
1501
+ if (data === '\r' || data === '\n') {
1502
+ const filtered = filterSlash(slashQuery);
1503
+ if (filtered.length > 0) {
1504
+ const cmd = filtered[Math.min(slashSel, filtered.length - 1)];
1505
+ prefix = cmd.name;
1506
+ closeSlashPicker();
1507
+ submit();
1508
+ }
1509
+ else {
1510
+ prefix = '/' + slashQuery;
1511
+ closeSlashPicker();
1512
+ submit();
1513
+ }
1514
+ return;
1515
+ }
1516
+ if (data === '\t') {
1465
1517
  const filtered = filterSlash(slashQuery);
1466
1518
  if (filtered.length > 0) {
1467
1519
  const cmd = filtered[Math.min(slashSel, filtered.length - 1)];
@@ -1711,8 +1763,8 @@ function timeAgoShort(iso) {
1711
1763
  return `${d}d`;
1712
1764
  return `${Math.floor(d / 30)}mo`;
1713
1765
  }
1714
- async function showConversationPicker(cwdForList) {
1715
- const allConvs = listConversations(cwdForList, 100);
1766
+ async function showConversationPicker(cwd) {
1767
+ const allConvs = listConversations(cwd, 100);
1716
1768
  if (allConvs.length === 0) {
1717
1769
  printInfo('No saved conversations yet.');
1718
1770
  return null;
@@ -2149,159 +2201,123 @@ async function showDesignModeLoader(skills) {
2149
2201
  }
2150
2202
  export async function startRepl(cwd) {
2151
2203
  const alwaysAllowed = new Set();
2152
- // ── Terminal title helper ─────────────────────────────────────
2153
- function setTerminalTitle(title) {
2154
- process.stdout.write(`\x1b]0;Dravix Code — ${title}\x07`);
2155
- }
2156
- setTerminalTitle('AI-powered coding assistant');
2157
2204
  const token = getToken() ?? '';
2158
- let SYSTEM_PROMPT = `You are Dravix Code, an interactive CLI coding agent powered by DeepSeek that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
2159
-
2160
- You are a proactive, precise, and safe engineering partner. You think before you act, you verify your work, and you communicate clearly.
2161
-
2162
- IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
2163
-
2164
- # Core Principles
2165
-
2166
- - Be correct, helpful, and safe above all else.
2167
- - Think step by step before writing or modifying any code. State the plan in one short sentence before acting.
2168
- - Prefer minimal, surgical changes that preserve the existing codebase structure and conventions.
2169
- - Never guess at APIs, libraries, or configurations. Read the codebase first, or admit uncertainty.
2170
- - Respect the user's time: be concise but never omit crucial details.
2171
- - If a request is genuinely ambiguous and blocks progress, ask one short clarifying question. Otherwise make the reasonable call and continue.
2172
-
2173
- # File Operations
2174
-
2175
- - read_file = read a file (returns numbered lines like " 42 │ code")
2176
- - write_file = CREATE new files only — NEVER overwrite existing files
2177
- - edit_file = MODIFY existing files (use <find>/<replace> with EXACT text from read_file)
2178
- - create_folder = create directories
2179
- - delete_file = delete files
2180
- - run_command = execute shell commands
2181
- - read_folder = list directory contents
2182
- - search_code = search file contents with regex or literal text
2183
- - glob_files = find files by glob pattern like "**/*.ts"
2184
-
2185
- # edit_file rules — CRITICAL
2186
-
2187
- 1. ALWAYS read_file first to see the current content
2188
- 2. Copy the EXACT text from the file into <find> — character by character
2189
- 3. The <find> text must match EXACTLY (including whitespace, indentation)
2190
- 4. Do NOT put line numbers in <find> (e.g., "42 │ code" is WRONG — use only "code")
2191
- 5. If edit fails, the system will show you the actual content — use it to retry
2192
- 6. Multiple edits on the same file: order from BOTTOM to TOP
2193
- 7. NEVER use write_file on an existing file — it destroys user code
2194
-
2195
- # Communication Style
2196
-
2197
- - Output is rendered in a terminal as Markdown. Be short and concise.
2198
- - Reference code with \`file_path:line_number\` so the user can jump to it.
2199
- - Do not start replies with conversational filler ("Got it", "Sure", "Great question"). State the action or result directly.
2200
- - Do not narrate internal deliberation. Give brief updates at key moments.
2201
- - End-of-turn summary: one or two sentences. What changed and what's next.
2202
- - Respond in the same language the user writes in.
2203
-
2204
- # Code Quality
2205
-
2206
- - Keep things in one function unless composable or reusable.
2207
- - Avoid try/catch where possible; avoid the any type.
2208
- - Prefer functional array methods (flatMap/filter/map) over for loops.
2209
- - Prefer const. Use ternaries or early returns instead of reassignment. Avoid else after return.
2210
- - Do not destructure unnecessarily — obj.a over const { a } = obj.
2211
- - Don't add features, refactors, or abstractions beyond what the task requires.
2212
- - Don't add error handling for impossible scenarios.
2213
- - Delete unused code completely rather than leaving _var or comments.
2214
- - Default to writing no comments. Only comment when the WHY is non-obvious.
2215
-
2216
- # Safety
2217
-
2218
- - Never generate malicious, obfuscated, or deceptive code.
2219
- - Do not output secrets, tokens, or private keys.
2220
- - Guard against SQL injection, XSS, path traversal, insecure deserialization, command injection.
2221
- - When you encounter an obstacle, do not use destructive actions as a shortcut.
2222
-
2223
- # Working discipline
2224
-
2225
- - If the user asks to create a file: use write_file, NOT code blocks.
2226
- - Read files before editing them — always!
2227
- - Plan all files first, create them in logical order, run commands last.
2228
- - Only touch files the user explicitly mentioned or clearly required by the task.
2229
- - When fixing a bug, fix the ROOT CAUSE, not just the symptom.
2230
- - If a run_command fails, read the error message and fix the issue.
2231
- - Complete the ENTIRE task in ONE response — do not ask for permission to continue.`;
2205
+ let SYSTEM_PROMPT = 'You are Dravix Code an elite AI coding assistant. The server is temporarily unreachable. Use your best judgment to help the user.';
2232
2206
  if (token) {
2233
2207
  const { prompt, webDesignerSkill } = await fetchSystemPrompt(token);
2234
2208
  if (prompt) {
2235
- SYSTEM_PROMPT = prompt + '\n\n' + SYSTEM_PROMPT;
2209
+ SYSTEM_PROMPT = prompt;
2236
2210
  _serverWebDesignerSkill = webDesignerSkill;
2237
2211
  }
2238
2212
  }
2239
2213
  // activeCwd can change when /resume loads a conversation from a different directory
2240
2214
  let activeCwd = cwd;
2241
2215
  // Rules appended to every system prompt to enforce safe file-operation behavior
2242
- const SAFE_FILE_RULES = `
2243
-
2244
- ## XML Tag Format (REQUIRED — use these EXACTLY)
2245
-
2246
- All file operations MUST use these tags — NEVER put them in markdown code blocks:
2247
-
2248
- ### <write_file path="relative/path"> ... </write_file>
2249
- Create a NEW file. The path is relative to the project root. The content goes between the tags.
2250
-
2251
- ### <edit_file path="relative/path">
2252
- ### <find>EXACT text to find</find>
2253
- ### <replace>replacement text</replace>
2254
- ### </edit_file>
2255
- Modify an EXISTING file. The <find> text must match EXACTLY copy it verbatim from read_file output, WITHOUT the line number prefixes (e.g., if read_file shows " 42 │ code", use just "code" in <find>).
2256
-
2257
- ### <read_file path="relative/path" />
2258
- Read a file. Optional: lines="100-200" to read a specific range.
2259
-
2260
- ### <read_folder path="relative/path" />
2261
- List directory contents with file sizes.
2262
-
2263
- ### <create_folder path="relative/path" />
2264
- Create a new directory (and parents if needed).
2265
-
2266
- ### <delete_file path="relative/path" />
2267
- Delete a file or directory.
2268
-
2269
- ### <run_command>command here</run_command>
2270
- Execute a shell command. Optional: cwd="subdir" to run in a subdirectory.
2271
-
2272
- ### <search_code pattern="text" />
2273
- Search file contents. Options: path="dir", include="*.ts", context="3", regex="true".
2274
-
2275
- ### <glob_files pattern="**/*.ts" />
2276
- Find files by glob pattern.
2277
-
2278
- ## Critical Reminders
2279
-
2280
- - write_file = NEW files ONLY. For existing files, ALWAYS use edit_file.
2281
- - read_file shows line numbers as " N │ code" — strip the "N " prefix when copying into <find>.
2282
- - Multiple edit_file on the same file: order from BOTTOM to TOP (line numbers shift as you edit).
2283
- - DO NOT output <write_file> / <edit_file> / <run_command> tags inside markdown code blocks — they must be raw in the response.
2284
- - Execute ALL operations in ONE response when possible — do not split work across multiple turns.
2285
- - ⚠️ CRITICAL: If you write "now I will run/start/execute/install X" — you MUST also include the <run_command> tag. Saying it is NOT enough. Always output the actual tag with the command.
2286
-
2287
- ## End-of-turn summary REQUIRED after every task
2288
-
2289
- After you finish ALL operations for the user's request, you MUST write a short, natural summary. This is the most important part of your response.
2290
-
2291
- Write 1-3 sentences in the user's language. No tables. No emoji lists. No rigid formats. Just explain what you did, like a colleague updating a teammate.
2292
-
2293
- Good examples:
2294
- - "I deleted BOT_INFO.md from the project. No other changes were made."
2295
- - "Updated main.ts changed the port from 8000 to 9000. The API now listens on port 9000."
2296
- - "Created three files: auth.ts, routes.ts, and models.ts. The basic API structure is ready."
2297
- - "Fixed the import path in app.tsx and removed the unused variable in utils.ts."
2298
-
2299
- Bad examples:
2300
- - Outputting operation tags with no human-readable text
2301
- - Saying only "Done" or "Summary:" with a rigid table of emojis
2302
- - Ending with nothing — just silence after the last XML tag
2303
-
2304
- The summary should feel like a natural conversation close — informative, brief, in the user's language.`;
2216
+ const SAFE_FILE_RULES = `
2217
+
2218
+ ## CORE RULES
2219
+
2220
+ ### READ FIRST, THEN ANSWER
2221
+ When the user asks about their code:
2222
+ 1. Find the relevant file(s)
2223
+ 2. Read them silently
2224
+ 3. Give a direct, accurate answer based on what you found
2225
+ Never guess or fabricate information.
2226
+
2227
+ ### BE ACCURATE
2228
+ - Only report what you actually see in the code
2229
+ - If you are unsure, say sodo not fabricate answers
2230
+ - Never add features or items that do not exist in the code
2231
+
2232
+ ### RESPOND IN THE SAME LANGUAGE
2233
+ - Respond in the same language the user writes in
2234
+ - If user writes in English → respond in English
2235
+ - If user writes in Russian → respond in Russian
2236
+ - And so on for any language
2237
+
2238
+ ### FORMAT ANSWERS CLEARLY
2239
+ - Use bullet points or numbered lists for multiple items
2240
+ - Put each item on its own line
2241
+ - Keep answers concise — 3-10 lines maximum
2242
+ - Do not dump entire file contents unless asked
2243
+ - Do not add unnecessary commentary
2244
+
2245
+ ### WORK SILENTLY
2246
+ - Do not say "I will read the file" — just read it and answer
2247
+ - Do not narrate your thought process
2248
+ - Do not say "I already answered your question"
2249
+ - Give your answer ONCE and STOP
2250
+
2251
+ ### WHEN CREATING/EDITING FILES
2252
+ - write_file = CREATE new files only
2253
+ - edit_file = MODIFY existing (read first, then find/replace)
2254
+ - Before editing: ALWAYS read_file first
2255
+ - Complete the ENTIRE task in ONE response
2256
+ - Use tags, NOT code blocks
2257
+
2258
+ ### WHEN CREATING/EDITING FILES
2259
+ - write_file = CREATE new files only
2260
+ - edit_file = MODIFY existing (read first, then find/replace)
2261
+ - Before editing: ALWAYS read_file first
2262
+ - Multiple edits: order from BOTTOM to TOP
2263
+ - Complete the ENTIRE task in ONE response
2264
+ - Use tags, NOT code blocks
2265
+
2266
+ ### When the user asks you to do something:
2267
+ 1. Read the relevant files first
2268
+ 2. Do the work using <write_file> / <edit_file> tags
2269
+ 3. Summarize what you did in 1-2 sentences
2270
+
2271
+ ### Act decisively
2272
+ - Use judgment and execute immediately when intent is clear.
2273
+ - Do NOT ask unnecessary clarifying questions — just do the task.
2274
+ - Make reasonable assumptions and proceed.
2275
+
2276
+ ### Creating files
2277
+ - NEVER show code in code blocks when the user asks to create files or a project.
2278
+ - ALWAYS use <write_file path="filename"> tags to create actual files.
2279
+ - Code blocks are for short inline explanations only — they do NOT create files.
2280
+ - Output ALL required files in ONE response using <write_file> tags.
2281
+
2282
+ ### File operations
2283
+ - **write_file** = CREATE only. Use it ONLY when a file does NOT exist yet.
2284
+ - **edit_file** = MODIFY existing files. For ANY change to an existing file, always use edit_file with <find>/<replace>.
2285
+ - Before editing any file: use <read_file> to see its current content first.
2286
+ - NEVER overwrite an existing file with write_file — it destroys user code.
2287
+
2288
+ ### How to use edit_file correctly
2289
+ 1. ALWAYS read_file first to see the current content
2290
+ 2. Copy the EXACT text from the file into <find> — character by character
2291
+ 3. The <find> text must match EXACTLY (including whitespace, indentation)
2292
+ 4. If edit fails, the system will show you the actual content — use it to retry
2293
+ 5. Do NOT put line numbers in <find> (e.g., "42 │ code" is WRONG)
2294
+ 6. Do NOT modify the text in <find> — copy it EXACTLY from the file
2295
+
2296
+ ### Searching and navigating
2297
+ - Use <glob_files pattern="**/*.ts" /> to find files by extension
2298
+ - Use <search_code pattern="functionName" /> to find code
2299
+ - Use <search_code pattern="regex.*pattern" regex="true" /> for regex search
2300
+ - Use <search_code pattern="error" include="*.ts" /> to search specific file types
2301
+ - Use <search_code pattern="TODO" context="2" /> to show context around matches
2302
+ - Read files before editing them — always!
2303
+
2304
+ ### Multi-file tasks
2305
+ - Plan all files FIRST (list them in your response)
2306
+ - Create/edit files in LOGICAL ORDER (dependencies first)
2307
+ - Use write_file for ALL new files
2308
+ - Use edit_file for ALL modifications
2309
+ - Run commands LAST (after all files are created)
2310
+
2311
+ ### Error recovery
2312
+ - If an edit_file fails, read the file again and retry with the actual content
2313
+ - If a run_command fails, read the error message and fix the issue
2314
+ - Never give up — keep trying until the task is complete
2315
+
2316
+ ### Scope
2317
+ - Only touch files the user explicitly mentioned or that are clearly required by the task.
2318
+ - Do NOT restructure, rename, or "improve" anything that wasn't asked.
2319
+ - When creating a new project, create ALL necessary files (not just some).
2320
+ - When fixing a bug, fix the ROOT CAUSE, not just the symptom.`;
2305
2321
  const buildSystemMsg = (dir) => ({
2306
2322
  role: 'system',
2307
2323
  content: SYSTEM_PROMPT + SAFE_FILE_RULES + '\n\nProject context:\n' + buildContext(dir),
@@ -2351,8 +2367,8 @@ The summary should feel like a natural conversation close — informative, brief
2351
2367
  process.on('exit', resetTerminal);
2352
2368
  process.on('SIGINT', () => { resetTerminal(); process.exit(0); });
2353
2369
  process.on('SIGTERM', () => { resetTerminal(); process.exit(0); });
2354
- process.on('unhandledRejection', (reason) => { logError('unhandledRejection', reason); /* don't kill — let process continue */ });
2355
- process.on('uncaughtException', (err) => { logError('uncaughtException', err); /* don't kill — let process continue */ });
2370
+ process.on('unhandledRejection', (reason) => { logError('unhandledRejection', reason); resetTerminal(); });
2371
+ process.on('uncaughtException', (err) => { logError('uncaughtException', err); resetTerminal(); });
2356
2372
  // Set raw mode ONCE — never toggle it during the session to avoid CMD getting stuck
2357
2373
  process.stdin.setRawMode(true);
2358
2374
  process.stdin.resume();
@@ -2373,8 +2389,20 @@ The summary should feel like a natural conversation close — informative, brief
2373
2389
  if (readFileContinue) {
2374
2390
  readFileContinue = false;
2375
2391
  readFileTurnCount++;
2376
- // Keep auto-continue prompt SHORTlarge prompts time out DeepSeek reasoning model
2377
- const taskInstruction = `Continue. All necessary file contents are above in context. Complete the task now — do NOT read more files. Output operations with <edit_file> or <run_command> tags.`;
2392
+ // Remind AI of the original user request show first + last line so pasted code
2393
+ // doesn't eclipse the actual instruction written at the end.
2394
+ const rawUserMsg = lastUserLine.trim();
2395
+ const msgLines = rawUserMsg.split('\n').map(l => l.trim()).filter(Boolean);
2396
+ let userReminder = '';
2397
+ if (msgLines.length > 0) {
2398
+ const first = msgLines[0].slice(0, 100);
2399
+ const last = msgLines[msgLines.length - 1].slice(0, 150);
2400
+ const display = msgLines.length <= 2 ? msgLines.join(' / ').slice(0, 200) : `${first} [...] ${last}`;
2401
+ userReminder = `The user's request: "${display}". `;
2402
+ }
2403
+ const taskInstruction = userReminder
2404
+ ? `The user's exact request was: "${rawUserMsg.slice(0, 300)}"\nNow execute THIS request and ONLY this request — nothing else. Do not invent, add, or change anything the user did not ask for. Use <edit_file> with targeted <find>/<replace> — never use <write_file> on existing files. Use the file content above to find the exact text and apply the change.`
2405
+ : `Execute the user's request using the file content above. Use <edit_file> for existing files, <write_file> only for new files.`;
2378
2406
  // Keep using FLASH_MODEL after file reads
2379
2407
  if (readFileTurnCount > 5 && !forcedEditMode) {
2380
2408
  forcedEditMode = true;
@@ -2436,7 +2464,6 @@ The summary should feel like a natural conversation close — informative, brief
2436
2464
  });
2437
2465
  sessionId = convId;
2438
2466
  conversationTitle = conv.title;
2439
- setTerminalTitle(conv.title);
2440
2467
  printConversationHistory(conv.messages);
2441
2468
  }
2442
2469
  }
@@ -2490,13 +2517,11 @@ The summary should feel like a natural conversation close — informative, brief
2490
2517
  history.splice(1, history.length - 22);
2491
2518
  }
2492
2519
  // ── Auto-inject mentioned file contents ──────────────────────
2493
- // Reduced from 3000 keeps prompt small enough for DeepSeek reasoning model
2520
+ // Files 3000 lines: full raw content injected — AI has exact text for <find>, no reads needed.
2494
2521
  const FILE_PATTERN = /[\w\-.\/\\]+\.\w{2,5}/g;
2495
2522
  const mentioned = [...new Set(line.match(FILE_PATTERN) ?? [])];
2496
2523
  let fileContext = '';
2497
- const FULL_INJECT_LINES = 400;
2498
- const MAX_INJECT_CHARS = 25000;
2499
- let injectedChars = 0;
2524
+ const FULL_INJECT_LINES = 3000;
2500
2525
  for (const fname of mentioned) {
2501
2526
  const fullPath = path.resolve(activeCwd, fname);
2502
2527
  if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
@@ -2504,15 +2529,8 @@ The summary should feel like a natural conversation close — informative, brief
2504
2529
  const content = fs.readFileSync(fullPath, 'utf-8');
2505
2530
  const fileLines = content.split('\n');
2506
2531
  const lineCount = fileLines.length;
2507
- if (lineCount <= FULL_INJECT_LINES && injectedChars < MAX_INJECT_CHARS) {
2508
- const toAdd = `\n\n[File: ${fname} — ${lineCount} lines — FULL]\n` + content;
2509
- if (injectedChars + toAdd.length > MAX_INJECT_CHARS) {
2510
- fileContext += `\n\n[File: ${fname} — ${lineCount} lines — skipped (prompt limit)]`;
2511
- }
2512
- else {
2513
- fileContext += toAdd;
2514
- injectedChars += toAdd.length;
2515
- }
2532
+ if (lineCount <= FULL_INJECT_LINES) {
2533
+ fileContext += `\n\n[File: ${fname} — ${lineCount} lines — FULL]\n` + content;
2516
2534
  }
2517
2535
  else {
2518
2536
  fileContext += `\n\n[File: ${fname} — ${lineCount} lines]\n` +
@@ -2546,7 +2564,6 @@ The summary should feel like a natural conversation close — informative, brief
2546
2564
  }
2547
2565
  // ── Ctrl+C cancel during streaming ──────────────────────────
2548
2566
  let streamCancelled = false;
2549
- let streamInputBuffer = '';
2550
2567
  const streamAbort = new AbortController();
2551
2568
  const onStreamKey = (data) => {
2552
2569
  try {
@@ -2554,10 +2571,6 @@ The summary should feel like a natural conversation close — informative, brief
2554
2571
  streamCancelled = true;
2555
2572
  streamAbort.abort();
2556
2573
  }
2557
- else {
2558
- // Capture user input during stream — will be queued for next turn
2559
- streamInputBuffer += data;
2560
- }
2561
2574
  }
2562
2575
  catch { /* ignore */ }
2563
2576
  };
@@ -2942,7 +2955,6 @@ The summary should feel like a natural conversation close — informative, brief
2942
2955
  if (_u && _a) {
2943
2956
  generateAITitle(String(_u.content), String(_a.content), token).then(t => {
2944
2957
  conversationTitle = t;
2945
- setTerminalTitle(t);
2946
2958
  saveConversation(sessionId, t, activeCwd, history.slice(1));
2947
2959
  }).catch(() => { });
2948
2960
  }
@@ -3029,8 +3041,6 @@ The summary should feel like a natural conversation close — informative, brief
3029
3041
  // Trailing newline — skip when read ops follow (they start on current line)
3030
3042
  if (hasRenderedContent && readOps.length === 0)
3031
3043
  process.stdout.write('\n');
3032
- // Synchronisation flag: ops/else must wait for readOps to finish
3033
- let readOpsDone = readOps.length === 0;
3034
3044
  // ── Handle read_file ops ──────────────────────────────
3035
3045
  // forcedEditMode: block further reads — AI must use existing context
3036
3046
  if (readOps.length > 0 && forcedEditMode) {
@@ -3041,7 +3051,6 @@ The summary should feel like a natural conversation close — informative, brief
3041
3051
  readFileContinue = true;
3042
3052
  }
3043
3053
  else if (readOps.length > 0) {
3044
- readOpsDone = false;
3045
3054
  // Absorb blank lines so reading block starts immediately after user message
3046
3055
  if (hasRenderedContent) {
3047
3056
  // Ensure cursor is on a new line, then absorb excess trailing blank lines
@@ -3219,22 +3228,18 @@ The summary should feel like a natural conversation close — informative, brief
3219
3228
  }
3220
3229
  process.stdout.write(` ` + BC('╰' + '─'.repeat(boxW - 2) + '╯') + `\n`);
3221
3230
  readFileContinue = true;
3222
- readOpsDone = true;
3231
+ resolve();
3223
3232
  }
3224
3233
  catch (e) {
3225
3234
  logError('readOps', e);
3226
- readFileContinue = true;
3227
- readOpsDone = true;
3235
+ resolve();
3228
3236
  }
3229
3237
  })();
3238
+ return;
3230
3239
  } // end else if (readOps.length > 0)
3231
- // Process write/run/mkdir/delete ops — waits for readOps to finish first
3232
3240
  if (ops.length > 0) {
3233
3241
  (async () => {
3234
3242
  try {
3235
- // Wait for any pending read operations to finish
3236
- while (!readOpsDone)
3237
- await new Promise(r => setTimeout(r, 20));
3238
3243
  const skippedPaths = new Set();
3239
3244
  const runOutputs = [];
3240
3245
  const fileOpErrors = [];
@@ -3373,12 +3378,7 @@ The summary should feel like a natural conversation close — informative, brief
3373
3378
  })();
3374
3379
  }
3375
3380
  else {
3376
- // No ops — just wait for readOps to finish, then resolve
3377
- (async () => {
3378
- while (!readOpsDone)
3379
- await new Promise(r => setTimeout(r, 20));
3380
- resolve();
3381
- })();
3381
+ resolve();
3382
3382
  }
3383
3383
  }, (err) => {
3384
3384
  clearInterval(spinnerInterval);
@@ -3409,12 +3409,6 @@ The summary should feel like a natural conversation close — informative, brief
3409
3409
  });
3410
3410
  // ── Remove streaming key listener ─────────────────────────
3411
3411
  process.stdin.removeListener('data', onStreamKey);
3412
- // ── Queue captured input for next user turn ─────────────────
3413
- const captured = streamInputBuffer.trim();
3414
- if (captured && !queuedResult) {
3415
- queuedResult = { text: captured, lines: [captured] };
3416
- }
3417
- streamInputBuffer = '';
3418
3412
  // ── Accumulate token usage (deferred — report on next user turn) ──
3419
3413
  if (cliTok && fullResponse) {
3420
3414
  pendingOutputTokens += estimateTokens(normalizeResponse(fullResponse));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taj-special/dravix-code",
3
- "version": "1.4.7",
3
+ "version": "1.4.9",
4
4
  "description": "AI-powered coding assistant CLI — Dravix Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,10 +10,10 @@
10
10
  "dist"
11
11
  ],
12
12
  "scripts": {
13
- "build": "tsc",
13
+ "build": "node node_modules/typescript/lib/tsc.js",
14
14
  "start": "node dist/cli/index.js",
15
- "dev": "tsc --watch",
16
- "prepublishOnly": "tsc"
15
+ "dev": "node node_modules/typescript/lib/tsc.js --watch",
16
+ "prepublishOnly": "node node_modules/typescript/lib/tsc.js"
17
17
  },
18
18
  "dependencies": {
19
19
  "chalk": "^5.3.0"