@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 +0 -0
- package/dist/cli/repl.js +353 -359
- package/package.json +4 -4
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
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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 -
|
|
1033
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1062
|
-
|
|
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
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
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
|
-
|
|
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;
|
|
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 -
|
|
1100
|
-
|
|
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
|
-
|
|
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 <
|
|
1110
|
-
const cmd =
|
|
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 ?
|
|
1118
|
-
|
|
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 = ' ↑↓
|
|
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
|
-
|
|
1127
|
-
|
|
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
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
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
|
|
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
|
-
|
|
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 -
|
|
1166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1194
|
-
|
|
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
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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
|
|
1215
|
-
const
|
|
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(
|
|
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[${
|
|
1228
|
-
process.stdout.write(
|
|
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.
|
|
1248
|
-
process.stdout.
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
const
|
|
1252
|
-
|
|
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(
|
|
1332
|
-
process.stdout.write(
|
|
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'
|
|
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(
|
|
1715
|
-
const allConvs = listConversations(
|
|
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 =
|
|
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
|
|
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
|
-
##
|
|
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
|
-
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
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 so — do 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);
|
|
2355
|
-
process.on('uncaughtException', (err) => { logError('uncaughtException', err);
|
|
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
|
-
//
|
|
2377
|
-
|
|
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
|
-
//
|
|
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 =
|
|
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
|
|
2508
|
-
|
|
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
|
-
|
|
3231
|
+
resolve();
|
|
3223
3232
|
}
|
|
3224
3233
|
catch (e) {
|
|
3225
3234
|
logError('readOps', e);
|
|
3226
|
-
|
|
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
|
-
|
|
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.
|
|
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"
|