feat: Update permissions command to support modifying trust for other… (#11642)

This commit is contained in:
shrutip90
2025-11-14 14:41:53 -08:00
committed by GitHub
parent ce56b4ee1b
commit 472e775a13
14 changed files with 498 additions and 212 deletions
@@ -5,7 +5,8 @@
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { directoryCommand, expandHomeDir } from './directoryCommand.js';
import { directoryCommand } from './directoryCommand.js';
import { expandHomeDir } from '../utils/directoryUtils.js';
import type { Config, WorkspaceContext } from '@google/gemini-cli-core';
import type { CommandContext } from './types.js';
import { MessageType } from '../types.js';
@@ -7,22 +7,8 @@
import type { SlashCommand, CommandContext } from './types.js';
import { CommandKind } from './types.js';
import { MessageType } from '../types.js';
import * as os from 'node:os';
import * as path from 'node:path';
import { refreshServerHierarchicalMemory } from '@google/gemini-cli-core';
export function expandHomeDir(p: string): string {
if (!p) {
return '';
}
let expandedPath = p;
if (p.toLowerCase().startsWith('%userprofile%')) {
expandedPath = os.homedir() + p.substring('%userprofile%'.length);
} else if (p === '~' || p.startsWith('~/')) {
expandedPath = os.homedir() + p.substring(1);
}
return path.normalize(expandedPath);
}
import { expandHomeDir } from '../utils/directoryUtils.js';
export const directoryCommand: SlashCommand = {
name: 'directory',
@@ -4,32 +4,113 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import * as process from 'node:process';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { permissionsCommand } from './permissionsCommand.js';
import { type CommandContext, CommandKind } from './types.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
vi.mock('node:fs');
describe('permissionsCommand', () => {
let mockContext: CommandContext;
beforeEach(() => {
mockContext = createMockCommandContext();
vi.mocked(fs).statSync.mockReturnValue({
isDirectory: vi.fn(() => true),
} as unknown as fs.Stats);
});
afterEach(() => {
vi.restoreAllMocks();
});
it('should have the correct name and description', () => {
expect(permissionsCommand.name).toBe('permissions');
expect(permissionsCommand.description).toBe('Manage folder trust settings');
expect(permissionsCommand.description).toBe(
'Manage folder trust settings and other permissions',
);
});
it('should be a built-in command', () => {
expect(permissionsCommand.kind).toBe(CommandKind.BUILT_IN);
});
it('should return an action to open the permissions dialog', () => {
const actionResult = permissionsCommand.action?.(mockContext, '');
it('should have a trust subcommand', () => {
const trustCommand = permissionsCommand.subCommands?.find(
(cmd) => cmd.name === 'trust',
);
expect(trustCommand).toBeDefined();
expect(trustCommand?.name).toBe('trust');
expect(trustCommand?.description).toBe(
'Manage folder trust settings. Usage: /permissions trust [<directory-path>]',
);
expect(trustCommand?.kind).toBe(CommandKind.BUILT_IN);
});
it('should return an action to open the permissions dialog with a specified directory', () => {
const trustCommand = permissionsCommand.subCommands?.find(
(cmd) => cmd.name === 'trust',
);
const actionResult = trustCommand?.action?.(mockContext, '/test/dir');
expect(actionResult).toEqual({
type: 'dialog',
dialog: 'permissions',
props: {
targetDirectory: path.resolve('/test/dir'),
},
});
});
it('should return an action to open the permissions dialog with the current directory if no path is provided', () => {
const trustCommand = permissionsCommand.subCommands?.find(
(cmd) => cmd.name === 'trust',
);
const actionResult = trustCommand?.action?.(mockContext, '');
expect(actionResult).toEqual({
type: 'dialog',
dialog: 'permissions',
props: {
targetDirectory: process.cwd(),
},
});
});
it('should return an error message if the provided path does not exist', () => {
const trustCommand = permissionsCommand.subCommands?.find(
(cmd) => cmd.name === 'trust',
);
vi.mocked(fs).statSync.mockImplementation(() => {
throw new Error('ENOENT: no such file or directory');
});
const actionResult = trustCommand?.action?.(
mockContext,
'/nonexistent/dir',
);
expect(actionResult).toEqual({
type: 'message',
messageType: 'error',
content: `Error accessing path: ${path.resolve(
'/nonexistent/dir',
)}. ENOENT: no such file or directory`,
});
});
it('should return an error message if the provided path is not a directory', () => {
const trustCommand = permissionsCommand.subCommands?.find(
(cmd) => cmd.name === 'trust',
);
vi.mocked(fs).statSync.mockReturnValue({
isDirectory: vi.fn(() => false),
} as unknown as fs.Stats);
const actionResult = trustCommand?.action?.(mockContext, '/file/not/dir');
expect(actionResult).toEqual({
type: 'message',
messageType: 'error',
content: `Path is not a directory: ${path.resolve('/file/not/dir')}`,
});
});
});
@@ -4,15 +4,80 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type { OpenDialogActionReturn, SlashCommand } from './types.js';
import type {
OpenDialogActionReturn,
SlashCommand,
SlashCommandActionReturn,
} from './types.js';
import { CommandKind } from './types.js';
import * as process from 'node:process';
import * as path from 'node:path';
import * as fs from 'node:fs';
import { expandHomeDir } from '../utils/directoryUtils.js';
export const permissionsCommand: SlashCommand = {
name: 'permissions',
description: 'Manage folder trust settings',
description: 'Manage folder trust settings and other permissions',
kind: CommandKind.BUILT_IN,
action: (): OpenDialogActionReturn => ({
type: 'dialog',
dialog: 'permissions',
}),
subCommands: [
{
name: 'trust',
description:
'Manage folder trust settings. Usage: /permissions trust [<directory-path>]',
kind: CommandKind.BUILT_IN,
action: (context, input): SlashCommandActionReturn => {
const dirPath = input.trim();
let targetDirectory: string;
if (!dirPath) {
targetDirectory = process.cwd();
} else {
targetDirectory = path.resolve(expandHomeDir(dirPath));
}
try {
if (!fs.statSync(targetDirectory).isDirectory()) {
return {
type: 'message',
messageType: 'error',
content: `Path is not a directory: ${targetDirectory}`,
};
}
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return {
type: 'message',
messageType: 'error',
content: `Error accessing path: ${targetDirectory}. ${message}`,
};
}
return {
type: 'dialog',
dialog: 'permissions',
props: {
targetDirectory,
},
} as OpenDialogActionReturn;
},
},
],
action: (context, input): SlashCommandActionReturn => {
const parts = input.trim().split(' ');
const subcommand = parts[0];
if (!subcommand) {
return {
type: 'message',
messageType: 'error',
content: `Please provide a subcommand for /permissions. Usage: /permissions trust [<directory-path>]`,
};
}
return {
type: 'message',
messageType: 'error',
content: `Invalid subcommand for /permissions: ${subcommand}. Usage: /permissions trust [<directory-path>]`,
};
},
};
+1
View File
@@ -113,6 +113,7 @@ export interface MessageActionReturn {
*/
export interface OpenDialogActionReturn {
type: 'dialog';
props?: Record<string, unknown>;
dialog:
| 'help'