summaryrefslogtreecommitdiff
path: root/packages/cli/src/config/memoryUtils.ts
diff options
context:
space:
mode:
authorAllen Hutchison <[email protected]>2025-05-16 16:36:50 -0700
committerGitHub <[email protected]>2025-05-16 16:36:50 -0700
commit1bdec55fe1c658069a45df0aa8e4923ba1954e41 (patch)
tree32aa334cb0590f06830f52ed7c0d84e2d4ed7db3 /packages/cli/src/config/memoryUtils.ts
parentd9bd2b0e144560c8a82806bfb021a028c7cd43c9 (diff)
feat: Implement CLI and model memory management (#371)
Co-authored-by: N. Taylor Mullen <[email protected]>
Diffstat (limited to 'packages/cli/src/config/memoryUtils.ts')
-rw-r--r--packages/cli/src/config/memoryUtils.ts163
1 files changed, 163 insertions, 0 deletions
diff --git a/packages/cli/src/config/memoryUtils.ts b/packages/cli/src/config/memoryUtils.ts
new file mode 100644
index 00000000..d44b5ebf
--- /dev/null
+++ b/packages/cli/src/config/memoryUtils.ts
@@ -0,0 +1,163 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import * as fs from 'fs/promises';
+import * as path from 'path';
+import { homedir } from 'os';
+import { SETTINGS_DIRECTORY_NAME } from './settings.js';
+import {
+ getErrorMessage,
+ MemoryTool,
+ GEMINI_MD_FILENAME,
+ MEMORY_SECTION_HEADER,
+} from '@gemini-code/server';
+
+/**
+ * Gets the absolute path to the global GEMINI.md file.
+ */
+export function getGlobalMemoryFilePath(): string {
+ return path.join(homedir(), SETTINGS_DIRECTORY_NAME, GEMINI_MD_FILENAME);
+}
+
+/**
+ * Adds a new memory entry to the global GEMINI.md file under the specified header.
+ */
+export async function addMemoryEntry(text: string): Promise<void> {
+ const filePath = getGlobalMemoryFilePath();
+ // The performAddMemoryEntry method from MemoryTool will handle its own errors
+ // and throw an appropriately formatted error if needed.
+ await MemoryTool.performAddMemoryEntry(text, filePath, {
+ readFile: fs.readFile,
+ writeFile: fs.writeFile,
+ mkdir: fs.mkdir,
+ });
+}
+
+/**
+ * Deletes the last added memory entry from the "Gemini Added Memories" section.
+ */
+export async function deleteLastMemoryEntry(): Promise<boolean> {
+ const filePath = getGlobalMemoryFilePath();
+ try {
+ let content = await fs.readFile(filePath, 'utf-8');
+ const headerIndex = content.indexOf(MEMORY_SECTION_HEADER);
+
+ if (headerIndex === -1) return false; // Section not found
+
+ const startOfSectionContent = headerIndex + MEMORY_SECTION_HEADER.length;
+ let endOfSectionIndex = content.indexOf('\n## ', startOfSectionContent);
+ if (endOfSectionIndex === -1) {
+ endOfSectionIndex = content.length;
+ }
+
+ const sectionPart = content.substring(
+ startOfSectionContent,
+ endOfSectionIndex,
+ );
+ const lines = sectionPart.split(/\r?\n/).map((line) => line.trimEnd());
+
+ let lastBulletLineIndex = -1;
+ for (let i = lines.length - 1; i >= 0; i--) {
+ if (lines[i].trim().startsWith('- ')) {
+ lastBulletLineIndex = i;
+ break;
+ }
+ }
+
+ if (lastBulletLineIndex === -1) return false; // No bullets found in section
+
+ lines.splice(lastBulletLineIndex, 1);
+
+ const newSectionPart = lines
+ .filter((line) => line.trim().length > 0)
+ .join('\n');
+
+ const beforeHeader = content.substring(0, headerIndex);
+ const afterSection = content.substring(endOfSectionIndex);
+
+ if (newSectionPart.trim().length === 0) {
+ // If section is now empty (no bullets), remove header too or leave it clean
+ // For simplicity, let's leave the header but ensure it has a newline after if content follows
+ content = `${beforeHeader}${MEMORY_SECTION_HEADER}\n${afterSection}`
+ .replace(/\n{3,}/g, '\n\n')
+ .trimEnd();
+ if (content.length > 0) content += '\n';
+ } else {
+ content =
+ `${beforeHeader}${MEMORY_SECTION_HEADER}\n${newSectionPart}\n${afterSection}`
+ .replace(/\n{3,}/g, '\n\n')
+ .trimEnd();
+ if (content.length > 0) content += '\n';
+ }
+
+ await fs.writeFile(filePath, content, 'utf-8');
+ return true;
+ } catch (error) {
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
+ return false;
+ }
+ console.error(`Error deleting last memory entry from ${filePath}:`, error);
+ throw new Error(
+ `Failed to delete last memory entry: ${getErrorMessage(error)}`,
+ );
+ }
+}
+
+/**
+ * Deletes all added memory entries (the entire "Gemini Added Memories" section).
+ */
+export async function deleteAllAddedMemoryEntries(): Promise<number> {
+ const filePath = getGlobalMemoryFilePath();
+ try {
+ let content = await fs.readFile(filePath, 'utf-8');
+ const headerIndex = content.indexOf(MEMORY_SECTION_HEADER);
+
+ if (headerIndex === -1) return 0; // Section not found
+
+ let endOfSectionIndex = content.indexOf(
+ '\n## ',
+ headerIndex + MEMORY_SECTION_HEADER.length,
+ );
+ if (endOfSectionIndex === -1) {
+ endOfSectionIndex = content.length; // Section goes to EOF
+ }
+
+ const sectionContent = content.substring(headerIndex, endOfSectionIndex);
+ const bulletCount = (sectionContent.match(/\n- /g) || []).length;
+
+ if (bulletCount === 0 && !sectionContent.includes('- ')) {
+ // No bullets found
+ // If we only remove if bullets exist, or remove header if no bullets.
+ // For now, if header exists but no bullets, consider 0 deleted if we only count bullets.
+ // If the goal is to remove the section if it exists, this logic changes.
+ // Let's assume we only care about bulleted items for the count.
+ }
+
+ // Remove the section including the header
+ const beforeHeader = content.substring(0, headerIndex);
+ const afterSection = content.substring(endOfSectionIndex);
+
+ content = (
+ beforeHeader.trimEnd() +
+ (afterSection.length > 0 ? '\n' + afterSection.trimStart() : '')
+ ).trim();
+ if (content.length > 0) content += '\n';
+
+ await fs.writeFile(filePath, content, 'utf-8');
+ return bulletCount; // This counts '\n- ' occurrences, might need refinement for exact bullet count
+ } catch (error) {
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
+ return 0;
+ }
+ console.error(
+ `Error deleting all added memory entries from ${filePath}:`,
+ error,
+ );
+ throw new Error(
+ `Failed to delete all added memory entries: ${getErrorMessage(error)}`,
+ );
+ }
+}