Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Changelog

All notable changes to the Augment Extension Auto-Installer will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- **Multi-Profile Support for Cursor IDE**: Automatic detection and installation of extensions to all Cursor profiles
- **Profile Detection System**: Comprehensive scanning of Cursor profile directories
- **Direct Extension Copying**: Robust method to copy extensions directly to profile directories when CLI methods fail
- **Profile Extensions.json Management**: Automatic updating of profile extension registries
- **Enhanced Logging**: Detailed logging for profile operations and installation status
- **Profile Installation Testing Tools**: Development utilities for testing profile detection and installation
- **Configuration Option**: `installInAllProfiles` setting to control multi-profile behavior

### Changed
- **Windows Task Scheduler**: Updated to use `--install-all --install-all-profiles` flags for comprehensive installation
- **IDE Manager**: Enhanced `installExtensionInAllIDEs()` method to handle Cursor profiles specifically
- **Installation Logic**: Improved fallback mechanisms for profile installation failures
- **Configuration**: Added `installInAllProfiles: true` to default configuration

### Fixed
- **Cursor Profile Isolation Issue**: Extensions now install to all Cursor profiles, not just the active one
- **Version Inconsistency**: All profiles now receive the same extension version during updates
- **Profile Detection**: Robust detection of active vs inactive profiles based on extensions.json presence

### Technical Details
- Added `getCursorProfiles()` method for profile discovery
- Added `copyExtensionToAllProfiles()` method for direct extension copying
- Added `updateProfileExtensionsJson()` method for profile registry management
- Added `copyDirectory()` utility for recursive directory copying
- Enhanced error handling and logging throughout profile operations

## [Previous Versions]
- Initial Windows Task Scheduler implementation
- Basic IDE detection and extension installation
- VS Code and Cursor support
- Automated update checking and installation
2 changes: 1 addition & 1 deletion Install-WindowsTask.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ function Install-ScheduledTask {
}

# Create the action
$actionArgs = "index.js"
$actionArgs = "index.js --install-all --install-all-profiles"
$action = New-ScheduledTaskAction -Execute $NodePath -Argument $actionArgs -WorkingDirectory $ScriptDir

# Create the trigger
Expand Down
8 changes: 8 additions & 0 deletions augment-code-auto-install.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}
3 changes: 2 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"idePreferences": {
"priority": ["cursor", "vscode"],
"installInAllIDEs": false,
"installInAllIDEs": true,
"installInAllProfiles": true,
"preferredIDE": "auto"
},
"updateSettings": {
Expand Down
286 changes: 274 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { execSync } = require('child_process');
const fetch = require('node-fetch');
const fs = require('fs');
const path = require('path');
const os = require('os');
const readline = require('readline');
const semver = require('semver');
const chalk = require('chalk');
Expand Down Expand Up @@ -140,32 +141,293 @@ class IDEManager {
if (!this.currentIDE) {
throw new Error('No IDE selected for installation');
}
const { config, command } = this.currentIDE;

const { ide, config, command } = this.currentIDE;
this.log(`Installing extension via ${config.name} CLI...`);

execSync(`${command} ${config.commands.installExtension} "${vsixPath}"`, {
stdio: 'inherit',
timeout: 30000
});

this.log(`Extension installed successfully in ${config.name}`, 'success');

if (ide === 'cursor') {
// For Cursor, check if we should install to all profiles
const installInAllProfiles = process.argv.includes('--install-all-profiles') ||
process.argv.includes('--install-all');

if (installInAllProfiles) {
await this.installExtensionInAllCursorProfiles(vsixPath, command, config);
} else {
execSync(`${command} ${config.commands.installExtension} "${vsixPath}"`, {
stdio: 'inherit',
timeout: 30000
});
this.log(`Extension installed successfully in ${config.name} (current profile only)`, 'success');
}
} else {
execSync(`${command} ${config.commands.installExtension} "${vsixPath}"`, {
stdio: 'inherit',
timeout: 30000
});
this.log(`Extension installed successfully in ${config.name}`, 'success');
}
}

async installExtensionInAllIDEs(vsixPath) {
this.log('Installing extension in all available IDEs...');

for (const { ide, config, command } of this.detectedIDEs) {
try {
this.log(`Installing in ${config.name}...`);

if (ide === 'cursor') {
// For Cursor, also install to all profiles
await this.installExtensionInAllCursorProfiles(vsixPath, command, config);
} else {
// For other IDEs, install normally
execSync(`${command} ${config.commands.installExtension} "${vsixPath}"`, {
stdio: 'inherit',
timeout: 30000
});
this.log(`โœ“ Installed in ${config.name}`, 'success');
}
} catch (error) {
this.log(`โœ— Failed to install in ${config.name}: ${error.message}`, 'error');
}
}
}

async installExtensionInAllCursorProfiles(vsixPath, command, config) {
try {
// Get all Cursor profiles
const profiles = await this.getCursorProfiles();

if (profiles.length === 0) {
this.log('No Cursor profiles found, installing to default profile only', 'warning');
execSync(`${command} ${config.commands.installExtension} "${vsixPath}"`, {
stdio: 'inherit',
timeout: 30000
});
this.log(`โœ“ Installed in ${config.name} (default profile)`, 'success');
return;
}

this.log(`Found ${profiles.length} Cursor profile(s), installing to all...`);

// First, install to default profile to get the extension files
try {
this.log('Installing to Cursor default profile...');
execSync(`${command} ${config.commands.installExtension} "${vsixPath}"`, {
stdio: 'inherit',
timeout: 30000
});
this.log(`โœ“ Installed in ${config.name}`, 'success');
this.log(`โœ“ Installed in Cursor default profile`, 'success');
} catch (error) {
this.log(`โœ— Failed to install in ${config.name}: ${error.message}`, 'error');
this.log(`โœ— Failed to install in Cursor default profile: ${error.message}`, 'error');
throw error; // Can't proceed without a successful installation
}

// Now copy the extension to all profiles
await this.copyExtensionToAllProfiles(profiles);

} catch (error) {
this.log(`Error managing Cursor profiles: ${error.message}`, 'error');
// Fallback to default installation
execSync(`${command} ${config.commands.installExtension} "${vsixPath}"`, {
stdio: 'inherit',
timeout: 30000
});
this.log(`โœ“ Installed in ${config.name} (fallback to default)`, 'success');
}
}

async copyExtensionToAllProfiles(profiles) {
try {
const os = require('os');

// Find the Augment extension in the default extensions directory
const defaultExtensionsPath = path.join(os.homedir(), '.cursor', 'extensions');

if (!fs.existsSync(defaultExtensionsPath)) {
this.log('Default extensions directory not found', 'error');
return;
}

// Find the Augment extension directory
const extensionDirs = fs.readdirSync(defaultExtensionsPath)
.filter(dir => dir.startsWith('augment.vscode-augment-'));

if (extensionDirs.length === 0) {
this.log('Augment extension not found in default extensions directory', 'error');
return;
}

// Use the latest version (should be the one we just installed)
const latestExtensionDir = extensionDirs.sort().pop();
const sourceExtensionPath = path.join(defaultExtensionsPath, latestExtensionDir);

this.log(`Found Augment extension: ${latestExtensionDir}`);

// Copy to each profile
for (const profile of profiles) {
try {
const profileExtensionsPath = path.join(profile.path, '..', '..', '..', '.cursor', 'extensions');

// Create extensions directory if it doesn't exist
if (!fs.existsSync(profileExtensionsPath)) {
fs.mkdirSync(profileExtensionsPath, { recursive: true });
}

const targetExtensionPath = path.join(profileExtensionsPath, latestExtensionDir);

// Remove old version if it exists
if (fs.existsSync(targetExtensionPath)) {
fs.rmSync(targetExtensionPath, { recursive: true, force: true });
}

// Copy the extension
await this.copyDirectory(sourceExtensionPath, targetExtensionPath);

// Update the profile's extensions.json
await this.updateProfileExtensionsJson(profile, latestExtensionDir);

this.log(`โœ“ Copied Augment extension to profile: ${profile.name}`, 'success');

} catch (error) {
this.log(`โœ— Failed to copy extension to profile ${profile.name}: ${error.message}`, 'error');
}
}

} catch (error) {
this.log(`Error copying extension to profiles: ${error.message}`, 'error');
}
}

async copyDirectory(source, target) {
if (!fs.existsSync(source)) {
throw new Error(`Source directory does not exist: ${source}`);
}

// Create target directory
fs.mkdirSync(target, { recursive: true });

// Copy all files and subdirectories
const items = fs.readdirSync(source, { withFileTypes: true });

for (const item of items) {
const sourcePath = path.join(source, item.name);
const targetPath = path.join(target, item.name);

if (item.isDirectory()) {
await this.copyDirectory(sourcePath, targetPath);
} else {
fs.copyFileSync(sourcePath, targetPath);
}
}
}

async updateProfileExtensionsJson(profile, extensionDirName) {
try {
const extensionsJsonPath = path.join(profile.path, 'extensions.json');

if (!fs.existsSync(extensionsJsonPath)) {
// Create a new extensions.json if it doesn't exist
const newExtensionsData = [];
fs.writeFileSync(extensionsJsonPath, JSON.stringify(newExtensionsData, null, 2));
return;
}

// Read existing extensions.json
const extensionsData = JSON.parse(fs.readFileSync(extensionsJsonPath, 'utf8'));

// Remove any existing Augment extension entries
const filteredExtensions = extensionsData.filter(ext =>
!ext.identifier || !ext.identifier.id || !ext.identifier.id.includes('augment.vscode-augment')
);

// Add the new Augment extension entry
const versionMatch = extensionDirName.match(/augment\.vscode-augment-(.+)$/);
const version = versionMatch ? versionMatch[1] : '0.551.0';

const newExtensionEntry = {
identifier: {
id: "augment.vscode-augment",
uuid: "fc0e137d-e132-47ed-9455-c4636fa5b897"
},
version: version,
location: {
$mid: 1,
path: `/c:/Users/${os.userInfo().username}/.cursor/extensions/${extensionDirName}`,
scheme: "file"
},
relativeLocation: extensionDirName,
metadata: {
isApplicationScoped: false,
isMachineScoped: false,
isBuiltin: false,
installedTimestamp: Date.now(),
pinned: false,
source: "gallery",
id: "fc0e137d-e132-47ed-9455-c4636fa5b897",
publisherId: "7814b14b-491a-4e83-83ac-9222fa835050",
publisherDisplayName: "augment",
targetPlatform: "undefined",
updated: true,
private: false,
isPreReleaseVersion: true,
hasPreReleaseVersion: true,
preRelease: true
}
};

filteredExtensions.push(newExtensionEntry);

// Write back to extensions.json
fs.writeFileSync(extensionsJsonPath, JSON.stringify(filteredExtensions, null, 2));

} catch (error) {
this.log(`Error updating extensions.json for profile ${profile.name}: ${error.message}`, 'error');
}
}

async getCursorProfiles() {
try {
const os = require('os');
const cursorProfilesPath = path.join(os.homedir(), 'AppData', 'Roaming', 'Cursor', 'User', 'profiles');

if (!fs.existsSync(cursorProfilesPath)) {
this.log('Cursor profiles directory not found', 'warning');
return [];
}

const profileDirs = fs.readdirSync(cursorProfilesPath, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);

const profiles = [];

for (const profileDir of profileDirs) {
const profilePath = path.join(cursorProfilesPath, profileDir);
const extensionsJsonPath = path.join(profilePath, 'extensions.json');

// Check if this profile has extensions (indicating it's an active profile)
if (fs.existsSync(extensionsJsonPath)) {
profiles.push({
name: profileDir,
path: profilePath,
hasAugment: await this.profileHasAugment(extensionsJsonPath)
});
}
}

return profiles;
} catch (error) {
this.log(`Error getting Cursor profiles: ${error.message}`, 'error');
return [];
}
}

async profileHasAugment(extensionsJsonPath) {
try {
const extensionsData = fs.readFileSync(extensionsJsonPath, 'utf8');
return extensionsData.includes('augment.vscode-augment');
} catch (error) {
return false;
}
}

Expand Down
Loading