Skip to content

Instantly share code, notes, and snippets.

@SoMaCoSF
Created January 5, 2025 14:57
Show Gist options
  • Save SoMaCoSF/973441179e4f58014e19ef29dfca5816 to your computer and use it in GitHub Desktop.
Save SoMaCoSF/973441179e4f58014e19ef29dfca5816 to your computer and use it in GitHub Desktop.
Cursor Settings Sync Extension

Cursor Settings Sync Extension

A VSCode extension for Cursor that enables seamless synchronization of settings, keybindings, and extensions across multiple workspaces and machines.

Features

  • Sync settings across multiple Cursor installations
  • Export/Import functionality
  • Automatic workspace state preservation
  • Keybinding synchronization
  • Extension management
  • Real-time sync capabilities

Installation

  1. Clone this repository:
git clone https://github.com/yourusername/cursor-settings-sync.git
cd cursor-settings-sync
  1. Install dependencies:
npm install
  1. Build the extension:
npm run compile
  1. Package the extension:
vsce package
  1. Install in Cursor:
code --install-extension cursor-settings-sync-1.0.0.vsix

Alternatively, use the provided PowerShell script:

.\scripts\install.ps1

Usage

Keyboard Shortcuts

  • Ctrl+Alt+E (Windows) / Cmd+Alt+E (Mac) - Export settings
  • Ctrl+Alt+M (Windows) / Cmd+Alt+M (Mac) - Import settings
  • Ctrl+Alt+S (Windows) / Cmd+Alt+S (Mac) - Sync settings

Command Palette

Open the command palette (Ctrl+Shift+P / Cmd+Shift+P) and type:

  • Cursor: Export Settings
  • Cursor: Import Settings
  • Cursor: Sync Settings

Programmatic Usage

await vscode.commands.executeCommand("cursor.exportSettings");
await vscode.commands.executeCommand("cursor.importSettings");
await vscode.commands.executeCommand("cursor.syncSettings");

Configuration

Settings are stored in ~/.cursor-global/cursor-settings.json with the following structure:

{
  "settings": {
    "cursor.ai": {
      "logging": {
        "enabled": true,
        "level": "debug"
      },
      "chat": {
        "saveHistory": true
      }
    }
  },
  "keybindings": [
    {
      "key": "ctrl+alt+i",
      "command": "cursor.wrapWithIgnore"
    }
  ],
  "extensions": ["extension.id1", "extension.id2"],
  "workspaceState": {
    // Workspace-specific settings
  }
}

Development

  1. Open in VSCode:
code cursor-settings-sync
  1. Install development dependencies:
.\scripts\setup-dev.ps1
  1. Press F5 to start debugging

Contributing

  1. Fork the repository
  2. Create your feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create a Pull Request

License

MIT

Author

Your Name

import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import * as vscode from "vscode";
interface CursorSettings {
settings: any;
keybindings: any;
extensions: string[];
workspaceState: any;
}
export function activate(context: vscode.ExtensionContext) {
// Original ignore/focus commands
let ignoreDisposable = vscode.commands.registerCommand(
"cursor.wrapWithIgnore",
() => {
const editor = vscode.window.activeTextEditor;
if (editor) {
const selection = editor.selection;
const text = editor.document.getText(selection);
editor.edit((editBuilder) => {
editBuilder.replace(
selection,
`<!-- cursor-ignore -->\n${text}\n<!-- cursor-ignore-end -->`
);
});
}
}
);
let focusDisposable = vscode.commands.registerCommand(
"cursor.wrapWithFocus",
() => {
const editor = vscode.window.activeTextEditor;
if (editor) {
const selection = editor.selection;
const text = editor.document.getText(selection);
editor.edit((editBuilder) => {
editBuilder.replace(
selection,
`<!-- cursor-focus -->\n${text}\n<!-- cursor-focus-end -->`
);
});
}
}
);
// New settings sync commands
let exportDisposable = vscode.commands.registerCommand(
"cursor.exportSettings",
async () => {
try {
await exportCursorSettings();
vscode.window.showInformationMessage(
"Cursor settings exported successfully"
);
} catch (error) {
vscode.window.showErrorMessage(`Failed to export settings: ${error}`);
}
}
);
let importDisposable = vscode.commands.registerCommand(
"cursor.importSettings",
async () => {
try {
await importCursorSettings();
vscode.window.showInformationMessage(
"Cursor settings imported successfully"
);
} catch (error) {
vscode.window.showErrorMessage(`Failed to import settings: ${error}`);
}
}
);
let syncDisposable = vscode.commands.registerCommand(
"cursor.syncSettings",
async () => {
try {
await exportCursorSettings();
await importCursorSettings();
vscode.window.showInformationMessage(
"Cursor settings synced successfully"
);
} catch (error) {
vscode.window.showErrorMessage(`Failed to sync settings: ${error}`);
}
}
);
context.subscriptions.push(
ignoreDisposable,
focusDisposable,
exportDisposable,
importDisposable,
syncDisposable
);
}
async function exportCursorSettings(): Promise<void> {
const globalStoragePath = path.join(os.homedir(), ".cursor-global");
// Ensure global storage exists
if (!fs.existsSync(globalStoragePath)) {
fs.mkdirSync(globalStoragePath, { recursive: true });
}
const settings: CursorSettings = {
settings: vscode.workspace.getConfiguration("cursor"),
keybindings: await getKeybindings(),
extensions: await getInstalledExtensions(),
workspaceState: await getWorkspaceState(),
};
fs.writeFileSync(
path.join(globalStoragePath, "cursor-settings.json"),
JSON.stringify(settings, null, 2)
);
}
async function importCursorSettings(): Promise<void> {
const globalStoragePath = path.join(os.homedir(), ".cursor-global");
const settingsPath = path.join(globalStoragePath, "cursor-settings.json");
if (!fs.existsSync(settingsPath)) {
throw new Error("No settings file found");
}
const settings: CursorSettings = JSON.parse(
fs.readFileSync(settingsPath, "utf8")
);
// Apply settings
await vscode.workspace
.getConfiguration("cursor")
.update("", settings.settings, true);
await setKeybindings(settings.keybindings);
await installExtensions(settings.extensions);
await setWorkspaceState(settings.workspaceState);
}
async function getKeybindings(): Promise<any> {
const keybindingsPath = path.join(
os.homedir(),
".cursor",
"User",
"keybindings.json"
);
if (fs.existsSync(keybindingsPath)) {
return JSON.parse(fs.readFileSync(keybindingsPath, "utf8"));
}
return {};
}
async function setKeybindings(keybindings: any): Promise<void> {
const keybindingsPath = path.join(
os.homedir(),
".cursor",
"User",
"keybindings.json"
);
fs.writeFileSync(keybindingsPath, JSON.stringify(keybindings, null, 2));
}
async function getInstalledExtensions(): Promise<string[]> {
return vscode.extensions.all
.filter((ext) => !ext.packageJSON.isBuiltin)
.map((ext) => ext.id);
}
async function installExtensions(extensions: string[]): Promise<void> {
for (const ext of extensions) {
try {
await vscode.commands.executeCommand(
"workbench.extensions.installExtension",
ext
);
} catch (error) {
console.error(`Failed to install extension ${ext}:`, error);
}
}
}
async function getWorkspaceState(): Promise<any> {
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
if (!workspacePath) return {};
const statePath = path.join(workspacePath, ".cursor", "workspace-state.json");
if (fs.existsSync(statePath)) {
return JSON.parse(fs.readFileSync(statePath, "utf8"));
}
return {};
}
async function setWorkspaceState(state: any): Promise<void> {
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
if (!workspacePath) return;
const cursorPath = path.join(workspacePath, ".cursor");
if (!fs.existsSync(cursorPath)) {
fs.mkdirSync(cursorPath, { recursive: true });
}
fs.writeFileSync(
path.join(cursorPath, "workspace-state.json"),
JSON.stringify(state, null, 2)
);
}
<#
.SYNOPSIS
Installs the Cursor Settings Sync extension
.DESCRIPTION
Builds and installs the Cursor Settings Sync extension in the current Cursor installation
.NOTES
Version: 1.0
Author: Your Name
Last Updated: 2024-01-04
#>
[CmdletBinding()]
param()
$ErrorActionPreference = "Stop"
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
$RootPath = Split-Path -Parent $ScriptPath
# Verify Node.js installation
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
Write-Error "Node.js is required but not installed. Please install Node.js first."
exit 1
}
# Verify npm installation
if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
Write-Error "npm is required but not installed. Please install npm first."
exit 1
}
# Verify vsce installation
if (-not (Get-Command vsce -ErrorAction SilentlyContinue)) {
Write-Host "Installing vsce globally..."
npm install -g vsce
}
# Install dependencies
Write-Host "Installing dependencies..."
Push-Location $RootPath
try {
npm install
# Build extension
Write-Host "Building extension..."
npm run compile
# Package extension
Write-Host "Packaging extension..."
vsce package
# Get the vsix file
$vsixFile = Get-ChildItem -Path $RootPath -Filter "*.vsix" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if (-not $vsixFile) {
throw "Failed to find .vsix file"
}
# Install extension in Cursor
Write-Host "Installing extension in Cursor..."
& code --install-extension $vsixFile.FullName
Write-Host "Installation complete!" -ForegroundColor Green
Write-Host "Restart Cursor to activate the extension."
}
catch {
Write-Error "Installation failed: $_"
exit 1
}
finally {
Pop-Location
}
{
"name": "cursor-settings-sync",
"displayName": "Cursor Settings Sync",
"description": "Sync Cursor settings, keybindings, and extensions across workspaces",
"version": "1.0.0",
"engines": {
"vscode": "^1.60.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onCommand:cursor.wrapWithIgnore",
"onCommand:cursor.wrapWithFocus",
"onCommand:cursor.exportSettings",
"onCommand:cursor.importSettings",
"onCommand:cursor.syncSettings"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "cursor.wrapWithIgnore",
"title": "Cursor: Wrap with Ignore"
},
{
"command": "cursor.wrapWithFocus",
"title": "Cursor: Wrap with Focus"
},
{
"command": "cursor.exportSettings",
"title": "Cursor: Export Settings"
},
{
"command": "cursor.importSettings",
"title": "Cursor: Import Settings"
},
{
"command": "cursor.syncSettings",
"title": "Cursor: Sync Settings"
}
],
"keybindings": [
{
"command": "cursor.wrapWithIgnore",
"key": "ctrl+alt+i",
"mac": "cmd+alt+i",
"when": "editorTextFocus"
},
{
"command": "cursor.wrapWithFocus",
"key": "ctrl+alt+f",
"mac": "cmd+alt+f",
"when": "editorTextFocus"
},
{
"command": "cursor.exportSettings",
"key": "ctrl+alt+e",
"mac": "cmd+alt+e"
},
{
"command": "cursor.importSettings",
"key": "ctrl+alt+m",
"mac": "cmd+alt+m"
},
{
"command": "cursor.syncSettings",
"key": "ctrl+alt+s",
"mac": "cmd+alt+s"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"lint": "eslint src --ext ts",
"test": "node ./out/test/runTest.js"
},
"devDependencies": {
"@types/vscode": "^1.60.0",
"@types/glob": "^7.1.3",
"@types/mocha": "^8.2.2",
"@types/node": "14.x",
"eslint": "^7.27.0",
"@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
"glob": "^7.1.7",
"mocha": "^8.4.0",
"typescript": "^4.3.2",
"vscode-test": "^1.5.2"
}
}
<#
.SYNOPSIS
Sets up the development environment for Cursor Settings Sync extension
.DESCRIPTION
Installs all necessary development dependencies and configures the development environment
.NOTES
Version: 1.0
Author: Your Name
Last Updated: 2024-01-04
#>
[CmdletBinding()]
param()
$ErrorActionPreference = "Stop"
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
$RootPath = Split-Path -Parent $ScriptPath
# Verify development tools
$requiredTools = @(
@{Name = "node"; Message = "Node.js is required for development" },
@{Name = "npm"; Message = "npm is required for development" },
@{Name = "git"; Message = "git is required for version control" }
)
foreach ($tool in $requiredTools) {
if (-not (Get-Command $tool.Name -ErrorAction SilentlyContinue)) {
Write-Error $tool.Message
exit 1
}
}
# Install global development tools
$globalTools = @(
@{Name = "typescript"; Package = "typescript" },
@{Name = "vsce"; Package = "vsce" },
@{Name = "eslint"; Package = "eslint" }
)
foreach ($tool in $globalTools) {
if (-not (Get-Command $tool.Name -ErrorAction SilentlyContinue)) {
Write-Host "Installing $($tool.Name) globally..."
npm install -g $tool.Package
}
}
# Setup development environment
Push-Location $RootPath
try {
# Install dependencies
Write-Host "Installing project dependencies..."
npm install
# Create .vscode directory if it doesn't exist
$vscodePath = Join-Path $RootPath ".vscode"
if (-not (Test-Path $vscodePath)) {
New-Item -ItemType Directory -Path $vscodePath | Out-Null
}
# Create launch.json
$launchConfig = @{
version = "0.2.0"
configurations = @(
@{
name = "Run Extension"
type = "extensionHost"
request = "launch"
args = @(
"--extensionDevelopmentPath=`${workspaceFolder}"
)
outFiles = @(
"`${workspaceFolder}/out/**/*.js"
)
preLaunchTask = "npm: watch"
}
)
}
$launchPath = Join-Path $vscodePath "launch.json"
$launchConfig | ConvertTo-Json -Depth 10 | Set-Content $launchPath
# Create tasks.json
$tasksConfig = @{
version = "2.0.0"
tasks = @(
@{
type = "npm"
script = "watch"
problemMatcher = @(
"`$tsc-watch"
)
isBackground = $true
presentation = @{
reveal = "never"
}
group = @{
kind = "build"
isDefault = $true
}
}
)
}
$tasksPath = Join-Path $vscodePath "tasks.json"
$tasksConfig | ConvertTo-Json -Depth 10 | Set-Content $tasksPath
# Initialize git if not already initialized
if (-not (Test-Path (Join-Path $RootPath ".git"))) {
Write-Host "Initializing git repository..."
git init
# Create .gitignore
@"
node_modules/
out/
*.vsix
.DS_Store
Thumbs.db
"@ | Set-Content (Join-Path $RootPath ".gitignore")
git add .
git commit -m "Initial commit"
}
Write-Host "Development environment setup complete!" -ForegroundColor Green
Write-Host "You can now open the project in VSCode and start debugging."
}
catch {
Write-Error "Setup failed: $_"
exit 1
}
finally {
Pop-Location
}
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "out",
"lib": ["ES2020"],
"sourceMap": true,
"rootDir": "src",
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"esModuleInterop": true
},
"exclude": ["node_modules", ".vscode-test"]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment