feat(server): Implement Cursor CLI permissions management

Added new routes and handlers for managing Cursor CLI permissions, including:
- GET /api/setup/cursor-permissions: Retrieve current permissions configuration and available profiles.
- POST /api/setup/cursor-permissions/profile: Apply a predefined permission profile (global or project).
- POST /api/setup/cursor-permissions/custom: Set custom permissions for a project.
- DELETE /api/setup/cursor-permissions: Delete project-level permissions, reverting to global settings.
- GET /api/setup/cursor-permissions/example: Provide an example config file for a specified profile.

Also introduced a new service for handling Cursor CLI configuration files and updated the UI to support permissions management.

Affected files:
- Added new routes in index.ts and cursor-config.ts
- Created cursor-config-service.ts for permissions management logic
- Updated UI components to display and manage permissions

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Kacper
2025-12-30 17:08:18 +01:00
parent 078ab943a8
commit dac916496c
6 changed files with 1121 additions and 5 deletions

View File

@@ -17,6 +17,11 @@ import {
createGetCursorConfigHandler,
createSetCursorDefaultModelHandler,
createSetCursorModelsHandler,
createGetCursorPermissionsHandler,
createApplyPermissionProfileHandler,
createSetCustomPermissionsHandler,
createDeleteProjectPermissionsHandler,
createGetExampleConfigHandler,
} from './routes/cursor-config.js';
export function createSetupRoutes(): Router {
@@ -38,5 +43,12 @@ export function createSetupRoutes(): Router {
router.post('/cursor-config/default-model', createSetCursorDefaultModelHandler());
router.post('/cursor-config/models', createSetCursorModelsHandler());
// Cursor CLI Permissions routes
router.get('/cursor-permissions', createGetCursorPermissionsHandler());
router.post('/cursor-permissions/profile', createApplyPermissionProfileHandler());
router.post('/cursor-permissions/custom', createSetCustomPermissionsHandler());
router.delete('/cursor-permissions', createDeleteProjectPermissionsHandler());
router.get('/cursor-permissions/example', createGetExampleConfigHandler());
return router;
}

View File

@@ -5,11 +5,36 @@
* - GET /api/setup/cursor-config - Get current configuration
* - POST /api/setup/cursor-config/default-model - Set default model
* - POST /api/setup/cursor-config/models - Set enabled models
*
* Cursor CLI Permissions endpoints:
* - GET /api/setup/cursor-permissions - Get permissions config
* - POST /api/setup/cursor-permissions/profile - Apply a permission profile
* - POST /api/setup/cursor-permissions/custom - Set custom permissions
* - DELETE /api/setup/cursor-permissions - Delete project permissions (use global)
*/
import type { Request, Response } from 'express';
import { CursorConfigManager } from '../../../providers/cursor-config-manager.js';
import { CURSOR_MODEL_MAP, type CursorModelId } from '@automaker/types';
import {
CURSOR_MODEL_MAP,
CURSOR_PERMISSION_PROFILES,
type CursorModelId,
type CursorPermissionProfile,
type CursorCliPermissions,
} from '@automaker/types';
import {
readGlobalConfig,
readProjectConfig,
getEffectivePermissions,
applyProfileToProject,
applyProfileGlobally,
writeProjectConfig,
deleteProjectConfig,
detectProfile,
hasProjectConfig,
getAvailableProfiles,
generateExampleConfig,
} from '../../../services/cursor-config-service.js';
import { getErrorMessage, logError } from '../common.js';
/**
@@ -134,3 +159,209 @@ export function createSetCursorModelsHandler() {
}
};
}
// =============================================================================
// Cursor CLI Permissions Handlers
// =============================================================================
/**
* Creates handler for GET /api/setup/cursor-permissions
* Returns current permissions configuration and available profiles
*/
export function createGetCursorPermissionsHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
const projectPath = req.query.projectPath as string | undefined;
// Get global config
const globalConfig = await readGlobalConfig();
// Get project config if path provided
const projectConfig = projectPath ? await readProjectConfig(projectPath) : null;
// Get effective permissions
const effectivePermissions = await getEffectivePermissions(projectPath);
// Detect which profile is active
const activeProfile = detectProfile(effectivePermissions);
// Check if project has its own config
const hasProject = projectPath ? await hasProjectConfig(projectPath) : false;
res.json({
success: true,
globalPermissions: globalConfig?.permissions || null,
projectPermissions: projectConfig?.permissions || null,
effectivePermissions,
activeProfile,
hasProjectConfig: hasProject,
availableProfiles: getAvailableProfiles(),
});
} catch (error) {
logError(error, 'Get Cursor permissions failed');
res.status(500).json({
success: false,
error: getErrorMessage(error),
});
}
};
}
/**
* Creates handler for POST /api/setup/cursor-permissions/profile
* Applies a predefined permission profile
*/
export function createApplyPermissionProfileHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
const { profileId, projectPath, scope } = req.body as {
profileId: CursorPermissionProfile;
projectPath?: string;
scope: 'global' | 'project';
};
// Validate profile
const validProfiles = CURSOR_PERMISSION_PROFILES.map((p) => p.id);
if (!validProfiles.includes(profileId)) {
res.status(400).json({
success: false,
error: `Invalid profile. Valid profiles: ${validProfiles.join(', ')}`,
});
return;
}
if (scope === 'project') {
if (!projectPath) {
res.status(400).json({
success: false,
error: 'projectPath is required for project scope',
});
return;
}
await applyProfileToProject(projectPath, profileId);
} else {
await applyProfileGlobally(profileId);
}
res.json({
success: true,
message: `Applied "${profileId}" profile to ${scope}`,
scope,
profileId,
});
} catch (error) {
logError(error, 'Apply Cursor permission profile failed');
res.status(500).json({
success: false,
error: getErrorMessage(error),
});
}
};
}
/**
* Creates handler for POST /api/setup/cursor-permissions/custom
* Sets custom permissions for a project
*/
export function createSetCustomPermissionsHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath, permissions } = req.body as {
projectPath: string;
permissions: CursorCliPermissions;
};
if (!projectPath) {
res.status(400).json({
success: false,
error: 'projectPath is required',
});
return;
}
if (!permissions || !Array.isArray(permissions.allow) || !Array.isArray(permissions.deny)) {
res.status(400).json({
success: false,
error: 'permissions must have allow and deny arrays',
});
return;
}
await writeProjectConfig(projectPath, {
version: 1,
permissions,
});
res.json({
success: true,
message: 'Custom permissions saved',
permissions,
});
} catch (error) {
logError(error, 'Set custom Cursor permissions failed');
res.status(500).json({
success: false,
error: getErrorMessage(error),
});
}
};
}
/**
* Creates handler for DELETE /api/setup/cursor-permissions
* Deletes project-level permissions (falls back to global)
*/
export function createDeleteProjectPermissionsHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
const projectPath = req.query.projectPath as string;
if (!projectPath) {
res.status(400).json({
success: false,
error: 'projectPath query parameter is required',
});
return;
}
await deleteProjectConfig(projectPath);
res.json({
success: true,
message: 'Project permissions deleted, using global config',
});
} catch (error) {
logError(error, 'Delete Cursor project permissions failed');
res.status(500).json({
success: false,
error: getErrorMessage(error),
});
}
};
}
/**
* Creates handler for GET /api/setup/cursor-permissions/example
* Returns an example config file for a profile
*/
export function createGetExampleConfigHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
const profileId = (req.query.profileId as CursorPermissionProfile) || 'development';
const exampleConfig = generateExampleConfig(profileId);
res.json({
success: true,
profileId,
config: exampleConfig,
});
} catch (error) {
logError(error, 'Get example Cursor config failed');
res.status(500).json({
success: false,
error: getErrorMessage(error),
});
}
};
}