@taj-special/dravix-code 1.3.8 → 1.4.0
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/commands.js +1 -2
- package/dist/cli/index.js +84 -1
- package/dist/cli/repl.js +5 -5
- package/dist/services/conversations.js +28 -11
- package/package.json +1 -1
package/dist/cli/commands.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
1
|
import { execFileSync } from 'child_process';
|
|
3
2
|
import { buildContext } from '../services/context.js';
|
|
4
3
|
import { banner, printHelp, printInfo, colors } from '../utils/display.js';
|
|
@@ -38,7 +37,7 @@ export async function handleCommand(cmd, cwd, clearHistory, addFileToContext, ge
|
|
|
38
37
|
}
|
|
39
38
|
if (name === '/clear' || name === '/new') {
|
|
40
39
|
clearHistory();
|
|
41
|
-
process.stdout.write(`\x1b]0;Dravix Code —
|
|
40
|
+
process.stdout.write(`\x1b]0;Dravix Code — AI-powered coding assistant\x07`);
|
|
42
41
|
console.clear();
|
|
43
42
|
banner();
|
|
44
43
|
const { name: userName } = getSavedUser();
|
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
|
|
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,71 @@ 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, 58);
|
|
38
|
+
const BC = chalk.hex('#6366f1');
|
|
39
|
+
const Wt = chalk.hex('#e2e8f0');
|
|
40
|
+
const Dim = chalk.hex('#6b7280');
|
|
41
|
+
const G = chalk.hex('#34d399');
|
|
42
|
+
const R = chalk.hex('#f87171');
|
|
43
|
+
console.clear();
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log(' ' + BC('╭' + '─'.repeat(W) + '╮'));
|
|
46
|
+
console.log(' ' + BC('│') + ' ' + chalk.hex('#a78bfa').bold('Dravix Code') + Dim(' — first time in this project') + ' '.repeat(Math.max(W - 44, 0)) + BC('│'));
|
|
47
|
+
console.log(' ' + BC('├' + '─'.repeat(W) + '┤'));
|
|
48
|
+
console.log(' ' + BC('│') + ' ' + Wt('Open Dravix Code in:') + ' '.repeat(Math.max(W - 22, 0)) + BC('│'));
|
|
49
|
+
console.log(' ' + BC('│') + ' ' + chalk.hex('#c4b5fd').bold(` ${projectName}`) + ' '.repeat(Math.max(W - projectName.length - 8, 0)) + BC('│'));
|
|
50
|
+
console.log(' ' + BC('├' + '─'.repeat(W) + '┤'));
|
|
51
|
+
console.log(' ' + BC('│') + ' ' + ' '.repeat(Math.max(W - 28, 0)) + BC('│'));
|
|
52
|
+
console.log(' ' + BC('│') + ' ' + G.bold(' [ Enter ] ') + Wt(' Yes, open here') + ' '.repeat(Math.max(W - 40, 0)) + BC('│'));
|
|
53
|
+
console.log(' ' + BC('│') + ' ' + R.bold(' [ Esc ] ') + Wt(' No, exit') + ' '.repeat(Math.max(W - 39, 0)) + BC('│'));
|
|
54
|
+
console.log(' ' + BC('│') + ' ' + ' '.repeat(Math.max(W - 28, 0)) + BC('│'));
|
|
55
|
+
console.log(' ' + BC('╰' + '─'.repeat(W) + '╯'));
|
|
56
|
+
console.log('');
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
process.stdin.setRawMode(true);
|
|
59
|
+
process.stdin.resume();
|
|
60
|
+
process.stdin.setEncoding('utf8');
|
|
61
|
+
function onKey(data) {
|
|
62
|
+
if (data === '\r' || data === '\n') {
|
|
63
|
+
cleanup();
|
|
64
|
+
resolve(true);
|
|
65
|
+
}
|
|
66
|
+
else if (data === '\x1b' || data === '\x03') {
|
|
67
|
+
cleanup();
|
|
68
|
+
resolve(false);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function cleanup() {
|
|
72
|
+
process.stdin.removeListener('data', onKey);
|
|
73
|
+
process.stdin.setRawMode(false);
|
|
74
|
+
process.stdin.pause();
|
|
75
|
+
}
|
|
76
|
+
process.stdin.on('data', onKey);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
12
79
|
function link(url) {
|
|
13
80
|
return `\x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\`;
|
|
14
81
|
}
|
|
@@ -221,6 +288,22 @@ async function main() {
|
|
|
221
288
|
process.on('exit', cleanup);
|
|
222
289
|
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
223
290
|
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
291
|
+
// ── First-time project check ──────────────────────────────
|
|
292
|
+
const pHash = projectHash(cwd);
|
|
293
|
+
const visited = getVisitedProjects();
|
|
294
|
+
if (!visited.includes(pHash)) {
|
|
295
|
+
process.stdin.setRawMode(false);
|
|
296
|
+
process.stdin.pause();
|
|
297
|
+
const open = await showFirstTimeDialog(path.basename(cwd));
|
|
298
|
+
if (!open) {
|
|
299
|
+
console.log(chalk.hex('#6b7280')('\n Exited.\n'));
|
|
300
|
+
process.exit(0);
|
|
301
|
+
}
|
|
302
|
+
markProjectVisited(pHash);
|
|
303
|
+
// Restore terminal for upcoming banner + REPL
|
|
304
|
+
process.stdin.setRawMode(true);
|
|
305
|
+
process.stdin.resume();
|
|
306
|
+
}
|
|
224
307
|
await startRepl(cwd);
|
|
225
308
|
}
|
|
226
309
|
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;
|
|
@@ -2149,7 +2149,7 @@ export async function startRepl(cwd) {
|
|
|
2149
2149
|
function setTerminalTitle(title) {
|
|
2150
2150
|
process.stdout.write(`\x1b]0;Dravix Code — ${title}\x07`);
|
|
2151
2151
|
}
|
|
2152
|
-
setTerminalTitle(
|
|
2152
|
+
setTerminalTitle('AI-powered coding assistant');
|
|
2153
2153
|
const token = getToken() ?? '';
|
|
2154
2154
|
let SYSTEM_PROMPT = `You are Dravix Code, an interactive CLI coding agent powered by DeepSeek that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
|
2155
2155
|
|
|
@@ -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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 {
|