@taj-special/dravix-code 1.1.23 → 1.1.25

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
@@ -265,6 +265,7 @@ async function main() {
265
265
  console.log(colors.muted(' Project ') + W(path.basename(cwd)));
266
266
  console.log(colors.muted(' Path ') + DIM(cwd));
267
267
  console.log(DIM(' ' + '─'.repeat(45)));
268
+ console.log(colors.muted(' ') + C('Tab') + colors.muted(' path complete ') + C('@') + colors.muted(' file picker ') + C('Ctrl+C') + colors.muted(' cancel ') + C('/help') + colors.muted(' commands'));
268
269
  process.stdout.write('\n');
269
270
  await startRepl(cwd);
270
271
  }
package/dist/cli/repl.js CHANGED
@@ -10,7 +10,6 @@ import { printOpResult, printError, printInfo, colors } from '../utils/display.j
10
10
  import { saveConversation, loadConversation, listConversations, generateTitle, generateId } from '../services/conversations.js';
11
11
  import { getToken } from '../services/auth.js';
12
12
  import { checkUsage, reportUsage, estimateTokens, usageBar, fmtNum, formatResetTime } from '../services/usage.js';
13
- import { ChatLayout } from '../utils/layout.js';
14
13
  let _serverWebDesignerSkill = '';
15
14
  const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
16
15
  const SLASH_COMMANDS = [
@@ -835,14 +834,11 @@ async function askPermission(label, key, alwaysAllowed, noAlways, diffShown, pre
835
834
  process.stdin.on('data', onData);
836
835
  });
837
836
  }
838
- const HINT_TEXT = (C) => colors.muted(' ') + C('Tab') + colors.muted(' path complete ') +
839
- C('@') + colors.muted(' file picker ') +
840
- C('Ctrl+C') + colors.muted(' cancel ') +
841
- C('/help') + colors.muted(' commands');
842
- async function readLine(prompt, cwd, layout) {
843
- const C = chalk.hex('#818cf8');
844
- layout.drawHint(HINT_TEXT(C));
845
- layout.posInput(prompt);
837
+ async function readLine(prompt, cwd) {
838
+ let SEP = colors.dim(' ' + '─'.repeat(Math.max((process.stdout.columns ?? 80) - 4, 40)));
839
+ let sepVisualLen = 2 + Math.max((process.stdout.columns ?? 80) - 4, 40);
840
+ process.stdout.write('\n' + SEP + '\n' + prompt);
841
+ process.stdout.write('\r\n' + SEP + '\x1b[1A\r' + prompt);
846
842
  return new Promise((resolve) => {
847
843
  let prefix = '';
848
844
  let pasteBlock = null;
@@ -870,25 +866,41 @@ async function readLine(prompt, cwd, layout) {
870
866
  let tabDrawn = false;
871
867
  let tabPreWord = ''; // prefix text before the tab-completed token
872
868
  function onResize() {
873
- layout.resize();
869
+ const newCols = process.stdout.columns ?? 80;
870
+ const oldSepRows = Math.ceil(sepVisualLen / newCols);
871
+ SEP = colors.dim(' ' + '─'.repeat(Math.max(newCols - 4, 40)));
872
+ sepVisualLen = 2 + Math.max(newCols - 4, 40);
874
873
  if (atMode) {
874
+ for (let i = 0; i < atBoxLines + 3; i++)
875
+ process.stdout.write('\x1b[1A');
876
+ process.stdout.write('\r\x1b[0J');
875
877
  atDrawn = false;
876
878
  atBoxLines = 0;
877
879
  drawPicker();
878
880
  }
879
881
  else if (slashMode) {
882
+ for (let i = 0; i < slashBoxLines + 3; i++)
883
+ process.stdout.write('\x1b[1A');
884
+ process.stdout.write('\r\x1b[0J');
880
885
  slashDrawn = false;
881
886
  slashBoxLines = 0;
882
887
  drawSlashPicker();
883
888
  }
884
889
  else if (tabMode) {
890
+ for (let i = 0; i < tabBoxLines + 3; i++)
891
+ process.stdout.write('\x1b[1A');
892
+ process.stdout.write('\r\x1b[0J');
885
893
  tabDrawn = false;
886
894
  tabBoxLines = 0;
887
895
  drawTabPicker();
888
896
  }
889
897
  else {
898
+ process.stdout.write('\r\x1b[K');
899
+ process.stdout.write('\x1b[1B\x1b[2K\x1b[1A');
900
+ for (let i = 0; i < oldSepRows + 1; i++)
901
+ process.stdout.write('\x1b[1A\x1b[2K');
902
+ process.stdout.write('\n' + SEP + '\n');
890
903
  redraw();
891
- layout.drawHint(HINT_TEXT(C));
892
904
  }
893
905
  }
894
906
  process.stdout.on('resize', onResize);
@@ -957,37 +969,35 @@ async function readLine(prompt, cwd, layout) {
957
969
  // then \x1b[2A\x1b[2K to skip bot-sep and clear input
958
970
  let atDrawn = false;
959
971
  function drawPicker() {
972
+ if (atDrawn) {
973
+ for (let i = 0; i < atBoxLines; i++)
974
+ process.stdout.write('\x1b[1A\x1b[2K');
975
+ process.stdout.write('\x1b[2A\x1b[2K');
976
+ }
977
+ else {
978
+ process.stdout.write('\r\x1b[K');
979
+ atDrawn = true;
980
+ }
981
+ atBoxLines = 0;
982
+ const inputLine = colors.primary(' › ') + chalk.white(prefix) +
983
+ (pasteBlock !== null ? colors.muted(`[paste: ${pasteCount} lines]`) + chalk.white(suffix) : '') +
984
+ chalk.hex('#818cf8')('@') + chalk.white(atQuery);
985
+ process.stdout.write(inputLine + '\r\n' + SEP + '\r\n');
986
+ const DW = Math.min(Math.max((process.stdout.columns ?? 80) - 6, 44), 72);
987
+ const IW = DW - 6;
960
988
  const filtered = filterFiles(atQuery);
961
989
  if (atSel >= filtered.length && filtered.length > 0)
962
990
  atSel = filtered.length - 1;
963
991
  const shown = filtered.slice(0, 10);
964
- const DW = Math.min(Math.max(layout.cols - 6, 44), 72);
965
- const IW = DW - 6;
966
- const itemCount = Math.max(shown.length, 1);
967
- atBoxLines = itemCount + 2; // header + items/no-match + footer
968
- const totalRows = 2 + atBoxLines; // input preview + blank + box
969
- const startRow = Math.max(layout.divRow - totalRows, 1);
970
- process.stdout.write('\x1b7'); // save cursor at inputRow
971
- // Input preview
972
- const inputLine = colors.primary(' › ') + chalk.white(prefix) +
973
- (pasteBlock !== null ? colors.muted(`[paste: ${pasteCount} lines]`) + chalk.white(suffix) : '') +
974
- chalk.hex('#818cf8')('@') + chalk.white(atQuery);
975
- process.stdout.write(`\x1b[${startRow};1H\x1b[2K${inputLine}`);
976
- process.stdout.write(`\x1b[${startRow + 1};1H\x1b[2K`); // blank separator
977
- let row = startRow + 2;
978
- // Header
979
992
  const qLabel = atQuery ? `@${atQuery}` : 'files';
980
993
  const hDashes = Math.max(DW - 5 - qLabel.length, 1);
981
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
982
- colors.dim(' ╭─ ') + colors.primary(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮'));
983
- row++;
984
- // Items
994
+ process.stdout.write(colors.dim(' ╭─ ') + colors.primary(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮') + '\n');
995
+ atBoxLines++;
985
996
  if (shown.length === 0) {
986
997
  const msg = 'no matches';
987
998
  const pad = ' '.repeat(Math.max(IW - msg.length, 0));
988
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
989
- colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│'));
990
- row++;
999
+ process.stdout.write(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│') + '\n');
1000
+ atBoxLines++;
991
1001
  }
992
1002
  else {
993
1003
  for (let i = 0; i < shown.length; i++) {
@@ -1001,66 +1011,60 @@ async function readLine(prompt, cwd, layout) {
1001
1011
  : sel ? colors.primary.bold(clip) + pad
1002
1012
  : isDir ? chalk.hex('#38bdf8')(clip) + pad
1003
1013
  : chalk.hex('#94a3b8')(clip + pad);
1004
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
1005
- colors.dim(' │') + ` ${mark} ` + name + colors.dim('│'));
1006
- row++;
1014
+ process.stdout.write(colors.dim(' │') + ` ${mark} ` + name + colors.dim('│') + '\n');
1015
+ atBoxLines++;
1007
1016
  }
1008
1017
  }
1009
- // Footer
1010
1018
  const hint = ' ↑↓ Enter Esc ';
1011
1019
  const fInner = DW - 2 - hint.length;
1012
1020
  const fLeft = Math.floor(fInner / 2);
1013
1021
  const fRight = fInner - fLeft;
1014
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
1015
- colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) +
1016
- colors.muted(hint) +
1017
- colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯'));
1018
- atDrawn = true;
1019
- process.stdout.write('\x1b8'); // restore cursor to inputRow
1022
+ process.stdout.write(colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) + colors.muted(hint) + colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯') + '\n');
1023
+ atBoxLines++;
1020
1024
  }
1021
1025
  function closePicker() {
1022
1026
  if (atDrawn) {
1023
- const totalRows = 2 + atBoxLines;
1024
- const startRow = Math.max(layout.divRow - totalRows, 1);
1025
- process.stdout.write('\x1b7');
1026
- for (let r = startRow; r < layout.divRow; r++) {
1027
- process.stdout.write(`\x1b[${r};1H\x1b[2K`);
1028
- }
1029
- process.stdout.write('\x1b8');
1027
+ for (let i = 0; i < atBoxLines; i++)
1028
+ process.stdout.write('\x1b[1A\x1b[2K');
1029
+ process.stdout.write('\x1b[2A\x1b[2K');
1030
1030
  atDrawn = false;
1031
1031
  }
1032
+ else {
1033
+ process.stdout.write('\r\x1b[K');
1034
+ }
1032
1035
  atBoxLines = 0;
1033
1036
  atMode = false;
1034
1037
  atQuery = '';
1035
1038
  atSel = 0;
1036
1039
  }
1037
1040
  function drawSlashPicker() {
1041
+ if (slashDrawn) {
1042
+ for (let i = 0; i < slashBoxLines; i++)
1043
+ process.stdout.write('\x1b[1A\x1b[2K');
1044
+ process.stdout.write('\x1b[2A\x1b[2K');
1045
+ }
1046
+ else {
1047
+ process.stdout.write('\r\x1b[K');
1048
+ slashDrawn = true;
1049
+ }
1050
+ slashBoxLines = 0;
1051
+ const inputLine = colors.primary(' › ') + colors.primary('/') + chalk.white(slashQuery);
1052
+ process.stdout.write(inputLine + '\r\n' + SEP + '\r\n');
1053
+ const DW = Math.min(Math.max((process.stdout.columns ?? 80) - 6, 44), 72);
1054
+ const nameW = 10;
1055
+ const descW = DW - nameW - 8;
1038
1056
  const filtered = filterSlash(slashQuery);
1039
1057
  if (slashSel >= filtered.length && filtered.length > 0)
1040
1058
  slashSel = filtered.length - 1;
1041
- const DW = Math.min(Math.max(layout.cols - 6, 44), 72);
1042
- const nameW = 10;
1043
- const descW = DW - nameW - 8;
1044
- const itemCount = Math.max(filtered.length, 1);
1045
- slashBoxLines = itemCount + 2;
1046
- const totalRows = 2 + slashBoxLines;
1047
- const startRow = Math.max(layout.divRow - totalRows, 1);
1048
- process.stdout.write('\x1b7');
1049
- const inputLine = colors.primary(' › ') + colors.primary('/') + chalk.white(slashQuery);
1050
- process.stdout.write(`\x1b[${startRow};1H\x1b[2K${inputLine}`);
1051
- process.stdout.write(`\x1b[${startRow + 1};1H\x1b[2K`);
1052
- let row = startRow + 2;
1053
1059
  const qLabel = slashQuery ? `/${slashQuery}` : 'commands';
1054
1060
  const hDashes = Math.max(DW - 5 - qLabel.length, 1);
1055
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
1056
- colors.dim(' ╭─ ') + colors.primary(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮'));
1057
- row++;
1061
+ process.stdout.write(colors.dim(' ╭─ ') + colors.primary(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮') + '\n');
1062
+ slashBoxLines++;
1058
1063
  if (filtered.length === 0) {
1059
1064
  const msg = 'no matching commands';
1060
1065
  const pad = ' '.repeat(Math.max(DW - 4 - msg.length, 0));
1061
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
1062
- colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│'));
1063
- row++;
1066
+ process.stdout.write(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│') + '\n');
1067
+ slashBoxLines++;
1064
1068
  }
1065
1069
  else {
1066
1070
  for (let i = 0; i < filtered.length; i++) {
@@ -1072,66 +1076,61 @@ async function readLine(prompt, cwd, layout) {
1072
1076
  const descPad = ' '.repeat(Math.max(descW - descStr.length, 0));
1073
1077
  const namePaint = sel ? colors.primary.bold(nameStr) : chalk.hex('#818cf8')(nameStr);
1074
1078
  const descPaint = sel ? colors.muted(descStr) : chalk.hex('#4b5563')(descStr);
1075
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
1076
- colors.dim(' │') + ` ${mark} ` + namePaint + ' ' + descPaint + descPad + colors.dim('│'));
1077
- row++;
1079
+ process.stdout.write(colors.dim(' │') + ` ${mark} ` + namePaint + ' ' + descPaint + descPad + colors.dim('│') + '\n');
1080
+ slashBoxLines++;
1078
1081
  }
1079
1082
  }
1080
1083
  const hint = ' ↑↓ Enter Esc ';
1081
1084
  const fInner = DW - 2 - hint.length;
1082
1085
  const fLeft = Math.floor(fInner / 2);
1083
1086
  const fRight = fInner - fLeft;
1084
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
1085
- colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) +
1086
- colors.muted(hint) +
1087
- colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯'));
1088
- slashDrawn = true;
1089
- process.stdout.write('\x1b8');
1087
+ process.stdout.write(colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) + colors.muted(hint) + colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯') + '\n');
1088
+ slashBoxLines++;
1090
1089
  }
1091
1090
  function closeSlashPicker() {
1092
1091
  if (slashDrawn) {
1093
- const totalRows = 2 + slashBoxLines;
1094
- const startRow = Math.max(layout.divRow - totalRows, 1);
1095
- process.stdout.write('\x1b7');
1096
- for (let r = startRow; r < layout.divRow; r++) {
1097
- process.stdout.write(`\x1b[${r};1H\x1b[2K`);
1098
- }
1099
- process.stdout.write('\x1b8');
1092
+ for (let i = 0; i < slashBoxLines; i++)
1093
+ process.stdout.write('\x1b[1A\x1b[2K');
1094
+ process.stdout.write('\x1b[2A\x1b[2K');
1100
1095
  slashDrawn = false;
1101
1096
  }
1097
+ else {
1098
+ process.stdout.write('\r\x1b[K');
1099
+ }
1102
1100
  slashBoxLines = 0;
1103
1101
  slashMode = false;
1104
1102
  slashQuery = '';
1105
1103
  slashSel = 0;
1106
1104
  }
1107
1105
  function drawTabPicker() {
1106
+ if (tabDrawn) {
1107
+ for (let i = 0; i < tabBoxLines; i++)
1108
+ process.stdout.write('\x1b[1A\x1b[2K');
1109
+ process.stdout.write('\x1b[2A\x1b[2K');
1110
+ }
1111
+ else {
1112
+ process.stdout.write('\r\x1b[K');
1113
+ tabDrawn = true;
1114
+ }
1115
+ tabBoxLines = 0;
1116
+ const TC = chalk.hex('#34d399');
1117
+ const inputLine = colors.primary(' › ') + chalk.white(tabPreWord) + TC(tabQuery);
1118
+ process.stdout.write(inputLine + '\r\n' + SEP + '\r\n');
1119
+ const DW = Math.min(Math.max((process.stdout.columns ?? 80) - 6, 44), 72);
1120
+ const IW = DW - 6;
1108
1121
  const filtered = filterFiles(tabQuery);
1109
1122
  if (tabSel >= filtered.length && filtered.length > 0)
1110
1123
  tabSel = filtered.length - 1;
1111
1124
  const shown = filtered.slice(0, 10);
1112
- const DW = Math.min(Math.max(layout.cols - 6, 44), 72);
1113
- const IW = DW - 6;
1114
- const TC = chalk.hex('#34d399');
1115
- const itemCount = Math.max(shown.length, 1);
1116
- tabBoxLines = itemCount + 2;
1117
- const totalRows = 2 + tabBoxLines;
1118
- const startRow = Math.max(layout.divRow - totalRows, 1);
1119
- process.stdout.write('\x1b7');
1120
- const inputLine = colors.primary(' › ') + chalk.white(tabPreWord) + TC(tabQuery);
1121
- process.stdout.write(`\x1b[${startRow};1H\x1b[2K${inputLine}`);
1122
- process.stdout.write(`\x1b[${startRow + 1};1H\x1b[2K`);
1123
- let row = startRow + 2;
1124
1125
  const qLabel = tabQuery || 'tab complete';
1125
1126
  const hDashes = Math.max(DW - 5 - qLabel.length, 1);
1126
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
1127
- colors.dim(' ╭─ ') + TC(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮'));
1128
- row++;
1127
+ process.stdout.write(colors.dim(' ╭─ ') + TC(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮') + '\n');
1128
+ tabBoxLines++;
1129
1129
  if (shown.length === 0) {
1130
1130
  const msg = 'no matches';
1131
1131
  const pad = ' '.repeat(Math.max(IW - msg.length, 0));
1132
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
1133
- colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│'));
1134
- row++;
1132
+ process.stdout.write(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│') + '\n');
1133
+ tabBoxLines++;
1135
1134
  }
1136
1135
  else {
1137
1136
  for (let i = 0; i < shown.length; i++) {
@@ -1144,33 +1143,27 @@ async function readLine(prompt, cwd, layout) {
1144
1143
  const name = sel ? TC.bold(clip) + pad
1145
1144
  : isDir ? chalk.hex('#38bdf8')(clip) + pad
1146
1145
  : chalk.hex('#94a3b8')(clip + pad);
1147
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
1148
- colors.dim(' │') + ` ${mark} ` + name + colors.dim('│'));
1149
- row++;
1146
+ process.stdout.write(colors.dim(' │') + ` ${mark} ` + name + colors.dim('│') + '\n');
1147
+ tabBoxLines++;
1150
1148
  }
1151
1149
  }
1152
1150
  const hint = ' Tab/↑↓ Enter Esc ';
1153
1151
  const fInner = DW - 2 - hint.length;
1154
1152
  const fLeft = Math.floor(fInner / 2);
1155
1153
  const fRight = fInner - fLeft;
1156
- process.stdout.write(`\x1b[${row};1H\x1b[2K` +
1157
- colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) +
1158
- colors.muted(hint) +
1159
- colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯'));
1160
- tabDrawn = true;
1161
- process.stdout.write('\x1b8');
1154
+ process.stdout.write(colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) + colors.muted(hint) + colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯') + '\n');
1155
+ tabBoxLines++;
1162
1156
  }
1163
1157
  function closeTabPicker() {
1164
1158
  if (tabDrawn) {
1165
- const totalRows = 2 + tabBoxLines;
1166
- const startRow = Math.max(layout.divRow - totalRows, 1);
1167
- process.stdout.write('\x1b7');
1168
- for (let r = startRow; r < layout.divRow; r++) {
1169
- process.stdout.write(`\x1b[${r};1H\x1b[2K`);
1170
- }
1171
- process.stdout.write('\x1b8');
1159
+ for (let i = 0; i < tabBoxLines; i++)
1160
+ process.stdout.write('\x1b[1A\x1b[2K');
1161
+ process.stdout.write('\x1b[2A\x1b[2K');
1172
1162
  tabDrawn = false;
1173
1163
  }
1164
+ else {
1165
+ process.stdout.write('\r\x1b[K');
1166
+ }
1174
1167
  tabBoxLines = 0;
1175
1168
  tabMode = false;
1176
1169
  tabQuery = '';
@@ -1178,7 +1171,7 @@ async function readLine(prompt, cwd, layout) {
1178
1171
  tabPreWord = '';
1179
1172
  }
1180
1173
  function redraw() {
1181
- const cols = layout.cols;
1174
+ const cols = process.stdout.columns ?? 80;
1182
1175
  const maxLen = Math.max(cols - 6, 10);
1183
1176
  let displayPre = prefix;
1184
1177
  if (displayPre.length > maxLen)
@@ -1191,7 +1184,8 @@ async function readLine(prompt, cwd, layout) {
1191
1184
  else {
1192
1185
  inp = colors.primary(' › ') + chalk.white(displayPre);
1193
1186
  }
1194
- process.stdout.write(`\x1b[${layout.inputRow};1H\x1b[2K${inp}`);
1187
+ process.stdout.write('\r\x1b[K' + inp);
1188
+ process.stdout.write('\r\n' + SEP + '\x1b[1A\r' + inp);
1195
1189
  }
1196
1190
  function submit() {
1197
1191
  if (atMode)
@@ -1210,15 +1204,14 @@ async function readLine(prompt, cwd, layout) {
1210
1204
  const text = parts.join('\n');
1211
1205
  const lns = parts.length > 0 ? parts : (text ? [text] : []);
1212
1206
  cleanup();
1213
- // Clear the fixed input area
1214
- layout.clearInput();
1207
+ process.stdout.write('\r\x1b[K');
1208
+ process.stdout.write('\x1b[1B\x1b[2K');
1209
+ process.stdout.write('\x1b[2A\x1b[2K');
1215
1210
  if (!text) {
1216
1211
  resolve(null);
1217
1212
  return;
1218
1213
  }
1219
- // Move into the chat scroll region and print the user message
1220
- layout.enterChat();
1221
- const cols = layout.cols;
1214
+ const cols = process.stdout.columns ?? 80;
1222
1215
  const msgBg = chalk.bgHex('#334155').hex('#f1f5f9');
1223
1216
  const fillMsg = (raw) => (' > ' + raw + ' ').padEnd(cols);
1224
1217
  if (pasteBlock !== null) {
@@ -1227,10 +1220,10 @@ async function readLine(prompt, cwd, layout) {
1227
1220
  `[${pasteCount} lines]`,
1228
1221
  suffix.trim() ? suffix.trim() : '',
1229
1222
  ].filter(Boolean).join(' ');
1230
- process.stdout.write('\n' + msgBg(fillMsg(disp)) + '\n');
1223
+ process.stdout.write(msgBg(fillMsg(disp)) + '\n');
1231
1224
  }
1232
1225
  else {
1233
- process.stdout.write('\n' + msgBg(fillMsg(text)) + '\n');
1226
+ process.stdout.write(msgBg(fillMsg(text)) + '\n');
1234
1227
  }
1235
1228
  resolve({ text, lines: lns });
1236
1229
  }
@@ -1283,7 +1276,7 @@ async function readLine(prompt, cwd, layout) {
1283
1276
  resolve(null);
1284
1277
  return;
1285
1278
  }
1286
- // Ctrl+L — clear chat area, reinit layout
1279
+ // Ctrl+L — clear screen, redraw input
1287
1280
  if (data === '\x0c') {
1288
1281
  if (atMode)
1289
1282
  closePicker();
@@ -1291,10 +1284,9 @@ async function readLine(prompt, cwd, layout) {
1291
1284
  closeSlashPicker();
1292
1285
  if (tabMode)
1293
1286
  closeTabPicker();
1294
- layout.clearScrollRegion();
1295
- layout.redrawDivider();
1296
- layout.drawHint(HINT_TEXT(C));
1297
- layout.posInput(prompt);
1287
+ console.clear();
1288
+ process.stdout.write('\n' + SEP + '\n' + prompt);
1289
+ process.stdout.write('\r\n' + SEP + '\x1b[1A\r' + prompt);
1298
1290
  return;
1299
1291
  }
1300
1292
  // ── @ picker mode ─────────────────────────────────────────
@@ -2125,9 +2117,28 @@ export async function startRepl(cwd) {
2125
2117
  }
2126
2118
  // activeCwd can change when /resume loads a conversation from a different directory
2127
2119
  let activeCwd = cwd;
2120
+ // Rules appended to every system prompt to enforce safe file-operation behavior
2121
+ const SAFE_FILE_RULES = `
2122
+
2123
+ ## CRITICAL RULES — Follow these on every response
2124
+
2125
+ ### File operations
2126
+ - **write_file** = CREATE only. Use it ONLY when a file does NOT exist yet.
2127
+ - **edit_file** = MODIFY existing files. For ANY change to an existing file, always use edit_file with <find>/<replace>.
2128
+ - Before editing any file: use <read_file> to see its current content first.
2129
+ - NEVER overwrite an existing file with write_file — it destroys user code.
2130
+
2131
+ ### Scope
2132
+ - Only touch files the user explicitly mentioned or that are clearly required by the task.
2133
+ - If the user says "create X" and X already exists: STOP and ask — "I see X already exists at [path]. Do you want me to (a) modify it, or (b) create a new file?"
2134
+ - Do NOT restructure, rename, or "improve" anything that wasn't asked.
2135
+
2136
+ ### When unsure
2137
+ - Ask ONE short clarifying question before writing any code.
2138
+ - Better to ask than to guess wrong and destroy existing work.`;
2128
2139
  const buildSystemMsg = (dir) => ({
2129
2140
  role: 'system',
2130
- content: SYSTEM_PROMPT + '\n\nProject context:\n' + buildContext(dir),
2141
+ content: SYSTEM_PROMPT + SAFE_FILE_RULES + '\n\nProject context:\n' + buildContext(dir),
2131
2142
  });
2132
2143
  const history = [buildSystemMsg(activeCwd)];
2133
2144
  let sessionId = generateId();
@@ -2176,10 +2187,6 @@ export async function startRepl(cwd) {
2176
2187
  process.stdin.setRawMode(true);
2177
2188
  process.stdin.resume();
2178
2189
  process.stdin.setEncoding('utf8');
2179
- // Initialize sticky-bottom layout (scroll region + fixed input bar)
2180
- const layout = new ChatLayout();
2181
- layout.init();
2182
- process.on('exit', () => layout.cleanup());
2183
2190
  // Sequential loop — queued input lets user type during AI stream
2184
2191
  while (true) {
2185
2192
  try {
@@ -2208,8 +2215,8 @@ export async function startRepl(cwd) {
2208
2215
  userReminder = `The user's request: "${display}". `;
2209
2216
  }
2210
2217
  const taskInstruction = userReminder
2211
- ? `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 the file content above to find the exact text and apply the change.`
2212
- : `Execute the user's request using the file content above.`;
2218
+ ? `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.`
2219
+ : `Execute the user's request using the file content above. Use <edit_file> for existing files, <write_file> only for new files.`;
2213
2220
  // Keep using FLASH_MODEL after file reads
2214
2221
  if (readFileTurnCount > 5 && !forcedEditMode) {
2215
2222
  forcedEditMode = true;
@@ -2239,14 +2246,13 @@ export async function startRepl(cwd) {
2239
2246
  const q = queuedResult;
2240
2247
  queuedResult = null;
2241
2248
  result = q;
2242
- // Show queued message inside scroll region
2243
- layout.enterChat();
2249
+ // Show queued message as if just submitted
2244
2250
  const cols = process.stdout.columns ?? 80;
2245
2251
  const msgBgQ = chalk.bgHex('#334155').hex('#f1f5f9');
2246
- process.stdout.write('\n' + msgBgQ((' > ' + q.text + ' ').padEnd(cols)) + '\n');
2252
+ process.stdout.write(msgBgQ((' > ' + q.text + ' ').padEnd(cols)) + '\n');
2247
2253
  }
2248
2254
  else {
2249
- result = await readLine(PROMPT, activeCwd, layout);
2255
+ result = await readLine(PROMPT, activeCwd);
2250
2256
  }
2251
2257
  if (!result)
2252
2258
  continue;
@@ -2255,12 +2261,7 @@ export async function startRepl(cwd) {
2255
2261
  lastUserLine = line;
2256
2262
  // ── Slash commands ──────────────────────────────────────────
2257
2263
  if (!skipInput && line.trim() === '/resume') {
2258
- // Temporarily reset scroll region so picker can use full terminal with relative cursor movement
2259
- process.stdout.write('\x1b[r');
2260
2264
  const convId = await showConversationPicker();
2261
- // Restore scroll region + fixed rows
2262
- layout.resize();
2263
- layout.enterChat();
2264
2265
  if (convId) {
2265
2266
  const conv = loadConversation(convId);
2266
2267
  if (conv) {
@@ -2300,8 +2301,6 @@ export async function startRepl(cwd) {
2300
2301
  if (lastUserLine)
2301
2302
  queuedResult = { text: lastUserLine, lines: [lastUserLine] };
2302
2303
  });
2303
- // Re-establish scroll region after /clear (console.clear) or other commands
2304
- layout.resize();
2305
2304
  continue;
2306
2305
  }
2307
2306
  if (!skipInput) {
@@ -2455,6 +2454,16 @@ export async function startRepl(cwd) {
2455
2454
  executedInlineOps.add(inlineOpFingerprint(op));
2456
2455
  }
2457
2456
  else {
2457
+ // Track existing file size to detect write_file overwrites of existing code
2458
+ let existingLineCount = 0;
2459
+ if (op.type === 'write' && op.path) {
2460
+ try {
2461
+ const fp = path.resolve(activeCwd, op.path);
2462
+ if (fs.existsSync(fp))
2463
+ existingLineCount = fs.readFileSync(fp, 'utf-8').split('\n').length;
2464
+ }
2465
+ catch { /* ignore */ }
2466
+ }
2458
2467
  const previewLines = (op.type === 'write' || op.type === 'edit') ? printDiffPreview(op, activeCwd) : 0;
2459
2468
  const allowed = await askPermission(label, key, alwaysAllowed, op.type === 'run', previewLines > 0, previewLines);
2460
2469
  executedInlineOps.add(inlineOpFingerprint(op));
@@ -2530,11 +2539,17 @@ export async function startRepl(cwd) {
2530
2539
  else if ((opResult.type === 'modified' || opResult.type === 'created') && op.path &&
2531
2540
  (op.type === 'edit' || op.type === 'write')) {
2532
2541
  // After a successful edit, inject a note so AI knows the file changed
2533
- // This prevents subsequent <edit_file> ops from using stale <find> text
2534
2542
  history.push({
2535
2543
  role: 'system',
2536
- content: `[File updated] ${op.path} was just modified by a previous operation in this response. If you have more edits for this file, use <read_file path="${op.path}"/> first to get the current content before using <edit_file>, or use <write_file> with the complete new content.`,
2544
+ content: `[File updated] ${op.path} was just modified. For further edits to this file, use <read_file path="${op.path}"/> first to get the current content, then <edit_file> with targeted changes.`,
2537
2545
  });
2546
+ // Educate AI when it used write_file to overwrite a non-trivial existing file
2547
+ if (op.type === 'write' && opResult.type === 'modified' && existingLineCount > 20) {
2548
+ history.push({
2549
+ role: 'system',
2550
+ content: `[Rule] You used <write_file> to completely replace ${op.path} (${existingLineCount} lines of existing code). This is wrong. RULE: write_file is ONLY for creating new files. For existing files, always use <edit_file> with <find>/<replace> to make targeted changes. Never overwrite existing code with write_file.`,
2551
+ });
2552
+ }
2538
2553
  }
2539
2554
  }
2540
2555
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taj-special/dravix-code",
3
- "version": "1.1.23",
3
+ "version": "1.1.25",
4
4
  "description": "AI-powered coding assistant CLI — Dravix Code",
5
5
  "type": "module",
6
6
  "bin": {