@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
141 lines • 5.2 kB
JavaScript
import fs from 'fs/promises';
import { getClosestConfigFile } from './config/index.js';
import { MAX_PROMPT_HISTORY_SIZE } from './constants.js';
import { logError } from './utils/message-queue.js';
const ENTRY_SEPARATOR = '\n---ENTRY_SEPARATOR---\n';
const JSON_FORMAT_MARKER = '---JSON_FORMAT---';
export class PromptHistory {
history = [];
currentIndex = -1;
historyFile;
savePromise = Promise.resolve();
constructor(historyFile) {
this.historyFile =
historyFile ?? getClosestConfigFile('.nano-coder-history');
}
async loadHistory() {
try {
const content = await fs.readFile(this.historyFile, 'utf8');
if (content.startsWith(JSON_FORMAT_MARKER)) {
// New JSON format with InputState objects
const jsonContent = content.slice(JSON_FORMAT_MARKER.length);
this.history = JSON.parse(jsonContent);
}
else if (content.includes(ENTRY_SEPARATOR)) {
// Legacy format with separator - migrate to InputState
const stringEntries = content
.split(ENTRY_SEPARATOR)
.filter(entry => entry.trim() !== '');
this.history = this.migrateStringArrayToInputState(stringEntries);
}
else {
// Very old format - single lines - migrate to InputState
const stringEntries = content
.split('\n')
.filter(line => line.trim() !== '');
this.history = this.migrateStringArrayToInputState(stringEntries);
}
this.currentIndex = -1;
}
catch {
// File doesn't exist yet, start with empty history
this.history = [];
this.currentIndex = -1;
}
}
migrateStringArrayToInputState(stringEntries) {
return stringEntries.map(entry => ({
displayValue: entry,
placeholderContent: {},
}));
}
async saveHistory() {
// Chain this save onto the previous save to prevent concurrent writes
this.savePromise = this.savePromise.then(async () => {
try {
const jsonContent = JSON.stringify(this.history, null, 2);
await fs.writeFile(this.historyFile, JSON_FORMAT_MARKER + jsonContent, 'utf8');
}
catch (error) {
// Silently fail to avoid disrupting the user experience
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logError(`Failed to save prompt history: ${errorMessage}`);
}
});
return this.savePromise;
}
addPrompt(input) {
let inputState;
if (typeof input === 'string') {
const trimmed = input.trim();
if (!trimmed)
return;
inputState = {
displayValue: trimmed,
placeholderContent: {},
};
}
else {
if (!input.displayValue.trim())
return;
inputState = input;
}
// Remove duplicate if it exists (compare by displayValue)
const existingIndex = this.history.findIndex(entry => entry.displayValue === inputState.displayValue);
if (existingIndex !== -1) {
this.history.splice(existingIndex, 1);
}
// Add to the end
this.history.push(inputState);
// Keep only the last MAX_PROMPT_HISTORY_SIZE entries
if (this.history.length > MAX_PROMPT_HISTORY_SIZE) {
this.history = this.history.slice(-MAX_PROMPT_HISTORY_SIZE);
}
this.currentIndex = -1;
void this.saveHistory(); // Fire and forget
}
getPrevious() {
if (this.history.length === 0)
return null;
if (this.currentIndex === -1) {
this.currentIndex = this.history.length - 1;
}
else if (this.currentIndex > 0) {
this.currentIndex--;
}
return this.history[this.currentIndex] ?? null;
}
getNext() {
if (this.history.length === 0 || this.currentIndex === -1)
return null;
if (this.currentIndex < this.history.length - 1) {
this.currentIndex++;
return this.history[this.currentIndex] ?? null;
}
else {
this.currentIndex = -1;
return null; // Changed from empty string to null for consistency
}
}
// Legacy methods for backward compatibility
getPreviousString() {
const result = this.getPrevious();
return result?.displayValue ?? null;
}
getNextString() {
const result = this.getNext();
return result?.displayValue ?? '';
}
resetIndex() {
this.currentIndex = -1;
}
getHistory() {
return [...this.history];
}
// Legacy method for backward compatibility
getHistoryStrings() {
return this.history.map(entry => entry.displayValue);
}
}
export const promptHistory = new PromptHistory();
//# sourceMappingURL=prompt-history.js.map