@taj-special/dravix-code 1.3.9 → 1.4.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.
package/dist/cli/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import * as path from 'path';
3
- import { randomBytes } from 'crypto';
3
+ import * as fs from 'fs';
4
+ import * as os from 'os';
5
+ import { randomBytes, createHash } from 'crypto';
4
6
  import { execSync } from 'child_process';
5
7
  import chalk from 'chalk';
6
8
  import { banner, printHelp, printError, colors } from '../utils/display.js';
@@ -9,6 +11,85 @@ import { isLoggedIn, logout, getSavedUser, saveConfig, checkPlan, AUTH_URL } fro
9
11
  import { checkUsage, usageBar, fmtNum, formatResetTime } from '../services/usage.js';
10
12
  const POLL_URL = 'https://dravix.app/cli-auth.php';
11
13
  const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
14
+ const VISITED_FILE = path.join(os.homedir(), '.dravix-code', 'visited.json');
15
+ // ── Project visit tracking ─────────────────────────────────────
16
+ function projectHash(cwd) {
17
+ return createHash('sha256').update(path.resolve(cwd)).digest('hex').slice(0, 10);
18
+ }
19
+ function getVisitedProjects() {
20
+ try {
21
+ return JSON.parse(fs.readFileSync(VISITED_FILE, 'utf-8'));
22
+ }
23
+ catch {
24
+ return [];
25
+ }
26
+ }
27
+ function markProjectVisited(hash) {
28
+ const visited = getVisitedProjects();
29
+ if (!visited.includes(hash)) {
30
+ visited.push(hash);
31
+ fs.mkdirSync(path.dirname(VISITED_FILE), { recursive: true });
32
+ fs.writeFileSync(VISITED_FILE, JSON.stringify(visited), 'utf-8');
33
+ }
34
+ }
35
+ async function showFirstTimeDialog(projectName) {
36
+ const cols = process.stdout.columns ?? 80;
37
+ const W = Math.min(cols - 4, 56);
38
+ const inner = W + 2;
39
+ const BC = chalk.hex('#6366f1');
40
+ const Wt = chalk.hex('#e2e8f0');
41
+ const Dim = chalk.hex('#6b7280');
42
+ const G = chalk.hex('#34d399');
43
+ const R = chalk.hex('#f87171');
44
+ const head = chalk.hex('#a78bfa').bold('Dravix Code') + Dim(' - first time in this project');
45
+ const projText = chalk.hex('#c4b5fd').bold(' ' + projectName);
46
+ function row(text) {
47
+ const visible = text.replace(/\x1b\[[0-9;]*m/g, '').length;
48
+ const pad = Math.max(inner - visible, 0);
49
+ return ' ' + BC('│') + ' ' + text + ' '.repeat(pad) + BC('│');
50
+ }
51
+ function padLine(fullWidth, left, right) {
52
+ const lVisible = left.replace(/\x1b\[[0-9;]*m/g, '').length;
53
+ const rVisible = right.replace(/\x1b\[[0-9;]*m/g, '').length;
54
+ const pad = Math.max(fullWidth - lVisible - rVisible, 0);
55
+ return left + right + ' '.repeat(pad);
56
+ }
57
+ console.clear();
58
+ console.log('');
59
+ console.log(' ' + BC('╭' + '─'.repeat(inner) + '╮'));
60
+ console.log(row(head));
61
+ console.log(' ' + BC('├' + '─'.repeat(inner) + '┤'));
62
+ console.log(row(Wt('Open Dravix Code in:')));
63
+ console.log(row(projText));
64
+ console.log(' ' + BC('├' + '─'.repeat(inner) + '┤'));
65
+ console.log(row(''));
66
+ console.log(row(padLine(inner - 2, G.bold(' [ Enter ] '), Wt(' Yes, open here'))));
67
+ console.log(row(padLine(inner - 2, R.bold(' [ Esc ] '), Wt(' No, exit'))));
68
+ console.log(row(''));
69
+ console.log(' ' + BC('╰' + '─'.repeat(inner) + '╯'));
70
+ console.log('');
71
+ return new Promise((resolve) => {
72
+ process.stdin.setRawMode(true);
73
+ process.stdin.resume();
74
+ process.stdin.setEncoding('utf8');
75
+ function onKey(data) {
76
+ if (data === '\r' || data === '\n') {
77
+ cleanup();
78
+ resolve(true);
79
+ }
80
+ else if (data === '\x1b' || data === '\x03') {
81
+ cleanup();
82
+ resolve(false);
83
+ }
84
+ }
85
+ function cleanup() {
86
+ process.stdin.removeListener('data', onKey);
87
+ process.stdin.setRawMode(false);
88
+ process.stdin.pause();
89
+ }
90
+ process.stdin.on('data', onKey);
91
+ });
92
+ }
12
93
  function link(url) {
13
94
  return `\x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\`;
14
95
  }
@@ -221,6 +302,22 @@ async function main() {
221
302
  process.on('exit', cleanup);
222
303
  process.on('SIGINT', () => { cleanup(); process.exit(0); });
223
304
  process.on('SIGTERM', () => { cleanup(); process.exit(0); });
305
+ // ── First-time project check ──────────────────────────────
306
+ const pHash = projectHash(cwd);
307
+ const visited = getVisitedProjects();
308
+ if (!visited.includes(pHash)) {
309
+ process.stdin.setRawMode(false);
310
+ process.stdin.pause();
311
+ const open = await showFirstTimeDialog(path.basename(cwd));
312
+ if (!open) {
313
+ console.log(chalk.hex('#6b7280')('\n Exited.\n'));
314
+ process.exit(0);
315
+ }
316
+ markProjectVisited(pHash);
317
+ // Restore terminal for upcoming banner + REPL
318
+ process.stdin.setRawMode(true);
319
+ process.stdin.resume();
320
+ }
224
321
  await startRepl(cwd);
225
322
  }
226
323
  main().catch((err) => {
package/dist/cli/repl.js CHANGED
@@ -1707,8 +1707,8 @@ function timeAgoShort(iso) {
1707
1707
  return `${d}d`;
1708
1708
  return `${Math.floor(d / 30)}mo`;
1709
1709
  }
1710
- async function showConversationPicker() {
1711
- const allConvs = listConversations(100);
1710
+ async function showConversationPicker(cwdForList) {
1711
+ const allConvs = listConversations(cwdForList, 100);
1712
1712
  if (allConvs.length === 0) {
1713
1713
  printInfo('No saved conversations yet.');
1714
1714
  return null;
@@ -2415,9 +2415,9 @@ The summary should feel like a natural conversation close — informative, brief
2415
2415
  lastUserLine = line;
2416
2416
  // ── Slash commands ──────────────────────────────────────────
2417
2417
  if (!skipInput && line.trim() === '/resume') {
2418
- const convId = await showConversationPicker();
2418
+ const convId = await showConversationPicker(activeCwd);
2419
2419
  if (convId) {
2420
- const conv = loadConversation(convId);
2420
+ const conv = loadConversation(convId, activeCwd);
2421
2421
  if (conv) {
2422
2422
  // Restore the original working directory so file ops work correctly
2423
2423
  activeCwd = conv.cwd;
@@ -1,9 +1,18 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import * as os from 'os';
4
- const CONV_DIR = path.join(os.homedir(), '.dravix-code', 'conversations');
5
- function ensureDir() {
6
- fs.mkdirSync(CONV_DIR, { recursive: true });
4
+ import { createHash } from 'crypto';
5
+ const BASE_DIR = path.join(os.homedir(), '.dravix-code', 'projects');
6
+ function projectDir(cwd) {
7
+ const hash = createHash('sha256').update(cwd).digest('hex').slice(0, 12);
8
+ return path.join(BASE_DIR, hash);
9
+ }
10
+ function convDir(cwd) {
11
+ return path.join(projectDir(cwd), 'conversations');
12
+ }
13
+ function ensureDir(cwd) {
14
+ const dir = convDir(cwd);
15
+ fs.mkdirSync(dir, { recursive: true });
7
16
  }
8
17
  export function generateId() {
9
18
  return Date.now().toString(36) + Math.random().toString(36).slice(2, 9) + Math.random().toString(36).slice(2, 5);
@@ -18,7 +27,7 @@ export function saveConversation(id, title, cwd, messages) {
18
27
  const chat = messages.filter(m => m.role === 'user' || m.role === 'assistant');
19
28
  if (chat.length === 0)
20
29
  return;
21
- ensureDir();
30
+ ensureDir(cwd);
22
31
  const data = {
23
32
  id, title, cwd,
24
33
  updatedAt: new Date().toISOString(),
@@ -26,26 +35,34 @@ export function saveConversation(id, title, cwd, messages) {
26
35
  messages,
27
36
  };
28
37
  try {
29
- fs.writeFileSync(path.join(CONV_DIR, `${id}.json`), JSON.stringify(data), 'utf-8');
38
+ fs.writeFileSync(path.join(convDir(cwd), `${id}.json`), JSON.stringify(data), 'utf-8');
30
39
  }
31
40
  catch { /* ignore write errors */ }
32
41
  }
33
- export function loadConversation(id) {
42
+ export function loadConversation(id, cwd) {
34
43
  try {
35
- return JSON.parse(fs.readFileSync(path.join(CONV_DIR, `${id}.json`), 'utf-8'));
44
+ const fp = path.join(convDir(cwd), `${id}.json`);
45
+ if (!fs.existsSync(fp)) {
46
+ // Try legacy global conversations directory
47
+ const legacyPath = path.join(os.homedir(), '.dravix-code', 'conversations', `${id}.json`);
48
+ if (fs.existsSync(legacyPath))
49
+ return JSON.parse(fs.readFileSync(legacyPath, 'utf-8'));
50
+ return null;
51
+ }
52
+ return JSON.parse(fs.readFileSync(fp, 'utf-8'));
36
53
  }
37
54
  catch {
38
55
  return null;
39
56
  }
40
57
  }
41
- export function listConversations(limit = 30) {
42
- ensureDir();
58
+ export function listConversations(cwd, limit = 30) {
59
+ ensureDir(cwd);
43
60
  try {
44
- return fs.readdirSync(CONV_DIR)
61
+ return fs.readdirSync(convDir(cwd))
45
62
  .filter(f => f.endsWith('.json'))
46
63
  .map(f => {
47
64
  try {
48
- const c = JSON.parse(fs.readFileSync(path.join(CONV_DIR, f), 'utf-8'));
65
+ const c = JSON.parse(fs.readFileSync(path.join(convDir(cwd), f), 'utf-8'));
49
66
  return { id: c.id, title: c.title, cwd: c.cwd, updatedAt: c.updatedAt, messageCount: c.messageCount };
50
67
  }
51
68
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taj-special/dravix-code",
3
- "version": "1.3.9",
3
+ "version": "1.4.1",
4
4
  "description": "AI-powered coding assistant CLI — Dravix Code",
5
5
  "type": "module",
6
6
  "bin": {