archicore 0.2.4 → 0.2.5
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/interactive.js +336 -89
- package/dist/cli/utils/conversation-history.d.ts +84 -0
- package/dist/cli/utils/conversation-history.js +301 -0
- package/dist/cli/utils/index.d.ts +2 -0
- package/dist/cli/utils/index.js +2 -0
- package/dist/cli/utils/upload-utils.d.ts +66 -0
- package/dist/cli/utils/upload-utils.js +495 -0
- package/dist/server/routes/api.js +140 -1
- package/package.json +1 -1
|
@@ -10,6 +10,8 @@ import { printFormattedError, printStartupError, } from '../utils/error-handler.
|
|
|
10
10
|
import { isInitialized, getLocalProject } from './init.js';
|
|
11
11
|
import { requireAuth, logout } from './auth.js';
|
|
12
12
|
import { colors, icons, createSpinner, printHelp, printGoodbye, printSection, printSuccess, printError, printWarning, printInfo, printKeyValue, header, } from '../ui/index.js';
|
|
13
|
+
import { createSession, loadLastSession, addMessage, getContextMessages, listSessions, searchHistory, exportSessionAsText, clearAllHistory, } from '../utils/conversation-history.js';
|
|
14
|
+
import { uploadIndexData, analyzeNetworkError, analyzeHttpError, } from '../utils/upload-utils.js';
|
|
13
15
|
// Command registry with descriptions (like Claude CLI)
|
|
14
16
|
const COMMANDS = [
|
|
15
17
|
{ name: 'index', aliases: ['i'], description: 'Index your codebase for analysis' },
|
|
@@ -23,6 +25,8 @@ const COMMANDS = [
|
|
|
23
25
|
{ name: 'rules', aliases: [], description: 'Check architectural rules' },
|
|
24
26
|
{ name: 'docs', aliases: ['documentation'], description: 'Generate architecture documentation' },
|
|
25
27
|
{ name: 'export', aliases: [], description: 'Export analysis results' },
|
|
28
|
+
{ name: 'history', aliases: ['hist'], description: 'View and search conversation history' },
|
|
29
|
+
{ name: 'resume', aliases: [], description: 'Resume previous conversation session' },
|
|
26
30
|
{ name: 'status', aliases: [], description: 'Show connection and project status' },
|
|
27
31
|
{ name: 'clear', aliases: ['cls'], description: 'Clear the screen' },
|
|
28
32
|
{ name: 'help', aliases: ['h'], description: 'Show available commands' },
|
|
@@ -36,6 +40,7 @@ const state = {
|
|
|
36
40
|
projectPath: process.cwd(),
|
|
37
41
|
history: [],
|
|
38
42
|
user: null,
|
|
43
|
+
conversationSession: null,
|
|
39
44
|
};
|
|
40
45
|
// Track token usage for session
|
|
41
46
|
let sessionTokensUsed = 0;
|
|
@@ -124,6 +129,23 @@ export async function startInteractiveMode() {
|
|
|
124
129
|
process.exit(1);
|
|
125
130
|
}
|
|
126
131
|
state.user = user;
|
|
132
|
+
// Initialize or resume conversation session
|
|
133
|
+
const lastSession = await loadLastSession(state.projectId || undefined);
|
|
134
|
+
if (lastSession) {
|
|
135
|
+
// Check if last session was recent (within 1 hour)
|
|
136
|
+
const lastActivity = new Date(lastSession.lastActivityAt).getTime();
|
|
137
|
+
const oneHourAgo = Date.now() - 60 * 60 * 1000;
|
|
138
|
+
if (lastActivity > oneHourAgo) {
|
|
139
|
+
state.conversationSession = lastSession;
|
|
140
|
+
console.log(colors.muted(` ${icons.info} Resuming previous session (${lastSession.messages.length} messages)`));
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
state.conversationSession = await createSession(state.projectId || undefined, state.projectName || undefined);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
state.conversationSession = await createSession(state.projectId || undefined, state.projectName || undefined);
|
|
148
|
+
}
|
|
127
149
|
// Print welcome
|
|
128
150
|
console.log();
|
|
129
151
|
console.log(header('ArchiCore', 'AI Software Architect'));
|
|
@@ -134,6 +156,7 @@ export async function startInteractiveMode() {
|
|
|
134
156
|
console.log();
|
|
135
157
|
console.log(colors.muted(' Type / to see commands, or ask a question about your code.'));
|
|
136
158
|
console.log(colors.muted(' Press Tab to autocomplete. Type ? for shortcuts.'));
|
|
159
|
+
console.log(colors.muted(' Use /history to view past conversations, /resume to continue.'));
|
|
137
160
|
console.log();
|
|
138
161
|
// Interactive input with real-time autocomplete
|
|
139
162
|
await startInteractiveInput(state);
|
|
@@ -473,6 +496,13 @@ async function handleCommand(input) {
|
|
|
473
496
|
await logout();
|
|
474
497
|
state.running = false;
|
|
475
498
|
break;
|
|
499
|
+
case 'history':
|
|
500
|
+
case 'hist':
|
|
501
|
+
await handleHistoryCommand(args);
|
|
502
|
+
break;
|
|
503
|
+
case 'resume':
|
|
504
|
+
await handleResumeCommand(args);
|
|
505
|
+
break;
|
|
476
506
|
default:
|
|
477
507
|
printFormattedError(`Unknown command: /${command}`, {
|
|
478
508
|
suggestion: 'Use /help to see available commands',
|
|
@@ -496,11 +526,24 @@ async function handleIndexCommand() {
|
|
|
496
526
|
if (response.ok) {
|
|
497
527
|
const data = await response.json();
|
|
498
528
|
state.projectId = data.id || data.project?.id;
|
|
529
|
+
registerSpinner.succeed('Project registered');
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
533
|
+
const errorDetails = analyzeHttpError(response.status, errorBody);
|
|
534
|
+
registerSpinner.fail('Failed to register project');
|
|
535
|
+
printError(errorDetails.message);
|
|
536
|
+
printInfo(errorDetails.suggestion);
|
|
537
|
+
console.log(colors.dim(` Technical: ${errorDetails.technicalDetails}`));
|
|
538
|
+
return;
|
|
499
539
|
}
|
|
500
|
-
registerSpinner.succeed('Project registered');
|
|
501
540
|
}
|
|
502
|
-
catch {
|
|
541
|
+
catch (error) {
|
|
542
|
+
const errorDetails = analyzeNetworkError(error);
|
|
503
543
|
registerSpinner.fail('Failed to register project');
|
|
544
|
+
printError(errorDetails.message);
|
|
545
|
+
printInfo(errorDetails.suggestion);
|
|
546
|
+
console.log(colors.dim(` Technical: ${errorDetails.technicalDetails}`));
|
|
504
547
|
return;
|
|
505
548
|
}
|
|
506
549
|
}
|
|
@@ -524,22 +567,28 @@ async function handleIndexCommand() {
|
|
|
524
567
|
indexSpinner.update(`Found ${symbols.size} symbols, building graph...`);
|
|
525
568
|
// Строим граф зависимостей
|
|
526
569
|
const graph = codeIndex.buildDependencyGraph(asts, symbols);
|
|
527
|
-
|
|
528
|
-
|
|
570
|
+
// Определяем размер проекта для оптимизации
|
|
571
|
+
const isLargeProject = symbols.size > 50000 || asts.size > 1000;
|
|
572
|
+
// Читаем содержимое файлов (с оптимизацией для больших проектов)
|
|
529
573
|
const fileContents = [];
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
574
|
+
if (!isLargeProject) {
|
|
575
|
+
indexSpinner.update('Reading file contents...');
|
|
576
|
+
for (const [filePath] of asts) {
|
|
577
|
+
try {
|
|
578
|
+
const fullPath = pathModule.default.isAbsolute(filePath)
|
|
579
|
+
? filePath
|
|
580
|
+
: pathModule.default.join(state.projectPath, filePath);
|
|
581
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
582
|
+
fileContents.push([filePath, content]);
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
// Игнорируем ошибки чтения отдельных файлов
|
|
586
|
+
}
|
|
540
587
|
}
|
|
541
588
|
}
|
|
542
|
-
|
|
589
|
+
else {
|
|
590
|
+
indexSpinner.update('Large project detected, skipping file contents for faster upload...');
|
|
591
|
+
}
|
|
543
592
|
// Конвертируем Maps в массивы для JSON
|
|
544
593
|
const astsArray = Array.from(asts.entries());
|
|
545
594
|
const symbolsArray = Array.from(symbols.entries());
|
|
@@ -547,89 +596,78 @@ async function handleIndexCommand() {
|
|
|
547
596
|
nodes: Array.from(graph.nodes.entries()),
|
|
548
597
|
edges: Array.from(graph.edges.entries()),
|
|
549
598
|
};
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
599
|
+
const indexData = {
|
|
600
|
+
asts: astsArray,
|
|
601
|
+
symbols: symbolsArray,
|
|
602
|
+
graph: graphData,
|
|
603
|
+
fileContents,
|
|
604
|
+
statistics: {
|
|
605
|
+
totalFiles: asts.size,
|
|
606
|
+
totalSymbols: symbols.size,
|
|
556
607
|
},
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
fileContents,
|
|
562
|
-
statistics: {
|
|
563
|
-
totalFiles: asts.size,
|
|
564
|
-
totalSymbols: symbols.size,
|
|
565
|
-
},
|
|
566
|
-
}),
|
|
608
|
+
};
|
|
609
|
+
// Загружаем на сервер с прогрессом и обработкой ошибок
|
|
610
|
+
const uploadResult = await uploadIndexData(state.projectId, indexData, (progress) => {
|
|
611
|
+
indexSpinner.update(progress.message);
|
|
567
612
|
});
|
|
568
|
-
if (!
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const retryResponse = await fetch(`${config.serverUrl}/api/projects/${state.projectId}/upload-index`, {
|
|
588
|
-
method: 'POST',
|
|
589
|
-
headers: {
|
|
590
|
-
'Content-Type': 'application/json',
|
|
591
|
-
'Authorization': `Bearer ${config.accessToken}`,
|
|
592
|
-
},
|
|
593
|
-
body: JSON.stringify({
|
|
594
|
-
asts: astsArray,
|
|
595
|
-
symbols: symbolsArray,
|
|
596
|
-
graph: graphData,
|
|
597
|
-
fileContents,
|
|
598
|
-
statistics: {
|
|
599
|
-
totalFiles: asts.size,
|
|
600
|
-
totalSymbols: symbols.size,
|
|
613
|
+
if (!uploadResult.success) {
|
|
614
|
+
// Детальная обработка ошибок
|
|
615
|
+
indexSpinner.fail('Indexing failed');
|
|
616
|
+
// Debug output to see what's in the result
|
|
617
|
+
console.log(colors.dim(` [DEBUG] uploadResult: ${JSON.stringify(uploadResult, null, 2)}`));
|
|
618
|
+
if (uploadResult.errorDetails) {
|
|
619
|
+
const { code, message, suggestion, technicalDetails } = uploadResult.errorDetails;
|
|
620
|
+
console.log();
|
|
621
|
+
console.log(colors.error(` ${icons.error} Error: ${message}`));
|
|
622
|
+
console.log(colors.warning(` ${icons.info} ${suggestion}`));
|
|
623
|
+
if (code === 'FORBIDDEN') {
|
|
624
|
+
// Пробуем перерегистрировать проект
|
|
625
|
+
console.log(colors.muted(' Attempting to re-register project...'));
|
|
626
|
+
try {
|
|
627
|
+
const reRegisterResponse = await fetch(`${config.serverUrl}/api/projects`, {
|
|
628
|
+
method: 'POST',
|
|
629
|
+
headers: {
|
|
630
|
+
'Content-Type': 'application/json',
|
|
631
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
601
632
|
},
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
633
|
+
body: JSON.stringify({ name: state.projectName, path: state.projectPath }),
|
|
634
|
+
});
|
|
635
|
+
if (reRegisterResponse.ok) {
|
|
636
|
+
const reData = await reRegisterResponse.json();
|
|
637
|
+
state.projectId = reData.id || reData.project?.id;
|
|
638
|
+
printInfo('Project re-registered. Run /index again to complete indexing.');
|
|
639
|
+
// Обновляем локальный конфиг
|
|
640
|
+
const localProjectRetry = await getLocalProject(state.projectPath);
|
|
641
|
+
if (localProjectRetry && state.projectId) {
|
|
642
|
+
localProjectRetry.id = state.projectId;
|
|
643
|
+
await fs.writeFile(pathModule.default.join(state.projectPath, '.archicore', 'project.json'), JSON.stringify(localProjectRetry, null, 2));
|
|
644
|
+
}
|
|
645
|
+
}
|
|
606
646
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
indexSpinner.succeed('Project re-registered and indexed');
|
|
610
|
-
printKeyValue('Files', String(retryData.statistics?.filesCount || asts.size));
|
|
611
|
-
printKeyValue('Symbols', String(retryData.statistics?.symbolsCount || symbols.size));
|
|
612
|
-
// Update local config with new project ID
|
|
613
|
-
const localProjectRetry = await getLocalProject(state.projectPath);
|
|
614
|
-
if (localProjectRetry && state.projectId) {
|
|
615
|
-
localProjectRetry.id = state.projectId;
|
|
616
|
-
localProjectRetry.indexed = true;
|
|
617
|
-
await fs.writeFile(pathModule.default.join(state.projectPath, '.archicore', 'project.json'), JSON.stringify(localProjectRetry, null, 2));
|
|
647
|
+
catch {
|
|
648
|
+
// Игнорируем ошибку перерегистрации
|
|
618
649
|
}
|
|
619
|
-
return; // Exit early - already handled
|
|
620
|
-
}
|
|
621
|
-
else {
|
|
622
|
-
throw new Error('Failed to re-register project');
|
|
623
650
|
}
|
|
651
|
+
console.log(colors.dim(` [${code}] ${technicalDetails}`));
|
|
624
652
|
}
|
|
625
653
|
else {
|
|
626
|
-
|
|
654
|
+
printError(uploadResult.error || 'Unknown upload error');
|
|
627
655
|
}
|
|
656
|
+
return;
|
|
628
657
|
}
|
|
629
|
-
const data = await response.json();
|
|
630
658
|
indexSpinner.succeed('Project indexed and uploaded');
|
|
631
|
-
|
|
632
|
-
|
|
659
|
+
// Показываем статистику
|
|
660
|
+
const stats = uploadResult.statistics;
|
|
661
|
+
if (stats) {
|
|
662
|
+
printKeyValue('Files', String(stats.filesCount));
|
|
663
|
+
printKeyValue('Symbols', String(stats.symbolsCount));
|
|
664
|
+
if (stats.nodesCount) {
|
|
665
|
+
printKeyValue('Graph nodes', String(stats.nodesCount));
|
|
666
|
+
}
|
|
667
|
+
if (stats.edgesCount) {
|
|
668
|
+
printKeyValue('Graph edges', String(stats.edgesCount));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
633
671
|
// Обновляем локальный конфиг
|
|
634
672
|
const localProject = await getLocalProject(state.projectPath);
|
|
635
673
|
if (localProject) {
|
|
@@ -640,7 +678,28 @@ async function handleIndexCommand() {
|
|
|
640
678
|
}
|
|
641
679
|
catch (error) {
|
|
642
680
|
indexSpinner.fail('Indexing failed');
|
|
643
|
-
|
|
681
|
+
// Детальный анализ ошибки
|
|
682
|
+
const errorStr = String(error);
|
|
683
|
+
if (errorStr.includes('ENOENT') || errorStr.includes('no such file')) {
|
|
684
|
+
printError('File not found during indexing');
|
|
685
|
+
printInfo('Some files may have been deleted during the indexing process. Try again.');
|
|
686
|
+
}
|
|
687
|
+
else if (errorStr.includes('ENOMEM') || errorStr.includes('heap out of memory')) {
|
|
688
|
+
printError('Out of memory');
|
|
689
|
+
printInfo('The project is too large to index. Try increasing Node.js memory limit:');
|
|
690
|
+
console.log(colors.muted(' NODE_OPTIONS="--max-old-space-size=4096" archicore'));
|
|
691
|
+
}
|
|
692
|
+
else if (errorStr.includes('EACCES') || errorStr.includes('permission denied')) {
|
|
693
|
+
printError('Permission denied');
|
|
694
|
+
printInfo('Check that you have read access to all project files.');
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
// Общая ошибка
|
|
698
|
+
const errorDetails = analyzeNetworkError(error);
|
|
699
|
+
printError(errorDetails.message);
|
|
700
|
+
printInfo(errorDetails.suggestion);
|
|
701
|
+
console.log(colors.dim(` Technical: ${errorDetails.technicalDetails}`));
|
|
702
|
+
}
|
|
644
703
|
}
|
|
645
704
|
}
|
|
646
705
|
async function handleAnalyzeCommand(args) {
|
|
@@ -790,12 +849,30 @@ async function handleQuery(query) {
|
|
|
790
849
|
printInfo('Use /projects to list and select a project first');
|
|
791
850
|
return;
|
|
792
851
|
}
|
|
852
|
+
// Save user message to history
|
|
853
|
+
if (state.conversationSession) {
|
|
854
|
+
await addMessage(state.conversationSession, 'user', query, {
|
|
855
|
+
tokens: estimateTokens(query),
|
|
856
|
+
});
|
|
857
|
+
}
|
|
793
858
|
const spinner = createSpinner('Thinking...').start();
|
|
794
859
|
try {
|
|
795
860
|
const config = await loadConfig();
|
|
861
|
+
// Get context from conversation history for better responses
|
|
862
|
+
let contextMessages = [];
|
|
863
|
+
if (state.conversationSession) {
|
|
864
|
+
const history = getContextMessages(state.conversationSession, 10, 3000);
|
|
865
|
+
contextMessages = history.slice(0, -1).map(m => ({
|
|
866
|
+
role: m.role,
|
|
867
|
+
content: m.content,
|
|
868
|
+
}));
|
|
869
|
+
}
|
|
796
870
|
const response = await apiFetch(`${config.serverUrl}/api/projects/${state.projectId}/ask`, {
|
|
797
871
|
method: 'POST',
|
|
798
|
-
body: JSON.stringify({
|
|
872
|
+
body: JSON.stringify({
|
|
873
|
+
question: query,
|
|
874
|
+
context: contextMessages.length > 0 ? contextMessages : undefined,
|
|
875
|
+
}),
|
|
799
876
|
});
|
|
800
877
|
if (!response.ok)
|
|
801
878
|
throw new Error('Query failed');
|
|
@@ -811,6 +888,12 @@ async function handleQuery(query) {
|
|
|
811
888
|
for (const line of lines) {
|
|
812
889
|
console.log(' ' + line);
|
|
813
890
|
}
|
|
891
|
+
// Save assistant response to history
|
|
892
|
+
if (state.conversationSession) {
|
|
893
|
+
await addMessage(state.conversationSession, 'assistant', answer, {
|
|
894
|
+
tokens: estimateTokens(answer),
|
|
895
|
+
});
|
|
896
|
+
}
|
|
814
897
|
showTokenUsage();
|
|
815
898
|
}
|
|
816
899
|
catch (error) {
|
|
@@ -1203,4 +1286,168 @@ async function handleDocsCommand(args) {
|
|
|
1203
1286
|
throw error;
|
|
1204
1287
|
}
|
|
1205
1288
|
}
|
|
1289
|
+
/**
|
|
1290
|
+
* /history command - View and search conversation history
|
|
1291
|
+
*
|
|
1292
|
+
* Usage:
|
|
1293
|
+
* /history - Show recent sessions
|
|
1294
|
+
* /history search <query> - Search in history
|
|
1295
|
+
* /history export - Export current session
|
|
1296
|
+
* /history clear - Clear all history
|
|
1297
|
+
*/
|
|
1298
|
+
async function handleHistoryCommand(args) {
|
|
1299
|
+
const subcommand = args[0]?.toLowerCase();
|
|
1300
|
+
switch (subcommand) {
|
|
1301
|
+
case 'search': {
|
|
1302
|
+
const query = args.slice(1).join(' ');
|
|
1303
|
+
if (!query) {
|
|
1304
|
+
printError('Usage: /history search <query>');
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const spinner = createSpinner('Searching history...').start();
|
|
1308
|
+
const results = await searchHistory(query, { limit: 15 });
|
|
1309
|
+
spinner.stop();
|
|
1310
|
+
if (results.length === 0) {
|
|
1311
|
+
printWarning('No matches found');
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
printSection(`Search Results (${results.length})`);
|
|
1315
|
+
for (const { session, message } of results) {
|
|
1316
|
+
const date = new Date(message.timestamp).toLocaleDateString();
|
|
1317
|
+
const time = new Date(message.timestamp).toLocaleTimeString();
|
|
1318
|
+
const role = message.role === 'user' ? colors.primary('You') : colors.secondary('ArchiCore');
|
|
1319
|
+
const projectName = session.projectName || 'Unknown project';
|
|
1320
|
+
console.log();
|
|
1321
|
+
console.log(` ${colors.dim(`[${date} ${time}]`)} ${role} ${colors.dim(`in ${projectName}`)}`);
|
|
1322
|
+
// Show snippet with highlighted query
|
|
1323
|
+
const content = message.content.slice(0, 200);
|
|
1324
|
+
const queryLower = query.toLowerCase();
|
|
1325
|
+
const contentLower = content.toLowerCase();
|
|
1326
|
+
const idx = contentLower.indexOf(queryLower);
|
|
1327
|
+
if (idx >= 0) {
|
|
1328
|
+
const before = content.slice(0, idx);
|
|
1329
|
+
const match = content.slice(idx, idx + query.length);
|
|
1330
|
+
const after = content.slice(idx + query.length);
|
|
1331
|
+
console.log(` ${before}${colors.highlight(match)}${after}${content.length >= 200 ? '...' : ''}`);
|
|
1332
|
+
}
|
|
1333
|
+
else {
|
|
1334
|
+
console.log(` ${content}${content.length >= 200 ? '...' : ''}`);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
break;
|
|
1338
|
+
}
|
|
1339
|
+
case 'export': {
|
|
1340
|
+
if (!state.conversationSession) {
|
|
1341
|
+
printError('No active session to export');
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
const { writeFile } = await import('fs/promises');
|
|
1345
|
+
const exportContent = exportSessionAsText(state.conversationSession);
|
|
1346
|
+
const filename = `archicore-conversation-${state.conversationSession.id}.txt`;
|
|
1347
|
+
await writeFile(filename, exportContent);
|
|
1348
|
+
printSuccess(`Session exported to ${filename}`);
|
|
1349
|
+
break;
|
|
1350
|
+
}
|
|
1351
|
+
case 'clear': {
|
|
1352
|
+
const count = await clearAllHistory();
|
|
1353
|
+
printSuccess(`Cleared ${count} session(s) from history`);
|
|
1354
|
+
// Create a new session
|
|
1355
|
+
state.conversationSession = await createSession(state.projectId || undefined, state.projectName || undefined);
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
case 'context': {
|
|
1359
|
+
// Show current context being sent to AI
|
|
1360
|
+
if (!state.conversationSession) {
|
|
1361
|
+
printError('No active session');
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
const contextMessages = getContextMessages(state.conversationSession, 10, 2000);
|
|
1365
|
+
printSection(`Context (${contextMessages.length} messages)`);
|
|
1366
|
+
for (const msg of contextMessages) {
|
|
1367
|
+
const role = msg.role === 'user' ? colors.primary('You') : colors.secondary('ArchiCore');
|
|
1368
|
+
const time = new Date(msg.timestamp).toLocaleTimeString();
|
|
1369
|
+
const preview = msg.content.slice(0, 100).replace(/\n/g, ' ');
|
|
1370
|
+
console.log(` ${colors.dim(`[${time}]`)} ${role}: ${preview}${msg.content.length > 100 ? '...' : ''}`);
|
|
1371
|
+
}
|
|
1372
|
+
break;
|
|
1373
|
+
}
|
|
1374
|
+
default: {
|
|
1375
|
+
// Show recent sessions
|
|
1376
|
+
const sessions = await listSessions({ limit: 10 });
|
|
1377
|
+
if (sessions.length === 0) {
|
|
1378
|
+
printInfo('No conversation history yet');
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
printSection('Recent Sessions');
|
|
1382
|
+
for (const session of sessions) {
|
|
1383
|
+
const startDate = new Date(session.startedAt);
|
|
1384
|
+
const isActive = state.conversationSession?.id === session.id;
|
|
1385
|
+
const activeMarker = isActive ? colors.success(' (active)') : '';
|
|
1386
|
+
console.log();
|
|
1387
|
+
console.log(` ${colors.primary(session.id)}${activeMarker}`);
|
|
1388
|
+
console.log(` ${colors.dim('Project:')} ${session.projectName || 'N/A'}`);
|
|
1389
|
+
console.log(` ${colors.dim('Started:')} ${startDate.toLocaleString()}`);
|
|
1390
|
+
console.log(` ${colors.dim('Messages:')} ${session.messages.length}`);
|
|
1391
|
+
if (session.metadata?.commandsUsed && session.metadata.commandsUsed.length > 0) {
|
|
1392
|
+
console.log(` ${colors.dim('Commands:')} ${session.metadata.commandsUsed.slice(0, 5).join(', ')}`);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
console.log();
|
|
1396
|
+
console.log(colors.muted(' Use /resume <session_id> to continue a session'));
|
|
1397
|
+
console.log(colors.muted(' Use /history search <query> to search in history'));
|
|
1398
|
+
console.log(colors.muted(' Use /history export to save current session'));
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* /resume command - Resume a previous conversation session
|
|
1405
|
+
*/
|
|
1406
|
+
async function handleResumeCommand(args) {
|
|
1407
|
+
const sessionId = args[0];
|
|
1408
|
+
if (!sessionId) {
|
|
1409
|
+
// Show recent sessions to choose from
|
|
1410
|
+
const sessions = await listSessions({ limit: 5 });
|
|
1411
|
+
if (sessions.length === 0) {
|
|
1412
|
+
printInfo('No sessions available to resume');
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
printSection('Available Sessions');
|
|
1416
|
+
for (const session of sessions) {
|
|
1417
|
+
const date = new Date(session.startedAt).toLocaleString();
|
|
1418
|
+
const messages = session.messages.length;
|
|
1419
|
+
console.log(` ${colors.primary(session.id)}`);
|
|
1420
|
+
console.log(` ${session.projectName || 'Unknown'} - ${messages} messages - ${date}`);
|
|
1421
|
+
}
|
|
1422
|
+
console.log();
|
|
1423
|
+
printInfo('Usage: /resume <session_id>');
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
const spinner = createSpinner('Loading session...').start();
|
|
1427
|
+
try {
|
|
1428
|
+
const { loadSession } = await import('../utils/conversation-history.js');
|
|
1429
|
+
const session = await loadSession(sessionId);
|
|
1430
|
+
if (!session) {
|
|
1431
|
+
spinner.fail('Session not found');
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
state.conversationSession = session;
|
|
1435
|
+
spinner.succeed(`Resumed session with ${session.messages.length} messages`);
|
|
1436
|
+
// Show last few messages for context
|
|
1437
|
+
const recentMessages = session.messages.slice(-5);
|
|
1438
|
+
if (recentMessages.length > 0) {
|
|
1439
|
+
console.log();
|
|
1440
|
+
console.log(colors.muted(' Recent messages:'));
|
|
1441
|
+
for (const msg of recentMessages) {
|
|
1442
|
+
const role = msg.role === 'user' ? colors.primary('You') : colors.secondary('ArchiCore');
|
|
1443
|
+
const preview = msg.content.slice(0, 80).replace(/\n/g, ' ');
|
|
1444
|
+
console.log(` ${role}: ${preview}${msg.content.length > 80 ? '...' : ''}`);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
catch (error) {
|
|
1449
|
+
spinner.fail('Failed to load session');
|
|
1450
|
+
printError(String(error));
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1206
1453
|
//# sourceMappingURL=interactive.js.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArchiCore CLI - Conversation History Management
|
|
3
|
+
*
|
|
4
|
+
* Сохранение и загрузка истории диалогов между сессиями
|
|
5
|
+
*/
|
|
6
|
+
export interface ConversationMessage {
|
|
7
|
+
role: 'user' | 'assistant' | 'system';
|
|
8
|
+
content: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
command?: string;
|
|
11
|
+
tokens?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ConversationSession {
|
|
14
|
+
id: string;
|
|
15
|
+
projectId?: string;
|
|
16
|
+
projectName?: string;
|
|
17
|
+
startedAt: string;
|
|
18
|
+
lastActivityAt: string;
|
|
19
|
+
messages: ConversationMessage[];
|
|
20
|
+
metadata?: {
|
|
21
|
+
totalTokens?: number;
|
|
22
|
+
commandsUsed?: string[];
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Создать новую сессию разговора
|
|
27
|
+
*/
|
|
28
|
+
export declare function createSession(projectId?: string, projectName?: string): Promise<ConversationSession>;
|
|
29
|
+
/**
|
|
30
|
+
* Сохранить сессию на диск
|
|
31
|
+
*/
|
|
32
|
+
export declare function saveSession(session: ConversationSession): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Загрузить сессию с диска
|
|
35
|
+
*/
|
|
36
|
+
export declare function loadSession(sessionId: string): Promise<ConversationSession | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Загрузить последнюю сессию для проекта
|
|
39
|
+
*/
|
|
40
|
+
export declare function loadLastSession(projectId?: string): Promise<ConversationSession | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Добавить сообщение в сессию
|
|
43
|
+
*/
|
|
44
|
+
export declare function addMessage(session: ConversationSession, role: 'user' | 'assistant' | 'system', content: string, options?: {
|
|
45
|
+
command?: string;
|
|
46
|
+
tokens?: number;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Получить контекст для AI (последние N сообщений)
|
|
50
|
+
*/
|
|
51
|
+
export declare function getContextMessages(session: ConversationSession, maxMessages?: number, maxTokens?: number): ConversationMessage[];
|
|
52
|
+
/**
|
|
53
|
+
* Получить список всех сессий
|
|
54
|
+
*/
|
|
55
|
+
export declare function listSessions(options?: {
|
|
56
|
+
projectId?: string;
|
|
57
|
+
limit?: number;
|
|
58
|
+
search?: string;
|
|
59
|
+
}): Promise<ConversationSession[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Поиск по истории сообщений
|
|
62
|
+
*/
|
|
63
|
+
export declare function searchHistory(query: string, options?: {
|
|
64
|
+
projectId?: string;
|
|
65
|
+
limit?: number;
|
|
66
|
+
role?: 'user' | 'assistant';
|
|
67
|
+
}): Promise<Array<{
|
|
68
|
+
session: ConversationSession;
|
|
69
|
+
message: ConversationMessage;
|
|
70
|
+
index: number;
|
|
71
|
+
}>>;
|
|
72
|
+
/**
|
|
73
|
+
* Удалить сессию
|
|
74
|
+
*/
|
|
75
|
+
export declare function deleteSession(sessionId: string): Promise<boolean>;
|
|
76
|
+
/**
|
|
77
|
+
* Очистить всю историю
|
|
78
|
+
*/
|
|
79
|
+
export declare function clearAllHistory(): Promise<number>;
|
|
80
|
+
/**
|
|
81
|
+
* Экспорт сессии в текстовый формат
|
|
82
|
+
*/
|
|
83
|
+
export declare function exportSessionAsText(session: ConversationSession): string;
|
|
84
|
+
//# sourceMappingURL=conversation-history.d.ts.map
|