@taj-special/dravix-code 1.1.28 → 1.2.1

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.
@@ -0,0 +1,181 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ const UNDO_DIR = path.join(os.homedir(), '.dravix-code', 'undo');
5
+ let currentSession = null;
6
+ // Initialize undo directory
7
+ function ensureUndoDir() {
8
+ fs.mkdirSync(UNDO_DIR, { recursive: true });
9
+ }
10
+ // Generate unique session ID
11
+ function generateSessionId() {
12
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
13
+ }
14
+ // Start a new undo session (before a batch of operations)
15
+ export function startUndoSession() {
16
+ ensureUndoDir();
17
+ const id = generateSessionId();
18
+ currentSession = {
19
+ id,
20
+ timestamp: new Date().toISOString(),
21
+ operations: [],
22
+ };
23
+ return id;
24
+ }
25
+ // Backup a file before modification
26
+ export function backupFile(filePath, cwd) {
27
+ try {
28
+ if (!fs.existsSync(filePath))
29
+ return null;
30
+ const relPath = path.relative(cwd, filePath).replace(/\\/g, '/');
31
+ const backupId = currentSession?.id ?? generateSessionId();
32
+ const backupDir = path.join(UNDO_DIR, backupId);
33
+ fs.mkdirSync(backupDir, { recursive: true });
34
+ // Create backup with path-based naming
35
+ const backupName = relPath.replace(/[/\\]/g, '__');
36
+ const backupPath = path.join(backupDir, backupName);
37
+ fs.copyFileSync(filePath, backupPath);
38
+ return backupPath;
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ // Record a write operation for undo
45
+ export function recordWrite(filePath, backupPath) {
46
+ if (!currentSession)
47
+ return;
48
+ currentSession.operations.push({
49
+ type: 'write',
50
+ path: filePath,
51
+ backupPath: backupPath ?? undefined,
52
+ });
53
+ }
54
+ // Record an edit operation for undo
55
+ export function recordEdit(filePath, backupPath) {
56
+ if (!currentSession)
57
+ return;
58
+ currentSession.operations.push({
59
+ type: 'edit',
60
+ path: filePath,
61
+ backupPath: backupPath ?? undefined,
62
+ });
63
+ }
64
+ // Record a delete operation for undo
65
+ export function recordDelete(filePath) {
66
+ if (!currentSession)
67
+ return;
68
+ let content = '';
69
+ try {
70
+ content = fs.readFileSync(filePath, 'utf-8');
71
+ }
72
+ catch { /* ignore */ }
73
+ currentSession.operations.push({
74
+ type: 'delete',
75
+ path: filePath,
76
+ deletedContent: content || undefined,
77
+ });
78
+ }
79
+ // Save the current undo session
80
+ export function saveUndoSession() {
81
+ if (!currentSession || currentSession.operations.length === 0)
82
+ return;
83
+ ensureUndoDir();
84
+ const sessionPath = path.join(UNDO_DIR, `${currentSession.id}.json`);
85
+ fs.writeFileSync(sessionPath, JSON.stringify(currentSession, null, 2), 'utf-8');
86
+ }
87
+ // Undo the last session
88
+ export function undoLastSession(cwd) {
89
+ ensureUndoDir();
90
+ // Find the most recent session file
91
+ const files = fs.readdirSync(UNDO_DIR)
92
+ .filter(f => f.endsWith('.json'))
93
+ .sort()
94
+ .reverse();
95
+ if (files.length === 0) {
96
+ return { success: false, message: 'No undo history found' };
97
+ }
98
+ const sessionPath = path.join(UNDO_DIR, files[0]);
99
+ const session = JSON.parse(fs.readFileSync(sessionPath, 'utf-8'));
100
+ let restored = 0;
101
+ let errors = 0;
102
+ // Process operations in reverse order
103
+ for (const op of session.operations.reverse()) {
104
+ try {
105
+ if (op.type === 'write' || op.type === 'edit') {
106
+ // Restore from backup
107
+ if (op.backupPath && fs.existsSync(op.backupPath)) {
108
+ fs.copyFileSync(op.backupPath, op.path);
109
+ restored++;
110
+ }
111
+ else {
112
+ errors++;
113
+ }
114
+ }
115
+ else if (op.type === 'delete') {
116
+ // Recreate the deleted file
117
+ if (op.deletedContent !== undefined) {
118
+ fs.mkdirSync(path.dirname(op.path), { recursive: true });
119
+ fs.writeFileSync(op.path, op.deletedContent, 'utf-8');
120
+ restored++;
121
+ }
122
+ else {
123
+ errors++;
124
+ }
125
+ }
126
+ }
127
+ catch {
128
+ errors++;
129
+ }
130
+ }
131
+ // Clean up session file and backup directory
132
+ try {
133
+ fs.unlinkSync(sessionPath);
134
+ const backupDir = path.join(UNDO_DIR, session.id);
135
+ if (fs.existsSync(backupDir)) {
136
+ fs.rmSync(backupDir, { recursive: true });
137
+ }
138
+ }
139
+ catch { /* ignore */ }
140
+ const msg = `Undone: ${restored} operation(s) restored` +
141
+ (errors > 0 ? `, ${errors} error(s)` : '') +
142
+ ` from ${new Date(session.timestamp).toLocaleString()}`;
143
+ return { success: restored > 0, message: msg };
144
+ }
145
+ // Get list of available undo sessions
146
+ export function listUndoSessions() {
147
+ ensureUndoDir();
148
+ const files = fs.readdirSync(UNDO_DIR).filter(f => f.endsWith('.json'));
149
+ return files.map(f => {
150
+ try {
151
+ const session = JSON.parse(fs.readFileSync(path.join(UNDO_DIR, f), 'utf-8'));
152
+ return {
153
+ id: session.id,
154
+ timestamp: session.timestamp,
155
+ ops: session.operations.length,
156
+ };
157
+ }
158
+ catch {
159
+ return { id: f.replace('.json', ''), timestamp: '', ops: 0 };
160
+ }
161
+ }).sort((a, b) => b.timestamp.localeCompare(a.timestamp));
162
+ }
163
+ // Clean up old undo sessions (older than 7 days)
164
+ export function cleanupOldSessions() {
165
+ ensureUndoDir();
166
+ const files = fs.readdirSync(UNDO_DIR).filter(f => f.endsWith('.json'));
167
+ const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
168
+ for (const f of files) {
169
+ try {
170
+ const session = JSON.parse(fs.readFileSync(path.join(UNDO_DIR, f), 'utf-8'));
171
+ if (new Date(session.timestamp).getTime() < cutoff) {
172
+ fs.unlinkSync(path.join(UNDO_DIR, f));
173
+ const backupDir = path.join(UNDO_DIR, session.id);
174
+ if (fs.existsSync(backupDir)) {
175
+ fs.rmSync(backupDir, { recursive: true });
176
+ }
177
+ }
178
+ }
179
+ catch { /* ignore */ }
180
+ }
181
+ }
@@ -7,12 +7,18 @@ const _pkg = JSON.parse(readFileSync(join(_dir, '../../package.json'), 'utf-8'))
7
7
  export const VERSION = _pkg.version;
8
8
  export const colors = {
9
9
  primary: chalk.hex('#6366f1'),
10
+ accent: chalk.hex('#818cf8'),
10
11
  success: chalk.hex('#34d399'),
11
12
  error: chalk.hex('#f87171'),
12
13
  warn: chalk.hex('#fbbf24'),
13
14
  muted: chalk.hex('#6b7280'),
14
15
  ai: chalk.hex('#c4b5fd'),
15
16
  dim: chalk.hex('#374151'),
17
+ bg: chalk.hex('#0d1117'),
18
+ surface: chalk.hex('#161b22'),
19
+ border: chalk.hex('#30363d'),
20
+ text: chalk.hex('#e2e8f0'),
21
+ subtext: chalk.hex('#94a3b8'),
16
22
  };
17
23
  export function banner() {
18
24
  console.log(colors.primary.bold(`
@@ -22,57 +28,69 @@ export function banner() {
22
28
  ██║ ██║██╔══██╗██╔══██║╚██╗ ██╔╝██║ ██╔██╗
23
29
  ██████╔╝██║ ██║██║ ██║ ╚████╔╝ ██║██╔╝ ██╗
24
30
  ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚═╝╚═╝ ╚═╝`));
25
- console.log(chalk.hex('#6366f1').bold(' Code') + chalk.hex('#4b5563')(` v${VERSION} · `) + chalk.hex('#818cf8')('AI-powered coding assistant for your terminal'));
26
- console.log(chalk.hex('#374151')(' ─────────────────────────────────────────────\n'));
31
+ console.log(colors.primary.bold(' Code') + colors.muted(` v${VERSION} · `) + colors.accent('AI-powered coding assistant'));
32
+ console.log(colors.dim(' ─────────────────────────────────────────────\n'));
27
33
  }
28
34
  export function printAI(chunk) {
29
35
  process.stdout.write(colors.ai(chunk));
30
36
  }
31
37
  export function printOpResult(result) {
32
38
  if (result.type === 'created') {
33
- console.log(colors.success(' ✓') + chalk.white(' Created ') + colors.muted(result.path ?? ''));
39
+ console.log(colors.success(' ✓') + colors.text(' Created ') + colors.muted(result.path ?? ''));
34
40
  }
35
41
  else if (result.type === 'skipped') {
36
- console.log(colors.muted(' ·') + chalk.white(' Unchanged ') + colors.muted(result.path ?? ''));
42
+ console.log(colors.muted(' ·') + colors.text(' Unchanged ') + colors.muted(result.path ?? ''));
37
43
  }
38
44
  else if (result.type === 'folder_created') {
39
- console.log(colors.success(' ✓') + chalk.white(' Created dir ') + colors.muted(result.path ?? ''));
45
+ console.log(colors.success(' ✓') + colors.text(' Created dir ') + colors.muted(result.path ?? ''));
40
46
  }
41
47
  else if (result.type === 'deleted') {
42
- console.log(colors.error(' ✗') + chalk.white(' Deleted ') + colors.muted(result.path ?? ''));
48
+ console.log(colors.error(' ✗') + colors.text(' Deleted ') + colors.muted(result.path ?? ''));
43
49
  }
44
50
  else if (result.type === 'modified') {
45
51
  const diff = result.diff ?? [];
46
52
  const adds = diff.filter(d => d.kind === 'add').length;
47
53
  const removes = diff.filter(d => d.kind === 'remove').length;
48
54
  const cols = process.stdout.columns ?? 80;
49
- // ── Header ──────────────────────────────────────────────────
50
- // For large files (>1500 lines), diff is empty — use raw line counts
51
55
  const displayAdds = adds > 0 ? adds : (result.linesAfter ?? 0) - Math.min(result.linesBefore ?? 0, result.linesAfter ?? 0);
52
56
  const displayRemoves = removes > 0 ? removes : (result.linesBefore ?? 0) - Math.min(result.linesBefore ?? 0, result.linesAfter ?? 0);
53
57
  const statStr = [
54
- (adds > 0 || result.linesAfter !== undefined) && displayAdds > 0 ? chalk.hex('#34d399').bold(`+${displayAdds}`) : '',
55
- (removes > 0 || result.linesBefore !== undefined) && displayRemoves > 0 ? chalk.hex('#f87171').bold(`-${displayRemoves}`) : '',
58
+ (adds > 0 || result.linesAfter !== undefined) && displayAdds > 0 ? colors.success.bold(`+${displayAdds}`) : '',
59
+ (removes > 0 || result.linesBefore !== undefined) && displayRemoves > 0 ? colors.error.bold(`-${displayRemoves}`) : '',
56
60
  ].filter(Boolean).join(' ');
57
- const pathStr = chalk.hex('#e2e8f0')(result.path ?? '');
58
- const iconStr = chalk.hex('#fbbf24')(' ~');
59
- const labelStr = chalk.hex('#94a3b8')(' Updated ');
61
+ const pathStr = colors.text(result.path ?? '');
62
+ const iconStr = colors.warn(' ~');
63
+ const labelStr = colors.subtext(' Updated ');
60
64
  const statPad = statStr ? ' ' + statStr : '';
61
65
  console.log(iconStr + labelStr + pathStr + statPad);
62
- // Large file show line count change instead of full diff
66
+ // Compact mode: only show diff if changes are large (>30 lines)
67
+ // For smaller changes, show the actual diff
68
+ const totalChanges = displayAdds + displayRemoves;
69
+ if (totalChanges > 30) {
70
+ // Very large change — show compact summary only
71
+ if (result.linesBefore !== undefined && result.linesAfter !== undefined) {
72
+ const delta = result.linesAfter - result.linesBefore;
73
+ const deltaStr = delta > 0 ? colors.success(`+${delta}`) : delta < 0 ? colors.error(`${delta}`) : '=0';
74
+ console.log(colors.muted(` ${result.linesBefore} → ${result.linesAfter} lines (${deltaStr})`));
75
+ }
76
+ else {
77
+ console.log(colors.muted(` ${totalChanges} lines changed`));
78
+ }
79
+ console.log(colors.muted(` ${colors.muted('···')} ${colors.success(`+${displayAdds}`)} added, ${colors.error(`-${displayRemoves}`)} removed ${colors.muted('(more lines not shown)')}`));
80
+ return;
81
+ }
63
82
  if (diff.length === 0 && result.linesBefore !== undefined) {
64
83
  const delta = (result.linesAfter ?? 0) - (result.linesBefore ?? 0);
65
- const deltaStr = delta > 0 ? chalk.hex('#34d399')(`+${delta} lines`) : delta < 0 ? chalk.hex('#f87171')(`${delta} lines`) : 'no line count change';
66
- console.log(chalk.hex('#4b5563')(` ${result.linesBefore} → ${result.linesAfter} lines (${deltaStr})`));
84
+ const deltaStr = delta > 0 ? colors.success(`+${delta} lines`) : delta < 0 ? colors.error(`${delta} lines`) : 'no line count change';
85
+ console.log(colors.muted(` ${result.linesBefore} → ${result.linesAfter} lines (${deltaStr})`));
67
86
  return;
68
87
  }
69
88
  if (diff.length === 0 || (adds === 0 && removes === 0)) {
70
- console.log(chalk.hex('#4b5563')(' (formatting / whitespace change)'));
89
+ console.log(colors.muted(' (formatting / whitespace change)'));
71
90
  return;
72
91
  }
73
- // ── Diff body ────────────────────────────────────────────────
74
- const CONTEXT = 3;
75
- const MAX_SIDE = 40; // max removes and max adds shown (each independently)
92
+ const CONTEXT = 2; // Reduced from 3 to 2 for more compact diffs
93
+ const MAX_SIDE = 20; // Reduced from 40 to 20 for more compact diffs
76
94
  const show = new Uint8Array(diff.length);
77
95
  for (let i = 0; i < diff.length; i++) {
78
96
  if (diff[i].kind !== 'context') {
@@ -85,18 +103,17 @@ export function printOpResult(result) {
85
103
  const renderLine = (line) => {
86
104
  const num = String(line.lineNo).padStart(5, ' ');
87
105
  if (line.kind === 'remove') {
88
- const content = ` ${num} ${chalk.hex('#f87171')('-')} ${clip(line.text)}`;
89
- console.log(' ' + chalk.hex('#7f1d1d')('▌') + chalk.bgHex('#1c0a0a')(content.padEnd(cols - 3)));
106
+ const content = ` ${num} ${colors.error('-')} ${clip(line.text)}`;
107
+ console.log(' ' + colors.error('▌') + chalk.bgHex('#1c0a0a')(content.padEnd(cols - 3)));
90
108
  }
91
109
  else if (line.kind === 'add') {
92
- const content = ` ${num} ${chalk.hex('#34d399')('+')} ${clip(line.text)}`;
93
- console.log(' ' + chalk.hex('#065f46')('▌') + chalk.bgHex('#021a0e')(content.padEnd(cols - 3)));
110
+ const content = ` ${num} ${colors.success('+')} ${clip(line.text)}`;
111
+ console.log(' ' + colors.success('▌') + chalk.bgHex('#021a0e')(content.padEnd(cols - 3)));
94
112
  }
95
113
  else {
96
- console.log(chalk.hex('#374151')(` ${num} ${clip(line.text)}`));
114
+ console.log(colors.dim(` ${num} ${clip(line.text)}`));
97
115
  }
98
116
  };
99
- // Build hunks (contiguous visible sections)
100
117
  const hunks = [];
101
118
  let curHunk = [];
102
119
  let lastIdx = -1;
@@ -112,14 +129,13 @@ export function printOpResult(result) {
112
129
  }
113
130
  if (curHunk.length > 0)
114
131
  hunks.push(curHunk);
115
- // Per-hunk rendering: leading context → all removes → all adds → trailing context
116
132
  let shownRemoves = 0;
117
133
  let shownAdds = 0;
118
134
  for (let h = 0; h < hunks.length; h++) {
119
135
  if (shownRemoves >= MAX_SIDE && shownAdds >= MAX_SIDE)
120
136
  break;
121
137
  if (h > 0)
122
- console.log(chalk.hex('#374151')(' ╌╌╌'));
138
+ console.log(colors.dim(' ╌╌╌'));
123
139
  const hunk = hunks[h];
124
140
  const before = [];
125
141
  const hRemoves = [];
@@ -163,26 +179,26 @@ export function printOpResult(result) {
163
179
  const hiddenAdds = adds - shownAdds;
164
180
  if (hiddenRemoves > 0 || hiddenAdds > 0) {
165
181
  const parts = [
166
- hiddenRemoves > 0 ? chalk.hex('#f87171')(`-${hiddenRemoves}`) : '',
167
- hiddenAdds > 0 ? chalk.hex('#34d399')(`+${hiddenAdds}`) : '',
182
+ hiddenRemoves > 0 ? colors.error(`-${hiddenRemoves}`) : '',
183
+ hiddenAdds > 0 ? colors.success(`+${hiddenAdds}`) : '',
168
184
  ].filter(Boolean).join(' ');
169
- console.log(chalk.hex('#4b5563')(` … ${parts} more lines not shown`));
185
+ console.log(colors.muted(` … ${parts} more lines not shown`));
170
186
  }
171
187
  }
172
188
  else if (result.type === 'run') {
173
189
  const cmd = result.message ?? '';
174
190
  const cols = process.stdout.columns ?? 80;
175
- console.log(colors.primary(' $') + ' ' + chalk.white(cmd));
191
+ console.log(colors.primary(' $') + ' ' + colors.text(cmd));
176
192
  if (result.output) {
177
193
  const outLines = result.output.split('\n').filter(l => l.trim()).slice(0, 20);
178
194
  if (outLines.length > 0) {
179
195
  const maxW = Math.min(Math.max(...outLines.map(l => l.length), 10) + 2, cols - 8);
180
- console.log(chalk.hex('#374151')(' ╭' + '─'.repeat(maxW)));
196
+ console.log(colors.dim(' ╭' + '─'.repeat(maxW)));
181
197
  for (const line of outLines) {
182
198
  const clipped = line.length > maxW - 2 ? line.slice(0, maxW - 3) + '…' : line;
183
- console.log(chalk.hex('#374151')(' │') + ' ' + chalk.hex('#94a3b8')(clipped));
199
+ console.log(colors.dim(' │') + ' ' + colors.subtext(clipped));
184
200
  }
185
- console.log(chalk.hex('#374151')(' ╰' + '─'.repeat(maxW)));
201
+ console.log(colors.dim(' ╰' + '─'.repeat(maxW)));
186
202
  }
187
203
  }
188
204
  }
@@ -191,31 +207,31 @@ export function printOpResult(result) {
191
207
  const cols = process.stdout.columns ?? 80;
192
208
  const errLines = msg.split('\n').filter(l => l.trim());
193
209
  if (errLines.length <= 1) {
194
- console.log(colors.error(' ✗') + chalk.white(' ' + msg));
210
+ console.log(colors.error(' ✗') + colors.text(' ' + msg));
195
211
  }
196
212
  else {
197
- console.log(colors.error(' ✗') + chalk.white(' ' + errLines[0]));
213
+ console.log(colors.error(' ✗') + colors.text(' ' + errLines[0]));
198
214
  const rest = errLines.slice(1);
199
215
  const maxW = Math.min(Math.max(...rest.map(l => l.length), 10) + 2, cols - 8);
200
216
  console.log(chalk.hex('#450a0a')(' ╭' + '─'.repeat(maxW)));
201
217
  for (const line of rest) {
202
218
  const clipped = line.length > maxW - 2 ? line.slice(0, maxW - 3) + '…' : line;
203
- console.log(chalk.hex('#450a0a')(' │') + ' ' + chalk.hex('#fca5a5')(clipped));
219
+ console.log(chalk.hex('#450a0a')(' │') + ' ' + colors.error(clipped));
204
220
  }
205
221
  console.log(chalk.hex('#450a0a')(' ╰' + '─'.repeat(maxW)));
206
222
  }
207
223
  }
208
224
  }
209
225
  export function printError(msg) {
210
- console.log('\n' + colors.error(' ✗ ') + chalk.white(msg) + '\n');
226
+ console.log('\n' + colors.error(' ✗ ') + colors.text(msg) + '\n');
211
227
  }
212
228
  export function printInfo(msg) {
213
229
  console.log(colors.muted(' ' + msg));
214
230
  }
215
231
  export function printHelp() {
216
- const C = chalk.hex('#818cf8');
217
- const DIM = chalk.hex('#4b5563');
218
- const W = chalk.hex('#e2e8f0');
232
+ const C = colors.primary;
233
+ const DIM = colors.muted;
234
+ const W = colors.text;
219
235
  const cols = process.stdout.columns ?? 80;
220
236
  const line = DIM(' ' + '─'.repeat(Math.min(cols - 4, 60)));
221
237
  console.log(`
@@ -243,11 +259,6 @@ ${line}
243
259
  ${C('Space')} ${W('Select item in file picker')}
244
260
  ${C('Esc')} ${W('Close picker')}
245
261
 
246
- ${colors.primary.bold(' File picker triggers')}
247
- ${line}
248
- ${C('@filename')} ${W('Search and insert a file reference')}
249
- ${C('Tab')} ${W('Autocomplete path at cursor position')}
250
-
251
262
  ${colors.primary.bold(' Tips')}
252
263
  ${line}
253
264
  ${colors.muted('·')} ${W('Start typing while AI responds — your message queues automatically')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taj-special/dravix-code",
3
- "version": "1.1.28",
3
+ "version": "1.2.1",
4
4
  "description": "AI-powered coding assistant CLI — Dravix Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,11 +16,12 @@
16
16
  "prepublishOnly": "tsc"
17
17
  },
18
18
  "dependencies": {
19
+ "@taj-special/dravix-code": "^1.2.0",
19
20
  "chalk": "^5.3.0"
20
21
  },
21
22
  "devDependencies": {
22
- "typescript": "^5.4.0",
23
- "@types/node": "^20.0.0"
23
+ "@types/node": "^20.0.0",
24
+ "typescript": "^5.4.0"
24
25
  },
25
26
  "keywords": [
26
27
  "ai",