@taj-special/dravix-code 1.4.6 → 1.4.8
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 +2 -2
- package/dist/cli/repl.js +326 -348
- package/package.json +4 -4
package/dist/cli/index.js
CHANGED
|
@@ -278,9 +278,9 @@ async function main() {
|
|
|
278
278
|
}
|
|
279
279
|
const rows = process.stdout.rows ?? 24;
|
|
280
280
|
const cols = process.stdout.columns ?? 80;
|
|
281
|
-
// FULL-SCREEN: clear everything including scrollback, set scroll region
|
|
281
|
+
// FULL-SCREEN: clear everything including scrollback, set scroll region for chat (bottom 3 rows fixed)
|
|
282
282
|
process.stdout.write('\x1b[2J\x1b[3J'); // Clear screen + scrollback buffer
|
|
283
|
-
process.stdout.write(`\x1b[1;${rows}r`); // Scroll region =
|
|
283
|
+
process.stdout.write(`\x1b[1;${rows - 3}r`); // Scroll region = chat area (rows 1 to rows-3)
|
|
284
284
|
process.stdout.write('\x1b[1;1H'); // Move to top-left
|
|
285
285
|
// Draw the app (ONE logo only)
|
|
286
286
|
banner();
|
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)
|
|
@@ -906,39 +894,17 @@ async function readLine(prompt, cwd) {
|
|
|
906
894
|
let tabPreWord = '';
|
|
907
895
|
function onResize() {
|
|
908
896
|
const newCols = process.stdout.columns ?? 80;
|
|
909
|
-
const oldSepRows = Math.ceil(sepVisualLen / newCols);
|
|
910
897
|
SEP = colors.dim(' ' + '─'.repeat(Math.max(newCols - 4, 40)));
|
|
911
|
-
sepVisualLen = 2 + Math.max(newCols - 4, 40);
|
|
912
898
|
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
899
|
drawPicker();
|
|
919
900
|
}
|
|
920
901
|
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
902
|
drawSlashPicker();
|
|
927
903
|
}
|
|
928
904
|
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
905
|
drawTabPicker();
|
|
935
906
|
}
|
|
936
907
|
else {
|
|
937
|
-
process.stdout.write('\r\x1b[K');
|
|
938
|
-
process.stdout.write('\x1b[1B\x1b[2K\x1b[1A');
|
|
939
|
-
for (let i = 0; i < oldSepRows + 1; i++)
|
|
940
|
-
process.stdout.write('\x1b[1A\x1b[2K');
|
|
941
|
-
process.stdout.write('\n' + SEP + '\n');
|
|
942
908
|
redraw();
|
|
943
909
|
}
|
|
944
910
|
}
|
|
@@ -1003,232 +969,284 @@ async function readLine(prompt, cwd) {
|
|
|
1003
969
|
const lq = q.toLowerCase();
|
|
1004
970
|
return SLASH_COMMANDS.filter(c => c.name.slice(1).toLowerCase().startsWith(lq));
|
|
1005
971
|
}
|
|
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
972
|
let atDrawn = false;
|
|
1010
973
|
function drawPicker() {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
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;
|
|
974
|
+
const cols = process.stdout.columns ?? 80;
|
|
975
|
+
const rows = process.stdout.rows ?? 24;
|
|
976
|
+
const sR = rows - 2;
|
|
977
|
+
const iR = rows - 1;
|
|
1027
978
|
const filtered = filterFiles(atQuery);
|
|
1028
979
|
if (atSel >= filtered.length && filtered.length > 0)
|
|
1029
980
|
atSel = filtered.length - 1;
|
|
1030
|
-
|
|
981
|
+
if (atSel < 0)
|
|
982
|
+
atSel = 0;
|
|
983
|
+
const maxVisible = Math.min(8, Math.max(3, rows - 6));
|
|
984
|
+
const shown = filtered.slice(0, maxVisible);
|
|
985
|
+
const DW = Math.min(Math.max(cols - 6, 44), 74);
|
|
986
|
+
const IW = DW - 6;
|
|
987
|
+
const boxLines = [];
|
|
1031
988
|
const qLabel = atQuery ? `@${atQuery}` : 'files';
|
|
1032
|
-
const hDashes = Math.max(DW -
|
|
1033
|
-
|
|
1034
|
-
atBoxLines++;
|
|
989
|
+
const hDashes = Math.max(DW - 4 - qLabel.length, 1);
|
|
990
|
+
boxLines.push(colors.dim(' ╭─ ') + colors.primary(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮'));
|
|
1035
991
|
if (shown.length === 0) {
|
|
1036
992
|
const msg = 'no matches';
|
|
1037
993
|
const pad = ' '.repeat(Math.max(IW - msg.length, 0));
|
|
1038
|
-
|
|
1039
|
-
atBoxLines++;
|
|
994
|
+
boxLines.push(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│'));
|
|
1040
995
|
}
|
|
1041
996
|
else {
|
|
1042
997
|
for (let i = 0; i < shown.length; i++) {
|
|
1043
998
|
const f = shown[i];
|
|
1044
999
|
const isDir = f.endsWith('/');
|
|
1045
1000
|
const sel = i === atSel;
|
|
1046
|
-
const mark = sel ? colors.primary('
|
|
1001
|
+
const mark = sel ? colors.primary('›') : ' ';
|
|
1047
1002
|
const clip = f.length > IW ? f.slice(0, IW - 1) + '…' : f;
|
|
1048
1003
|
const pad = ' '.repeat(Math.max(IW - clip.length, 0));
|
|
1049
1004
|
const name = sel && isDir ? colors.primary.bold(clip) + pad
|
|
1050
1005
|
: sel ? colors.primary.bold(clip) + pad
|
|
1051
1006
|
: isDir ? chalk.hex('#74b9ff')(clip) + pad
|
|
1052
1007
|
: colors.subtext(clip + pad);
|
|
1053
|
-
|
|
1054
|
-
atBoxLines++;
|
|
1008
|
+
boxLines.push(colors.dim(' │') + ` ${mark} ` + name + colors.dim('│'));
|
|
1055
1009
|
}
|
|
1056
1010
|
}
|
|
1057
|
-
const hint = ' ↑↓ Enter Esc ';
|
|
1011
|
+
const hint = ' ↑↓ Navigate Enter Select Esc Close ';
|
|
1058
1012
|
const fInner = DW - 2 - hint.length;
|
|
1059
|
-
const fLeft = Math.floor(fInner / 2);
|
|
1060
|
-
const fRight = fInner - fLeft;
|
|
1061
|
-
|
|
1062
|
-
|
|
1013
|
+
const fLeft = Math.max(0, Math.floor(fInner / 2));
|
|
1014
|
+
const fRight = Math.max(0, fInner - fLeft);
|
|
1015
|
+
boxLines.push(colors.dim(' ╰' + '─'.repeat(fLeft)) + colors.muted(hint) + colors.dim('─'.repeat(fRight) + '╯'));
|
|
1016
|
+
const newLinesCount = boxLines.length;
|
|
1017
|
+
// Clear any leftover lines from previous taller popup
|
|
1018
|
+
if (atBoxLines > newLinesCount) {
|
|
1019
|
+
for (let r = sR - atBoxLines; r < sR - newLinesCount; r++) {
|
|
1020
|
+
if (r >= 1)
|
|
1021
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
// Render popup above separator
|
|
1025
|
+
for (let i = 0; i < newLinesCount; i++) {
|
|
1026
|
+
const targetRow = sR - newLinesCount + i;
|
|
1027
|
+
if (targetRow >= 1) {
|
|
1028
|
+
process.stdout.write(`\x1b[${targetRow};1H\x1b[2K${boxLines[i]}`);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
// Re-draw separator and input
|
|
1032
|
+
process.stdout.write(`\x1b[${sR};1H\x1b[2K${SEP}`);
|
|
1033
|
+
const inputLine = colors.primary(' › ') + colors.text(prefix) +
|
|
1034
|
+
(pasteBlock !== null ? colors.muted(`[paste: ${pasteCount} lines]`) + colors.text(suffix) : '') +
|
|
1035
|
+
colors.primary('@') + colors.text(atQuery);
|
|
1036
|
+
process.stdout.write(`\x1b[${iR};1H\x1b[2K${inputLine}`);
|
|
1037
|
+
const cursorCol = 5 + prefix.length + (pasteBlock !== null ? `[paste: ${pasteCount} lines]`.length + suffix.length : 0) + 1 + atQuery.length;
|
|
1038
|
+
process.stdout.write(`\x1b[${iR};${cursorCol}H`);
|
|
1039
|
+
atBoxLines = newLinesCount;
|
|
1040
|
+
atDrawn = true;
|
|
1063
1041
|
}
|
|
1064
1042
|
function closePicker() {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
process.stdout.write('\r\x1b[K');
|
|
1043
|
+
const rows = process.stdout.rows ?? 24;
|
|
1044
|
+
const sR = rows - 2;
|
|
1045
|
+
if (atDrawn && atBoxLines > 0) {
|
|
1046
|
+
for (let r = sR - atBoxLines; r < sR; r++) {
|
|
1047
|
+
if (r >= 1)
|
|
1048
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
1049
|
+
}
|
|
1073
1050
|
}
|
|
1074
1051
|
atBoxLines = 0;
|
|
1075
1052
|
atMode = false;
|
|
1076
1053
|
atQuery = '';
|
|
1077
1054
|
atSel = 0;
|
|
1055
|
+
atDrawn = false;
|
|
1078
1056
|
}
|
|
1079
1057
|
function drawSlashPicker() {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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;
|
|
1058
|
+
const cols = process.stdout.columns ?? 80;
|
|
1059
|
+
const rows = process.stdout.rows ?? 24;
|
|
1060
|
+
const sR = rows - 2;
|
|
1061
|
+
const iR = rows - 1;
|
|
1095
1062
|
const filtered = filterSlash(slashQuery);
|
|
1096
1063
|
if (slashSel >= filtered.length && filtered.length > 0)
|
|
1097
1064
|
slashSel = filtered.length - 1;
|
|
1065
|
+
if (slashSel < 0)
|
|
1066
|
+
slashSel = 0;
|
|
1067
|
+
const maxVisible = Math.min(8, Math.max(3, rows - 6));
|
|
1068
|
+
const shown = filtered.slice(0, maxVisible);
|
|
1069
|
+
const DW = Math.min(Math.max(cols - 6, 44), 74);
|
|
1070
|
+
const nameW = 11;
|
|
1071
|
+
const descW = Math.max(DW - nameW - 9, 10);
|
|
1072
|
+
const boxLines = [];
|
|
1098
1073
|
const qLabel = slashQuery ? `/${slashQuery}` : 'commands';
|
|
1099
|
-
const hDashes = Math.max(DW -
|
|
1100
|
-
|
|
1101
|
-
slashBoxLines++;
|
|
1074
|
+
const hDashes = Math.max(DW - 4 - qLabel.length, 1);
|
|
1075
|
+
boxLines.push(colors.dim(' ╭─ ') + colors.primary(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮'));
|
|
1102
1076
|
if (filtered.length === 0) {
|
|
1103
1077
|
const msg = 'no matching commands';
|
|
1104
|
-
const pad = ' '.repeat(Math.max(DW - 4 - msg.length, 0));
|
|
1105
|
-
|
|
1106
|
-
slashBoxLines++;
|
|
1078
|
+
const pad = ' '.repeat(Math.max(DW - 4 - msg.length - 2, 0));
|
|
1079
|
+
boxLines.push(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│'));
|
|
1107
1080
|
}
|
|
1108
1081
|
else {
|
|
1109
|
-
for (let i = 0; i <
|
|
1110
|
-
const cmd =
|
|
1082
|
+
for (let i = 0; i < shown.length; i++) {
|
|
1083
|
+
const cmd = shown[i];
|
|
1111
1084
|
const sel = i === slashSel;
|
|
1112
|
-
const mark = sel ? colors.primary('
|
|
1085
|
+
const mark = sel ? colors.primary('›') : ' ';
|
|
1113
1086
|
const nameStr = cmd.name.padEnd(nameW);
|
|
1114
1087
|
const descStr = cmd.desc.length > descW ? cmd.desc.slice(0, descW - 1) + '…' : cmd.desc;
|
|
1115
1088
|
const descPad = ' '.repeat(Math.max(descW - descStr.length, 0));
|
|
1116
1089
|
const namePaint = sel ? colors.primary.bold(nameStr) : colors.primary(nameStr);
|
|
1117
|
-
const descPaint = sel ?
|
|
1118
|
-
|
|
1119
|
-
slashBoxLines++;
|
|
1090
|
+
const descPaint = sel ? chalk.hex('#f1f5f9')(descStr) : colors.subtext(descStr);
|
|
1091
|
+
boxLines.push(colors.dim(' │') + ` ${mark} ` + namePaint + ' ' + descPaint + descPad + colors.dim('│'));
|
|
1120
1092
|
}
|
|
1121
1093
|
}
|
|
1122
|
-
const hint = ' ↑↓
|
|
1094
|
+
const hint = ' ↑↓ Navigate Tab Complete ↵ Run Esc Close ';
|
|
1123
1095
|
const fInner = DW - 2 - hint.length;
|
|
1124
|
-
const fLeft = Math.floor(fInner / 2);
|
|
1125
|
-
const fRight = fInner - fLeft;
|
|
1126
|
-
|
|
1127
|
-
|
|
1096
|
+
const fLeft = Math.max(0, Math.floor(fInner / 2));
|
|
1097
|
+
const fRight = Math.max(0, fInner - fLeft);
|
|
1098
|
+
boxLines.push(colors.dim(' ╰' + '─'.repeat(fLeft)) + colors.muted(hint) + colors.dim('─'.repeat(fRight) + '╯'));
|
|
1099
|
+
const newLinesCount = boxLines.length;
|
|
1100
|
+
// Clear any leftover lines from previous taller popup
|
|
1101
|
+
if (slashBoxLines > newLinesCount) {
|
|
1102
|
+
for (let r = sR - slashBoxLines; r < sR - newLinesCount; r++) {
|
|
1103
|
+
if (r >= 1)
|
|
1104
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
// Render popup above separator
|
|
1108
|
+
for (let i = 0; i < newLinesCount; i++) {
|
|
1109
|
+
const targetRow = sR - newLinesCount + i;
|
|
1110
|
+
if (targetRow >= 1) {
|
|
1111
|
+
process.stdout.write(`\x1b[${targetRow};1H\x1b[2K${boxLines[i]}`);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
// Re-draw separator and input line
|
|
1115
|
+
process.stdout.write(`\x1b[${sR};1H\x1b[2K${SEP}`);
|
|
1116
|
+
const inputLine = colors.primary(' › ') + colors.primary('/') + colors.text(slashQuery);
|
|
1117
|
+
process.stdout.write(`\x1b[${iR};1H\x1b[2K${inputLine}`);
|
|
1118
|
+
process.stdout.write(`\x1b[${iR};${5 + 1 + slashQuery.length}H`);
|
|
1119
|
+
slashBoxLines = newLinesCount;
|
|
1120
|
+
slashDrawn = true;
|
|
1128
1121
|
}
|
|
1129
1122
|
function closeSlashPicker() {
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
process.stdout.write('\r\x1b[K');
|
|
1123
|
+
const rows = process.stdout.rows ?? 24;
|
|
1124
|
+
const sR = rows - 2;
|
|
1125
|
+
if (slashDrawn && slashBoxLines > 0) {
|
|
1126
|
+
for (let r = sR - slashBoxLines; r < sR; r++) {
|
|
1127
|
+
if (r >= 1)
|
|
1128
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
1129
|
+
}
|
|
1138
1130
|
}
|
|
1139
1131
|
slashBoxLines = 0;
|
|
1140
1132
|
slashMode = false;
|
|
1141
1133
|
slashQuery = '';
|
|
1142
1134
|
slashSel = 0;
|
|
1135
|
+
slashDrawn = false;
|
|
1143
1136
|
}
|
|
1144
1137
|
function drawTabPicker() {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
}
|
|
1150
|
-
else {
|
|
1151
|
-
process.stdout.write('\r\x1b[K');
|
|
1152
|
-
tabDrawn = true;
|
|
1153
|
-
}
|
|
1154
|
-
tabBoxLines = 0;
|
|
1138
|
+
const cols = process.stdout.columns ?? 80;
|
|
1139
|
+
const rows = process.stdout.rows ?? 24;
|
|
1140
|
+
const sR = rows - 2;
|
|
1141
|
+
const iR = rows - 1;
|
|
1155
1142
|
const TC = colors.success;
|
|
1156
|
-
const
|
|
1157
|
-
process.stdout.write(inputLine + '\r\n' + SEP + '\r\n');
|
|
1158
|
-
const DW = Math.min(Math.max((process.stdout.columns ?? 80) - 6, 44), 72);
|
|
1143
|
+
const DW = Math.min(Math.max(cols - 6, 44), 74);
|
|
1159
1144
|
const IW = DW - 6;
|
|
1160
1145
|
const filtered = filterFiles(tabQuery);
|
|
1161
1146
|
if (tabSel >= filtered.length && filtered.length > 0)
|
|
1162
1147
|
tabSel = filtered.length - 1;
|
|
1163
|
-
|
|
1148
|
+
if (tabSel < 0)
|
|
1149
|
+
tabSel = 0;
|
|
1150
|
+
const maxVisible = Math.min(8, Math.max(3, rows - 6));
|
|
1151
|
+
const shown = filtered.slice(0, maxVisible);
|
|
1152
|
+
const boxLines = [];
|
|
1164
1153
|
const qLabel = tabQuery || 'tab complete';
|
|
1165
|
-
const hDashes = Math.max(DW -
|
|
1166
|
-
|
|
1167
|
-
tabBoxLines++;
|
|
1154
|
+
const hDashes = Math.max(DW - 4 - qLabel.length, 1);
|
|
1155
|
+
boxLines.push(colors.dim(' ╭─ ') + TC(qLabel) + colors.dim(' ' + '─'.repeat(hDashes) + '╮'));
|
|
1168
1156
|
if (shown.length === 0) {
|
|
1169
1157
|
const msg = 'no matches';
|
|
1170
1158
|
const pad = ' '.repeat(Math.max(IW - msg.length, 0));
|
|
1171
|
-
|
|
1172
|
-
tabBoxLines++;
|
|
1159
|
+
boxLines.push(colors.dim(' │') + ' ' + colors.muted(msg) + pad + colors.dim('│'));
|
|
1173
1160
|
}
|
|
1174
1161
|
else {
|
|
1175
1162
|
for (let i = 0; i < shown.length; i++) {
|
|
1176
1163
|
const f = shown[i];
|
|
1177
1164
|
const isDir = f.endsWith('/');
|
|
1178
1165
|
const sel = i === tabSel;
|
|
1179
|
-
const mark = sel ? TC('
|
|
1166
|
+
const mark = sel ? TC('›') : ' ';
|
|
1180
1167
|
const clip = f.length > IW ? f.slice(0, IW - 1) + '…' : f;
|
|
1181
1168
|
const pad = ' '.repeat(Math.max(IW - clip.length, 0));
|
|
1182
1169
|
const name = sel ? TC.bold(clip) + pad
|
|
1183
1170
|
: isDir ? chalk.hex('#74b9ff')(clip) + pad
|
|
1184
1171
|
: colors.subtext(clip + pad);
|
|
1185
|
-
|
|
1186
|
-
tabBoxLines++;
|
|
1172
|
+
boxLines.push(colors.dim(' │') + ` ${mark} ` + name + colors.dim('│'));
|
|
1187
1173
|
}
|
|
1188
1174
|
}
|
|
1189
|
-
const hint = ' Tab/↑↓ Enter Esc ';
|
|
1175
|
+
const hint = ' Tab/↑↓ Navigate Enter Select Esc Close ';
|
|
1190
1176
|
const fInner = DW - 2 - hint.length;
|
|
1191
1177
|
const fLeft = Math.floor(fInner / 2);
|
|
1192
1178
|
const fRight = fInner - fLeft;
|
|
1193
|
-
|
|
1194
|
-
|
|
1179
|
+
boxLines.push(colors.dim(' ╰' + '─'.repeat(Math.max(fLeft, 0))) + colors.muted(hint) + colors.dim('─'.repeat(Math.max(fRight, 0)) + '╯'));
|
|
1180
|
+
const newLinesCount = boxLines.length;
|
|
1181
|
+
// Clear any leftover lines from previous taller popup
|
|
1182
|
+
if (tabBoxLines > newLinesCount) {
|
|
1183
|
+
for (let r = sR - tabBoxLines; r < sR - newLinesCount; r++) {
|
|
1184
|
+
if (r >= 1)
|
|
1185
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
// Render popup above separator
|
|
1189
|
+
for (let i = 0; i < newLinesCount; i++) {
|
|
1190
|
+
const targetRow = sR - newLinesCount + i;
|
|
1191
|
+
if (targetRow >= 1) {
|
|
1192
|
+
process.stdout.write(`\x1b[${targetRow};1H\x1b[2K${boxLines[i]}`);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
// Re-draw separator and input line
|
|
1196
|
+
process.stdout.write(`\x1b[${sR};1H\x1b[2K${SEP}`);
|
|
1197
|
+
const inputLine = colors.primary(' › ') + colors.text(tabPreWord) + TC(tabQuery);
|
|
1198
|
+
process.stdout.write(`\x1b[${iR};1H\x1b[2K${inputLine}`);
|
|
1199
|
+
const cursorCol = 5 + tabPreWord.length + tabQuery.length;
|
|
1200
|
+
process.stdout.write(`\x1b[${iR};${cursorCol}H`);
|
|
1201
|
+
tabBoxLines = newLinesCount;
|
|
1202
|
+
tabDrawn = true;
|
|
1195
1203
|
}
|
|
1196
1204
|
function closeTabPicker() {
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
process.stdout.write('\r\x1b[K');
|
|
1205
|
+
const rows = process.stdout.rows ?? 24;
|
|
1206
|
+
const sR = rows - 2;
|
|
1207
|
+
if (tabDrawn && tabBoxLines > 0) {
|
|
1208
|
+
for (let r = sR - tabBoxLines; r < sR; r++) {
|
|
1209
|
+
if (r >= 1)
|
|
1210
|
+
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
1211
|
+
}
|
|
1205
1212
|
}
|
|
1206
1213
|
tabBoxLines = 0;
|
|
1207
1214
|
tabMode = false;
|
|
1208
1215
|
tabQuery = '';
|
|
1209
1216
|
tabSel = 0;
|
|
1210
1217
|
tabPreWord = '';
|
|
1218
|
+
tabDrawn = false;
|
|
1211
1219
|
}
|
|
1212
1220
|
function redraw() {
|
|
1213
1221
|
const cols = process.stdout.columns ?? 80;
|
|
1214
|
-
const
|
|
1222
|
+
const rows = process.stdout.rows ?? 24;
|
|
1223
|
+
const sR = rows - 2;
|
|
1224
|
+
const iR = rows - 1;
|
|
1225
|
+
const maxLen = Math.max(cols - 8, 10);
|
|
1215
1226
|
let displayPre = prefix;
|
|
1216
1227
|
if (displayPre.length > maxLen)
|
|
1217
1228
|
displayPre = '…' + displayPre.slice(-(maxLen - 1));
|
|
1218
1229
|
let inp;
|
|
1230
|
+
let cursorCol = 5 + displayPre.length;
|
|
1219
1231
|
if (pasteBlock !== null) {
|
|
1232
|
+
const pTag = `[paste: ${pasteCount} lines]`;
|
|
1220
1233
|
inp = colors.primary(' › ') + colors.text(displayPre) +
|
|
1221
|
-
colors.muted(
|
|
1234
|
+
colors.muted(pTag) + colors.text(suffix);
|
|
1235
|
+
cursorCol = 5 + displayPre.length + pTag.length + suffix.length;
|
|
1222
1236
|
}
|
|
1223
1237
|
else {
|
|
1224
|
-
inp = colors.primary(' › ') + colors.text(displayPre);
|
|
1238
|
+
inp = colors.primary(' › ') + colors.text(displayPre) + colors.text(suffix);
|
|
1239
|
+
cursorCol = 5 + displayPre.length;
|
|
1225
1240
|
}
|
|
1226
|
-
process.stdout.write(
|
|
1227
|
-
process.stdout.write(
|
|
1241
|
+
process.stdout.write(`\x1b[${sR};1H\x1b[2K${SEP}`);
|
|
1242
|
+
process.stdout.write(`\x1b[${iR};1H\x1b[2K${inp}`);
|
|
1243
|
+
process.stdout.write(`\x1b[${iR};${cursorCol}H`);
|
|
1228
1244
|
}
|
|
1229
1245
|
function submit() {
|
|
1230
1246
|
if (atMode)
|
|
1231
1247
|
closePicker();
|
|
1248
|
+
if (slashMode)
|
|
1249
|
+
closeSlashPicker();
|
|
1232
1250
|
if (tabMode) {
|
|
1233
1251
|
prefix = tabPreWord + tabQuery;
|
|
1234
1252
|
closeTabPicker();
|
|
@@ -1243,9 +1261,12 @@ async function readLine(prompt, cwd) {
|
|
|
1243
1261
|
const text = parts.join('\n');
|
|
1244
1262
|
const lns = parts.length > 0 ? parts : (text ? [text] : []);
|
|
1245
1263
|
cleanup();
|
|
1246
|
-
process.stdout.
|
|
1247
|
-
|
|
1248
|
-
|
|
1264
|
+
const rows = process.stdout.rows ?? 24;
|
|
1265
|
+
const sR = rows - 2;
|
|
1266
|
+
const iR = rows - 1;
|
|
1267
|
+
process.stdout.write(`\x1b[${sR};1H\x1b[2K`);
|
|
1268
|
+
process.stdout.write(`\x1b[${iR};1H\x1b[2K`);
|
|
1269
|
+
process.stdout.write(`\x1b[${sR};1H`);
|
|
1249
1270
|
if (!text) {
|
|
1250
1271
|
resolve(null);
|
|
1251
1272
|
return;
|
|
@@ -1457,7 +1478,22 @@ async function readLine(prompt, cwd) {
|
|
|
1457
1478
|
}
|
|
1458
1479
|
return;
|
|
1459
1480
|
}
|
|
1460
|
-
if (data === '\r' || data === '\n'
|
|
1481
|
+
if (data === '\r' || data === '\n') {
|
|
1482
|
+
const filtered = filterSlash(slashQuery);
|
|
1483
|
+
if (filtered.length > 0) {
|
|
1484
|
+
const cmd = filtered[Math.min(slashSel, filtered.length - 1)];
|
|
1485
|
+
prefix = cmd.name;
|
|
1486
|
+
closeSlashPicker();
|
|
1487
|
+
submit();
|
|
1488
|
+
}
|
|
1489
|
+
else {
|
|
1490
|
+
prefix = '/' + slashQuery;
|
|
1491
|
+
closeSlashPicker();
|
|
1492
|
+
submit();
|
|
1493
|
+
}
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
if (data === '\t') {
|
|
1461
1497
|
const filtered = filterSlash(slashQuery);
|
|
1462
1498
|
if (filtered.length > 0) {
|
|
1463
1499
|
const cmd = filtered[Math.min(slashSel, filtered.length - 1)];
|
|
@@ -1707,8 +1743,8 @@ function timeAgoShort(iso) {
|
|
|
1707
1743
|
return `${d}d`;
|
|
1708
1744
|
return `${Math.floor(d / 30)}mo`;
|
|
1709
1745
|
}
|
|
1710
|
-
async function showConversationPicker(
|
|
1711
|
-
const allConvs = listConversations(
|
|
1746
|
+
async function showConversationPicker(cwd) {
|
|
1747
|
+
const allConvs = listConversations(cwd, 100);
|
|
1712
1748
|
if (allConvs.length === 0) {
|
|
1713
1749
|
printInfo('No saved conversations yet.');
|
|
1714
1750
|
return null;
|
|
@@ -2145,159 +2181,123 @@ async function showDesignModeLoader(skills) {
|
|
|
2145
2181
|
}
|
|
2146
2182
|
export async function startRepl(cwd) {
|
|
2147
2183
|
const alwaysAllowed = new Set();
|
|
2148
|
-
// ── Terminal title helper ─────────────────────────────────────
|
|
2149
|
-
function setTerminalTitle(title) {
|
|
2150
|
-
process.stdout.write(`\x1b]0;Dravix Code — ${title}\x07`);
|
|
2151
|
-
}
|
|
2152
|
-
setTerminalTitle('AI-powered coding assistant');
|
|
2153
2184
|
const token = getToken() ?? '';
|
|
2154
|
-
let SYSTEM_PROMPT =
|
|
2155
|
-
|
|
2156
|
-
You are a proactive, precise, and safe engineering partner. You think before you act, you verify your work, and you communicate clearly.
|
|
2157
|
-
|
|
2158
|
-
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.
|
|
2159
|
-
|
|
2160
|
-
# Core Principles
|
|
2161
|
-
|
|
2162
|
-
- Be correct, helpful, and safe above all else.
|
|
2163
|
-
- Think step by step before writing or modifying any code. State the plan in one short sentence before acting.
|
|
2164
|
-
- Prefer minimal, surgical changes that preserve the existing codebase structure and conventions.
|
|
2165
|
-
- Never guess at APIs, libraries, or configurations. Read the codebase first, or admit uncertainty.
|
|
2166
|
-
- Respect the user's time: be concise but never omit crucial details.
|
|
2167
|
-
- If a request is genuinely ambiguous and blocks progress, ask one short clarifying question. Otherwise make the reasonable call and continue.
|
|
2168
|
-
|
|
2169
|
-
# File Operations
|
|
2170
|
-
|
|
2171
|
-
- read_file = read a file (returns numbered lines like " 42 │ code")
|
|
2172
|
-
- write_file = CREATE new files only — NEVER overwrite existing files
|
|
2173
|
-
- edit_file = MODIFY existing files (use <find>/<replace> with EXACT text from read_file)
|
|
2174
|
-
- create_folder = create directories
|
|
2175
|
-
- delete_file = delete files
|
|
2176
|
-
- run_command = execute shell commands
|
|
2177
|
-
- read_folder = list directory contents
|
|
2178
|
-
- search_code = search file contents with regex or literal text
|
|
2179
|
-
- glob_files = find files by glob pattern like "**/*.ts"
|
|
2180
|
-
|
|
2181
|
-
# edit_file rules — CRITICAL
|
|
2182
|
-
|
|
2183
|
-
1. ALWAYS read_file first to see the current content
|
|
2184
|
-
2. Copy the EXACT text from the file into <find> — character by character
|
|
2185
|
-
3. The <find> text must match EXACTLY (including whitespace, indentation)
|
|
2186
|
-
4. Do NOT put line numbers in <find> (e.g., "42 │ code" is WRONG — use only "code")
|
|
2187
|
-
5. If edit fails, the system will show you the actual content — use it to retry
|
|
2188
|
-
6. Multiple edits on the same file: order from BOTTOM to TOP
|
|
2189
|
-
7. NEVER use write_file on an existing file — it destroys user code
|
|
2190
|
-
|
|
2191
|
-
# Communication Style
|
|
2192
|
-
|
|
2193
|
-
- Output is rendered in a terminal as Markdown. Be short and concise.
|
|
2194
|
-
- Reference code with \`file_path:line_number\` so the user can jump to it.
|
|
2195
|
-
- Do not start replies with conversational filler ("Got it", "Sure", "Great question"). State the action or result directly.
|
|
2196
|
-
- Do not narrate internal deliberation. Give brief updates at key moments.
|
|
2197
|
-
- End-of-turn summary: one or two sentences. What changed and what's next.
|
|
2198
|
-
- Respond in the same language the user writes in.
|
|
2199
|
-
|
|
2200
|
-
# Code Quality
|
|
2201
|
-
|
|
2202
|
-
- Keep things in one function unless composable or reusable.
|
|
2203
|
-
- Avoid try/catch where possible; avoid the any type.
|
|
2204
|
-
- Prefer functional array methods (flatMap/filter/map) over for loops.
|
|
2205
|
-
- Prefer const. Use ternaries or early returns instead of reassignment. Avoid else after return.
|
|
2206
|
-
- Do not destructure unnecessarily — obj.a over const { a } = obj.
|
|
2207
|
-
- Don't add features, refactors, or abstractions beyond what the task requires.
|
|
2208
|
-
- Don't add error handling for impossible scenarios.
|
|
2209
|
-
- Delete unused code completely rather than leaving _var or comments.
|
|
2210
|
-
- Default to writing no comments. Only comment when the WHY is non-obvious.
|
|
2211
|
-
|
|
2212
|
-
# Safety
|
|
2213
|
-
|
|
2214
|
-
- Never generate malicious, obfuscated, or deceptive code.
|
|
2215
|
-
- Do not output secrets, tokens, or private keys.
|
|
2216
|
-
- Guard against SQL injection, XSS, path traversal, insecure deserialization, command injection.
|
|
2217
|
-
- When you encounter an obstacle, do not use destructive actions as a shortcut.
|
|
2218
|
-
|
|
2219
|
-
# Working discipline
|
|
2220
|
-
|
|
2221
|
-
- If the user asks to create a file: use write_file, NOT code blocks.
|
|
2222
|
-
- Read files before editing them — always!
|
|
2223
|
-
- Plan all files first, create them in logical order, run commands last.
|
|
2224
|
-
- Only touch files the user explicitly mentioned or clearly required by the task.
|
|
2225
|
-
- When fixing a bug, fix the ROOT CAUSE, not just the symptom.
|
|
2226
|
-
- If a run_command fails, read the error message and fix the issue.
|
|
2227
|
-
- Complete the ENTIRE task in ONE response — do not ask for permission to continue.`;
|
|
2185
|
+
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.';
|
|
2228
2186
|
if (token) {
|
|
2229
2187
|
const { prompt, webDesignerSkill } = await fetchSystemPrompt(token);
|
|
2230
2188
|
if (prompt) {
|
|
2231
|
-
SYSTEM_PROMPT = prompt
|
|
2189
|
+
SYSTEM_PROMPT = prompt;
|
|
2232
2190
|
_serverWebDesignerSkill = webDesignerSkill;
|
|
2233
2191
|
}
|
|
2234
2192
|
}
|
|
2235
2193
|
// activeCwd can change when /resume loads a conversation from a different directory
|
|
2236
2194
|
let activeCwd = cwd;
|
|
2237
2195
|
// Rules appended to every system prompt to enforce safe file-operation behavior
|
|
2238
|
-
const SAFE_FILE_RULES = `
|
|
2239
|
-
|
|
2240
|
-
##
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
###
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
-
|
|
2277
|
-
-
|
|
2278
|
-
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2196
|
+
const SAFE_FILE_RULES = `
|
|
2197
|
+
|
|
2198
|
+
## CORE RULES
|
|
2199
|
+
|
|
2200
|
+
### READ FIRST, THEN ANSWER
|
|
2201
|
+
When the user asks about their code:
|
|
2202
|
+
1. Find the relevant file(s)
|
|
2203
|
+
2. Read them silently
|
|
2204
|
+
3. Give a direct, accurate answer based on what you found
|
|
2205
|
+
Never guess or fabricate information.
|
|
2206
|
+
|
|
2207
|
+
### BE ACCURATE
|
|
2208
|
+
- Only report what you actually see in the code
|
|
2209
|
+
- If you are unsure, say so — do not fabricate answers
|
|
2210
|
+
- Never add features or items that do not exist in the code
|
|
2211
|
+
|
|
2212
|
+
### RESPOND IN THE SAME LANGUAGE
|
|
2213
|
+
- Respond in the same language the user writes in
|
|
2214
|
+
- If user writes in English → respond in English
|
|
2215
|
+
- If user writes in Russian → respond in Russian
|
|
2216
|
+
- And so on for any language
|
|
2217
|
+
|
|
2218
|
+
### FORMAT ANSWERS CLEARLY
|
|
2219
|
+
- Use bullet points or numbered lists for multiple items
|
|
2220
|
+
- Put each item on its own line
|
|
2221
|
+
- Keep answers concise — 3-10 lines maximum
|
|
2222
|
+
- Do not dump entire file contents unless asked
|
|
2223
|
+
- Do not add unnecessary commentary
|
|
2224
|
+
|
|
2225
|
+
### WORK SILENTLY
|
|
2226
|
+
- Do not say "I will read the file" — just read it and answer
|
|
2227
|
+
- Do not narrate your thought process
|
|
2228
|
+
- Do not say "I already answered your question"
|
|
2229
|
+
- Give your answer ONCE and STOP
|
|
2230
|
+
|
|
2231
|
+
### WHEN CREATING/EDITING FILES
|
|
2232
|
+
- write_file = CREATE new files only
|
|
2233
|
+
- edit_file = MODIFY existing (read first, then find/replace)
|
|
2234
|
+
- Before editing: ALWAYS read_file first
|
|
2235
|
+
- Complete the ENTIRE task in ONE response
|
|
2236
|
+
- Use tags, NOT code blocks
|
|
2237
|
+
|
|
2238
|
+
### WHEN CREATING/EDITING FILES
|
|
2239
|
+
- write_file = CREATE new files only
|
|
2240
|
+
- edit_file = MODIFY existing (read first, then find/replace)
|
|
2241
|
+
- Before editing: ALWAYS read_file first
|
|
2242
|
+
- Multiple edits: order from BOTTOM to TOP
|
|
2243
|
+
- Complete the ENTIRE task in ONE response
|
|
2244
|
+
- Use tags, NOT code blocks
|
|
2245
|
+
|
|
2246
|
+
### When the user asks you to do something:
|
|
2247
|
+
1. Read the relevant files first
|
|
2248
|
+
2. Do the work using <write_file> / <edit_file> tags
|
|
2249
|
+
3. Summarize what you did in 1-2 sentences
|
|
2250
|
+
|
|
2251
|
+
### Act decisively
|
|
2252
|
+
- Use judgment and execute immediately when intent is clear.
|
|
2253
|
+
- Do NOT ask unnecessary clarifying questions — just do the task.
|
|
2254
|
+
- Make reasonable assumptions and proceed.
|
|
2255
|
+
|
|
2256
|
+
### Creating files
|
|
2257
|
+
- NEVER show code in code blocks when the user asks to create files or a project.
|
|
2258
|
+
- ALWAYS use <write_file path="filename"> tags to create actual files.
|
|
2259
|
+
- Code blocks are for short inline explanations only — they do NOT create files.
|
|
2260
|
+
- Output ALL required files in ONE response using <write_file> tags.
|
|
2261
|
+
|
|
2262
|
+
### File operations
|
|
2263
|
+
- **write_file** = CREATE only. Use it ONLY when a file does NOT exist yet.
|
|
2264
|
+
- **edit_file** = MODIFY existing files. For ANY change to an existing file, always use edit_file with <find>/<replace>.
|
|
2265
|
+
- Before editing any file: use <read_file> to see its current content first.
|
|
2266
|
+
- NEVER overwrite an existing file with write_file — it destroys user code.
|
|
2267
|
+
|
|
2268
|
+
### How to use edit_file correctly
|
|
2269
|
+
1. ALWAYS read_file first to see the current content
|
|
2270
|
+
2. Copy the EXACT text from the file into <find> — character by character
|
|
2271
|
+
3. The <find> text must match EXACTLY (including whitespace, indentation)
|
|
2272
|
+
4. If edit fails, the system will show you the actual content — use it to retry
|
|
2273
|
+
5. Do NOT put line numbers in <find> (e.g., "42 │ code" is WRONG)
|
|
2274
|
+
6. Do NOT modify the text in <find> — copy it EXACTLY from the file
|
|
2275
|
+
|
|
2276
|
+
### Searching and navigating
|
|
2277
|
+
- Use <glob_files pattern="**/*.ts" /> to find files by extension
|
|
2278
|
+
- Use <search_code pattern="functionName" /> to find code
|
|
2279
|
+
- Use <search_code pattern="regex.*pattern" regex="true" /> for regex search
|
|
2280
|
+
- Use <search_code pattern="error" include="*.ts" /> to search specific file types
|
|
2281
|
+
- Use <search_code pattern="TODO" context="2" /> to show context around matches
|
|
2282
|
+
- Read files before editing them — always!
|
|
2283
|
+
|
|
2284
|
+
### Multi-file tasks
|
|
2285
|
+
- Plan all files FIRST (list them in your response)
|
|
2286
|
+
- Create/edit files in LOGICAL ORDER (dependencies first)
|
|
2287
|
+
- Use write_file for ALL new files
|
|
2288
|
+
- Use edit_file for ALL modifications
|
|
2289
|
+
- Run commands LAST (after all files are created)
|
|
2290
|
+
|
|
2291
|
+
### Error recovery
|
|
2292
|
+
- If an edit_file fails, read the file again and retry with the actual content
|
|
2293
|
+
- If a run_command fails, read the error message and fix the issue
|
|
2294
|
+
- Never give up — keep trying until the task is complete
|
|
2295
|
+
|
|
2296
|
+
### Scope
|
|
2297
|
+
- Only touch files the user explicitly mentioned or that are clearly required by the task.
|
|
2298
|
+
- Do NOT restructure, rename, or "improve" anything that wasn't asked.
|
|
2299
|
+
- When creating a new project, create ALL necessary files (not just some).
|
|
2300
|
+
- When fixing a bug, fix the ROOT CAUSE, not just the symptom.`;
|
|
2301
2301
|
const buildSystemMsg = (dir) => ({
|
|
2302
2302
|
role: 'system',
|
|
2303
2303
|
content: SYSTEM_PROMPT + SAFE_FILE_RULES + '\n\nProject context:\n' + buildContext(dir),
|
|
@@ -2347,8 +2347,8 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
2347
2347
|
process.on('exit', resetTerminal);
|
|
2348
2348
|
process.on('SIGINT', () => { resetTerminal(); process.exit(0); });
|
|
2349
2349
|
process.on('SIGTERM', () => { resetTerminal(); process.exit(0); });
|
|
2350
|
-
process.on('unhandledRejection', (reason) => { logError('unhandledRejection', reason);
|
|
2351
|
-
process.on('uncaughtException', (err) => { logError('uncaughtException', err);
|
|
2350
|
+
process.on('unhandledRejection', (reason) => { logError('unhandledRejection', reason); resetTerminal(); });
|
|
2351
|
+
process.on('uncaughtException', (err) => { logError('uncaughtException', err); resetTerminal(); });
|
|
2352
2352
|
// Set raw mode ONCE — never toggle it during the session to avoid CMD getting stuck
|
|
2353
2353
|
process.stdin.setRawMode(true);
|
|
2354
2354
|
process.stdin.resume();
|
|
@@ -2369,8 +2369,20 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
2369
2369
|
if (readFileContinue) {
|
|
2370
2370
|
readFileContinue = false;
|
|
2371
2371
|
readFileTurnCount++;
|
|
2372
|
-
//
|
|
2373
|
-
|
|
2372
|
+
// Remind AI of the original user request — show first + last line so pasted code
|
|
2373
|
+
// doesn't eclipse the actual instruction written at the end.
|
|
2374
|
+
const rawUserMsg = lastUserLine.trim();
|
|
2375
|
+
const msgLines = rawUserMsg.split('\n').map(l => l.trim()).filter(Boolean);
|
|
2376
|
+
let userReminder = '';
|
|
2377
|
+
if (msgLines.length > 0) {
|
|
2378
|
+
const first = msgLines[0].slice(0, 100);
|
|
2379
|
+
const last = msgLines[msgLines.length - 1].slice(0, 150);
|
|
2380
|
+
const display = msgLines.length <= 2 ? msgLines.join(' / ').slice(0, 200) : `${first} [...] ${last}`;
|
|
2381
|
+
userReminder = `The user's request: "${display}". `;
|
|
2382
|
+
}
|
|
2383
|
+
const taskInstruction = userReminder
|
|
2384
|
+
? `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.`
|
|
2385
|
+
: `Execute the user's request using the file content above. Use <edit_file> for existing files, <write_file> only for new files.`;
|
|
2374
2386
|
// Keep using FLASH_MODEL after file reads
|
|
2375
2387
|
if (readFileTurnCount > 5 && !forcedEditMode) {
|
|
2376
2388
|
forcedEditMode = true;
|
|
@@ -2432,7 +2444,6 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
2432
2444
|
});
|
|
2433
2445
|
sessionId = convId;
|
|
2434
2446
|
conversationTitle = conv.title;
|
|
2435
|
-
setTerminalTitle(conv.title);
|
|
2436
2447
|
printConversationHistory(conv.messages);
|
|
2437
2448
|
}
|
|
2438
2449
|
}
|
|
@@ -2486,13 +2497,11 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
2486
2497
|
history.splice(1, history.length - 22);
|
|
2487
2498
|
}
|
|
2488
2499
|
// ── Auto-inject mentioned file contents ──────────────────────
|
|
2489
|
-
//
|
|
2500
|
+
// Files ≤ 3000 lines: full raw content injected — AI has exact text for <find>, no reads needed.
|
|
2490
2501
|
const FILE_PATTERN = /[\w\-.\/\\]+\.\w{2,5}/g;
|
|
2491
2502
|
const mentioned = [...new Set(line.match(FILE_PATTERN) ?? [])];
|
|
2492
2503
|
let fileContext = '';
|
|
2493
|
-
const FULL_INJECT_LINES =
|
|
2494
|
-
const MAX_INJECT_CHARS = 25000;
|
|
2495
|
-
let injectedChars = 0;
|
|
2504
|
+
const FULL_INJECT_LINES = 3000;
|
|
2496
2505
|
for (const fname of mentioned) {
|
|
2497
2506
|
const fullPath = path.resolve(activeCwd, fname);
|
|
2498
2507
|
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
|
|
@@ -2500,15 +2509,8 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
2500
2509
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
2501
2510
|
const fileLines = content.split('\n');
|
|
2502
2511
|
const lineCount = fileLines.length;
|
|
2503
|
-
if (lineCount <= FULL_INJECT_LINES
|
|
2504
|
-
|
|
2505
|
-
if (injectedChars + toAdd.length > MAX_INJECT_CHARS) {
|
|
2506
|
-
fileContext += `\n\n[File: ${fname} — ${lineCount} lines — skipped (prompt limit)]`;
|
|
2507
|
-
}
|
|
2508
|
-
else {
|
|
2509
|
-
fileContext += toAdd;
|
|
2510
|
-
injectedChars += toAdd.length;
|
|
2511
|
-
}
|
|
2512
|
+
if (lineCount <= FULL_INJECT_LINES) {
|
|
2513
|
+
fileContext += `\n\n[File: ${fname} — ${lineCount} lines — FULL]\n` + content;
|
|
2512
2514
|
}
|
|
2513
2515
|
else {
|
|
2514
2516
|
fileContext += `\n\n[File: ${fname} — ${lineCount} lines]\n` +
|
|
@@ -2542,7 +2544,6 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
2542
2544
|
}
|
|
2543
2545
|
// ── Ctrl+C cancel during streaming ──────────────────────────
|
|
2544
2546
|
let streamCancelled = false;
|
|
2545
|
-
let streamInputBuffer = '';
|
|
2546
2547
|
const streamAbort = new AbortController();
|
|
2547
2548
|
const onStreamKey = (data) => {
|
|
2548
2549
|
try {
|
|
@@ -2550,10 +2551,6 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
2550
2551
|
streamCancelled = true;
|
|
2551
2552
|
streamAbort.abort();
|
|
2552
2553
|
}
|
|
2553
|
-
else {
|
|
2554
|
-
// Capture user input during stream — will be queued for next turn
|
|
2555
|
-
streamInputBuffer += data;
|
|
2556
|
-
}
|
|
2557
2554
|
}
|
|
2558
2555
|
catch { /* ignore */ }
|
|
2559
2556
|
};
|
|
@@ -2938,7 +2935,6 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
2938
2935
|
if (_u && _a) {
|
|
2939
2936
|
generateAITitle(String(_u.content), String(_a.content), token).then(t => {
|
|
2940
2937
|
conversationTitle = t;
|
|
2941
|
-
setTerminalTitle(t);
|
|
2942
2938
|
saveConversation(sessionId, t, activeCwd, history.slice(1));
|
|
2943
2939
|
}).catch(() => { });
|
|
2944
2940
|
}
|
|
@@ -3025,8 +3021,6 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
3025
3021
|
// Trailing newline — skip when read ops follow (they start on current line)
|
|
3026
3022
|
if (hasRenderedContent && readOps.length === 0)
|
|
3027
3023
|
process.stdout.write('\n');
|
|
3028
|
-
// Synchronisation flag: ops/else must wait for readOps to finish
|
|
3029
|
-
let readOpsDone = readOps.length === 0;
|
|
3030
3024
|
// ── Handle read_file ops ──────────────────────────────
|
|
3031
3025
|
// forcedEditMode: block further reads — AI must use existing context
|
|
3032
3026
|
if (readOps.length > 0 && forcedEditMode) {
|
|
@@ -3037,7 +3031,6 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
3037
3031
|
readFileContinue = true;
|
|
3038
3032
|
}
|
|
3039
3033
|
else if (readOps.length > 0) {
|
|
3040
|
-
readOpsDone = false;
|
|
3041
3034
|
// Absorb blank lines so reading block starts immediately after user message
|
|
3042
3035
|
if (hasRenderedContent) {
|
|
3043
3036
|
// Ensure cursor is on a new line, then absorb excess trailing blank lines
|
|
@@ -3215,22 +3208,18 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
3215
3208
|
}
|
|
3216
3209
|
process.stdout.write(` ` + BC('╰' + '─'.repeat(boxW - 2) + '╯') + `\n`);
|
|
3217
3210
|
readFileContinue = true;
|
|
3218
|
-
|
|
3211
|
+
resolve();
|
|
3219
3212
|
}
|
|
3220
3213
|
catch (e) {
|
|
3221
3214
|
logError('readOps', e);
|
|
3222
|
-
|
|
3223
|
-
readOpsDone = true;
|
|
3215
|
+
resolve();
|
|
3224
3216
|
}
|
|
3225
3217
|
})();
|
|
3218
|
+
return;
|
|
3226
3219
|
} // end else if (readOps.length > 0)
|
|
3227
|
-
// Process write/run/mkdir/delete ops — waits for readOps to finish first
|
|
3228
3220
|
if (ops.length > 0) {
|
|
3229
3221
|
(async () => {
|
|
3230
3222
|
try {
|
|
3231
|
-
// Wait for any pending read operations to finish
|
|
3232
|
-
while (!readOpsDone)
|
|
3233
|
-
await new Promise(r => setTimeout(r, 20));
|
|
3234
3223
|
const skippedPaths = new Set();
|
|
3235
3224
|
const runOutputs = [];
|
|
3236
3225
|
const fileOpErrors = [];
|
|
@@ -3369,12 +3358,7 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
3369
3358
|
})();
|
|
3370
3359
|
}
|
|
3371
3360
|
else {
|
|
3372
|
-
|
|
3373
|
-
(async () => {
|
|
3374
|
-
while (!readOpsDone)
|
|
3375
|
-
await new Promise(r => setTimeout(r, 20));
|
|
3376
|
-
resolve();
|
|
3377
|
-
})();
|
|
3361
|
+
resolve();
|
|
3378
3362
|
}
|
|
3379
3363
|
}, (err) => {
|
|
3380
3364
|
clearInterval(spinnerInterval);
|
|
@@ -3405,12 +3389,6 @@ The summary should feel like a natural conversation close — informative, brief
|
|
|
3405
3389
|
});
|
|
3406
3390
|
// ── Remove streaming key listener ─────────────────────────
|
|
3407
3391
|
process.stdin.removeListener('data', onStreamKey);
|
|
3408
|
-
// ── Queue captured input for next user turn ─────────────────
|
|
3409
|
-
const captured = streamInputBuffer.trim();
|
|
3410
|
-
if (captured && !queuedResult) {
|
|
3411
|
-
queuedResult = { text: captured, lines: [captured] };
|
|
3412
|
-
}
|
|
3413
|
-
streamInputBuffer = '';
|
|
3414
3392
|
// ── Accumulate token usage (deferred — report on next user turn) ──
|
|
3415
3393
|
if (cliTok && fullResponse) {
|
|
3416
3394
|
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.
|
|
3
|
+
"version": "1.4.8",
|
|
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"
|