fix(cli): change image paste location to global temp directory (#17396) (#17396)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
Dev Randalpura
2026-01-23 14:45:24 -08:00
committed by GitHub
parent da1664c7a0
commit daccf4d6d1
3 changed files with 35 additions and 11 deletions
@@ -43,6 +43,9 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
debug: vi.fn(), debug: vi.fn(),
warn: vi.fn(), warn: vi.fn(),
}, },
Storage: class {
getProjectTempDir = vi.fn(() => '/tmp/global');
},
}; };
}); });
@@ -169,7 +172,7 @@ describe('clipboardUtils', () => {
describe('saveClipboardImage (Linux)', () => { describe('saveClipboardImage (Linux)', () => {
const mockTargetDir = '/tmp/target'; const mockTargetDir = '/tmp/target';
const mockTempDir = path.join(mockTargetDir, '.gemini-clipboard'); const mockTempDir = path.join('/tmp/global', 'images');
beforeEach(() => { beforeEach(() => {
setPlatform('linux'); setPlatform('linux');
@@ -240,6 +243,7 @@ describe('clipboardUtils', () => {
const result = await promise; const result = await promise;
expect(result).toContain(mockTempDir);
expect(result).toMatch(/clipboard-\d+\.png$/); expect(result).toMatch(/clipboard-\d+\.png$/);
expect(spawn).toHaveBeenCalledWith('wl-paste', expect.any(Array)); expect(spawn).toHaveBeenCalledWith('wl-paste', expect.any(Array));
expect(fs.mkdir).toHaveBeenCalledWith(mockTempDir, { recursive: true }); expect(fs.mkdir).toHaveBeenCalledWith(mockTempDir, { recursive: true });
@@ -310,15 +314,18 @@ describe('clipboardUtils', () => {
// Stateless functions continue to use static imports // Stateless functions continue to use static imports
describe('cleanupOldClipboardImages', () => { describe('cleanupOldClipboardImages', () => {
const mockTargetDir = '/tmp/target';
it('should not throw errors', async () => { it('should not throw errors', async () => {
// Should handle missing directories gracefully // Should handle missing directories gracefully
await expect( await expect(
cleanupOldClipboardImages('/path/that/does/not/exist'), cleanupOldClipboardImages(mockTargetDir),
).resolves.not.toThrow(); ).resolves.not.toThrow();
}); });
it('should complete without errors on valid directory', async () => { it('should complete without errors on valid directory', async () => {
await expect(cleanupOldClipboardImages('.')).resolves.not.toThrow(); await expect(
cleanupOldClipboardImages(mockTargetDir),
).resolves.not.toThrow();
}); });
}); });
+22 -8
View File
@@ -13,6 +13,7 @@ import {
spawnAsync, spawnAsync,
unescapePath, unescapePath,
escapePath, escapePath,
Storage,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
/** /**
@@ -244,19 +245,33 @@ const saveFileWithXclip = async (tempFilePath: string) => {
return false; return false;
}; };
/**
* Gets the directory where clipboard images should be stored for a specific project.
*
* This uses the global temporary directory but creates a project-specific subdirectory
* based on the hash of the project path (via `Storage.getProjectTempDir()`).
* This prevents path conflicts between different projects while keeping the images
* outside of the user's project directory.
*
* @param targetDir The root directory of the current project.
* @returns The absolute path to the images directory.
*/
function getProjectClipboardImagesDir(targetDir: string): string {
const storage = new Storage(targetDir);
const baseDir = storage.getProjectTempDir();
return path.join(baseDir, 'images');
}
/** /**
* Saves the image from clipboard to a temporary file (macOS, Windows, and Linux) * Saves the image from clipboard to a temporary file (macOS, Windows, and Linux)
* @param targetDir The target directory to create temp files within * @param targetDir The target directory to create temp files within
* @returns The path to the saved image file, or null if no image or error * @returns The path to the saved image file, or null if no image or error
*/ */
export async function saveClipboardImage( export async function saveClipboardImage(
targetDir?: string, targetDir: string,
): Promise<string | null> { ): Promise<string | null> {
try { try {
// Create a temporary directory for clipboard images within the target directory const tempDir = getProjectClipboardImagesDir(targetDir);
// This avoids security restrictions on paths outside the target directory
const baseDir = targetDir || process.cwd();
const tempDir = path.join(baseDir, '.gemini-clipboard');
await fs.mkdir(tempDir, { recursive: true }); await fs.mkdir(tempDir, { recursive: true });
// Generate a unique filename with timestamp // Generate a unique filename with timestamp
@@ -378,11 +393,10 @@ export async function saveClipboardImage(
* @param targetDir The target directory where temp files are stored * @param targetDir The target directory where temp files are stored
*/ */
export async function cleanupOldClipboardImages( export async function cleanupOldClipboardImages(
targetDir?: string, targetDir: string,
): Promise<void> { ): Promise<void> {
try { try {
const baseDir = targetDir || process.cwd(); const tempDir = getProjectClipboardImagesDir(targetDir);
const tempDir = path.join(baseDir, '.gemini-clipboard');
const files = await fs.readdir(tempDir); const files = await fs.readdir(tempDir);
const oneHourAgo = Date.now() - 60 * 60 * 1000; const oneHourAgo = Date.now() - 60 * 60 * 1000;
@@ -16,6 +16,9 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
return { return {
...actual, ...actual,
spawnAsync: vi.fn(), spawnAsync: vi.fn(),
Storage: class {
getProjectTempDir = vi.fn(() => "C:\\User's Files");
},
}; };
}); });