diff --git a/package-lock.json b/package-lock.json index cefcb757..2f55b474 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1166,6 +1166,21 @@ "tslib": "2" } }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "license": "MIT" + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz", @@ -8522,6 +8537,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-git": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.28.0.tgz", + "integrity": "sha512-Rs/vQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+a2w==", + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, "node_modules/slice-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", @@ -10765,6 +10795,7 @@ "ignore": "^7.0.0", "open": "^10.1.2", "shell-quote": "^1.8.2", + "simple-git": "^3.27.0", "strip-ansi": "^7.1.0", "undici": "^7.10.0" }, diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index d1e7ea0c..1c8ef625 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -43,6 +43,7 @@ interface CliArgs { show_memory_usage: boolean | undefined; yolo: boolean | undefined; telemetry: boolean | undefined; + checkpoint: boolean | undefined; } async function parseArguments(): Promise { @@ -91,6 +92,12 @@ async function parseArguments(): Promise { type: 'boolean', description: 'Enable telemetry?', }) + .option('checkpoint', { + alias: 'c', + type: 'boolean', + description: 'Enables checkpointing of file edits', + default: false, + }) .version(process.env.CLI_VERSION || '0.0.0') // This will enable the --version flag based on package.json .help() .alias('h', 'help') @@ -178,6 +185,7 @@ export async function loadCliConfig( fileFilteringAllowBuildArtifacts: settings.fileFiltering?.allowBuildArtifacts, enableModifyWithExternalEditors: settings.enableModifyWithExternalEditors, + checkpoint: argv.checkpoint, }); } diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index eb4f6bb6..555a7c11 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -17,6 +17,7 @@ import { getStartupWarnings } from './utils/startupWarnings.js'; import { runNonInteractive } from './nonInteractiveCli.js'; import { loadGeminiIgnorePatterns } from './utils/loadIgnorePatterns.js'; import { loadExtensions, ExtensionConfig } from './config/extension.js'; +import { cleanupCheckpoints } from './utils/cleanup.js'; import { ApprovalMode, Config, @@ -40,7 +41,7 @@ export async function main() { setWindowTitle(basename(workspaceRoot), settings); const geminiIgnorePatterns = loadGeminiIgnorePatterns(workspaceRoot); - + await cleanupCheckpoints(); if (settings.errors.length > 0) { for (const error of settings.errors) { let errorMessage = `Error in ${error.path}: ${error.message}`; @@ -63,6 +64,13 @@ export async function main() { // Initialize centralized FileDiscoveryService await config.getFileService(); + if (config.getCheckpointEnabled()) { + try { + await config.getGitService(); + } catch { + // For now swallow the error, later log it. + } + } if (settings.merged.theme) { if (!themeManager.setActiveTheme(settings.merged.theme)) { diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index fefb2fe2..bfd2efaf 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -63,6 +63,7 @@ interface MockServerConfig { getVertexAI: Mock<() => boolean | undefined>; getShowMemoryUsage: Mock<() => boolean>; getAccessibility: Mock<() => AccessibilitySettings>; + getProjectRoot: Mock<() => string | undefined>; } // Mock @gemini-cli/core and its Config class @@ -120,7 +121,9 @@ vi.mock('@gemini-cli/core', async (importOriginal) => { getVertexAI: vi.fn(() => opts.vertexai), getShowMemoryUsage: vi.fn(() => opts.showMemoryUsage ?? false), getAccessibility: vi.fn(() => opts.accessibility ?? {}), + getProjectRoot: vi.fn(() => opts.projectRoot), getGeminiClient: vi.fn(() => ({})), + getCheckpointEnabled: vi.fn(() => opts.checkpoint ?? true), }; }); return { diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index bf8c2abb..cdec11e2 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -66,7 +66,7 @@ export const AppWrapper = (props: AppProps) => ( ); const App = ({ config, settings, startupWarnings = [] }: AppProps) => { - const { history, addItem, clearItems } = useHistory(); + const { history, addItem, clearItems, loadHistory } = useHistory(); const { consoleMessages, handleNewMessage, @@ -151,8 +151,10 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => { const { handleSlashCommand, slashCommands } = useSlashCommandProcessor( config, + history, addItem, clearItems, + loadHistory, refreshStatic, setShowHelp, setDebugMessage, @@ -217,6 +219,7 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => { const { streamingState, submitQuery, initError, pendingHistoryItems } = useGeminiStream( config.getGeminiClient(), + history, addItem, setShowHelp, config, @@ -512,7 +515,6 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => { )} )} -