Skip to content

Instantly share code, notes, and snippets.

@zackiles
Created June 8, 2025 08:55
Show Gist options
  • Save zackiles/c4734c1d18eadee34337545bee392d20 to your computer and use it in GitHub Desktop.
Save zackiles/c4734c1d18eadee34337545bee392d20 to your computer and use it in GitHub Desktop.
Runs a Cursor Dev Container (In Deno and Node)
#!/usr/bin/env deno run --allow-run --allow-read --allow-env
/**
* @module run-cursor-dev-container
* @fileoverview Generic Cursor Dev Container Launcher
*
* Automates the process of launching Cursor directly into any dev container,
* bypassing the need for manual "Reopen in Container" command palette actions.
*
* @description This script manages Docker container lifecycle and constructs the proper
* vscode-remote:// URI format required by Cursor's dev container extension. Key differences
* from VS Code include specific JSON structure requirements and Docker context settings
* for cross-platform Docker Desktop integration.
*
* KEY DIFFERENCES FROM VS CODE:
* 1. Cursor uses the same dev-container URI format as VS Code, but requires specific
* JSON structure and Docker context settings for cross-platform Docker Desktop
* 2. The --folder-uri flag must be used with a properly constructed vscode-remote:// URI
* 3. Docker context varies by platform: desktop-linux (macOS), default (Linux), desktop-windows (Windows)
*
* @author Zachary Iles
* @version 0.0.1
* @since 2025
* @requires deno
* @requires docker
* @requires cursor
*
* @example
* ```bash
* # Use current directory with default settings
* deno run -A run-cursor-dev-container-deno.ts
*
* # Specify workspace and container name
* deno run -A run-cursor-dev-container-deno.ts --workspace /path/to/project --name my-container
*
* # Use custom devcontainer path
* deno run -A run-cursor-dev-container-deno.ts --devcontainer-path .devcontainer-custom
* ```
*
* @see ~/.cursor/extensions/ms-vscode-remote.remote-containers-0.394.0/dist/extension.js
* Contains URI construction logic around lines 50000-60000, including Buffer.from(configJSON,"utf8").toString("hex") pattern
* @see ~/.cursor/extensions/ms-vscode-remote.remote-containers-0.394.0/dist/common/uri.js
* Authority type definitions (dev-container, attached-container, k8s-container) and URI parsing functions
* @see ~/.cursor/extensions/ms-vscode-remote.remote-containers-0.394.0/dist/common/containerConfiguration.js
* JSON config structure requirements for container settings and Docker context handling
*/
import * as path from 'jsr:@std/path'
import { parseArgs } from 'jsr:@std/cli'
import { parse as parseJsonc } from 'jsr:@std/jsonc'
interface DevContainerConfig {
name?: string
workspaceFolder?: string
workspaceMount?: string
dockerFile?: string
image?: string
build?: {
dockerfile?: string
context?: string
}
}
interface CliArgs {
workspace: string
devcontainerPath: string
containerName?: string | undefined
help: boolean
verbose: boolean
reload: boolean
}
interface RunNewContainerOptions {
containerName: string
config: DevContainerConfig
workspacePath: string
log: (message: string) => void
}
interface StartContainerOptions extends RunNewContainerOptions {
devcontainerPath: string
}
function showHelp() {
console.log(`
Generic Cursor Dev Container Launcher
USAGE:
deno run -A run.ts [OPTIONS]
OPTIONS:
-w, --workspace <PATH> Workspace directory path (default: current directory)
-d, --devcontainer-path <PATH> Path to devcontainer directory (default: .devcontainer)
-n, --name <NAME> Override container name
-v, --verbose Show detailed output during execution
--no-reload Don't kill existing Cursor instances after launching container
-h, --help Show this help message
EXAMPLES:
deno run -A run.ts (assume .devcontainer in current directory with Dockerfile + devcontainer.json)
deno run -A run.ts --workspace /path/to/project
deno run -A run.ts --name my-custom-container --devcontainer-path .devcontainer-prod
deno run -A run.ts --verbose
`)
}
function createLogger(verbose: boolean) {
return function log(message: string) {
if (verbose) {
console.log(message)
}
}
}
async function run(cmd: string, args: string[] = []) {
const command = new Deno.Command(cmd, {
args,
stdout: 'piped',
stderr: 'piped',
})
const { success, stdout, stderr } = await command.output()
const decoder = new TextDecoder()
return {
success,
stdout: decoder.decode(stdout).trim(),
stderr: decoder.decode(stderr).trim(),
}
}
async function readDevContainerConfig(
configPath: string,
): Promise<DevContainerConfig> {
try {
const configFile = await Deno.readTextFile(configPath)
return parseJsonc(configFile) as DevContainerConfig
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
throw new Error(`Failed to read devcontainer.json: ${message}`)
}
}
function detectPlatform(): string {
const os = Deno.build.os
switch (os) {
case 'darwin':
return 'desktop-linux'
case 'windows':
return 'desktop-windows'
case 'linux':
return 'default'
default:
return 'default'
}
}
function generateContainerName(
config: DevContainerConfig,
workspacePath: string,
override?: string,
): string {
if (override) return override
if (config.name) return config.name
const workspaceName = path.basename(workspacePath) || 'devcontainer'
return `${workspaceName}-devcontainer`
}
/**
* Checks if a container exists (running or stopped).
* @param containerName The name of the container to check.
* @returns The status of the container.
* @description We need to ensure the container is running before launching Cursor
* because Cursor's dev container extension expects an existing container, unlike VS Code
* which can build containers on-demand more reliably.
*/
async function getContainerStatus(containerName: string) {
const { stdout } = await run('docker', [
'ps',
'-a',
'--filter',
`name=${containerName}`,
'--format',
'{{.Status}}',
])
return stdout
}
async function buildContainer(
containerName: string,
devcontainerPath: string,
config: DevContainerConfig,
) {
const buildArgs: string[] = ['build', '-t', containerName.toLowerCase()]
if (config.build?.dockerfile || config.dockerFile) {
const dockerfile = config.build?.dockerfile || config.dockerFile ||
'Dockerfile'
buildArgs.push('-f', path.join(devcontainerPath, dockerfile))
}
const context = config.build?.context || devcontainerPath
buildArgs.push(context)
const result = await run('docker', buildArgs)
if (!result.success) {
throw new Error(`Build failed: ${result.stderr}`)
}
}
async function startExistingContainer(
containerName: string,
log: (message: string) => void,
) {
log('Starting existing container...')
const result = await run('docker', ['start', containerName])
if (!result.success) {
throw new Error(`Start failed: ${result.stderr}`)
}
}
async function runNewContainer({
containerName,
config,
workspacePath,
log,
}: RunNewContainerOptions) {
log('Running new container...')
const runArgs = ['run', '-d', '--name', containerName]
if (config.workspaceMount) {
const mountParts = Object.fromEntries(
config.workspaceMount.split(',').map((part) => part.split('=')),
) as Record<string, string>
const source = mountParts.source?.replace(
'${localWorkspaceFolder}',
workspacePath,
)
const target = mountParts.target
if (source && target) {
runArgs.push('-v', `${source}:${target}`)
} else {
log('Warning: could not parse workspaceMount string, using default.')
const workspaceFolder = config.workspaceFolder || '/workspace'
runArgs.push('-v', `${workspacePath}:${workspaceFolder}`)
}
} else {
const workspaceFolder = config.workspaceFolder || '/workspace'
runArgs.push('-v', `${workspacePath}:${workspaceFolder}`)
}
runArgs.push(containerName.toLowerCase())
runArgs.push('sleep', 'infinity')
const result = await run('docker', runArgs)
if (!result.success) {
throw new Error(`Run failed: ${result.stderr}`)
}
}
async function startContainer({
containerName,
devcontainerPath,
config,
workspacePath,
log,
}: StartContainerOptions) {
const status = await getContainerStatus(containerName)
if (!status) {
const runOptions = { containerName, config, workspacePath, log }
if (config.image) {
await runNewContainer(runOptions)
} else {
await buildContainer(containerName, devcontainerPath, config)
await runNewContainer(runOptions)
}
return
}
if (status.startsWith('Up')) {
log('Container already running')
return
}
await startExistingContainer(containerName, log)
}
function createDevContainerUri(
workspacePath: string,
config: DevContainerConfig,
) {
// CURSOR DEV CONTAINER URI CONSTRUCTION
// This is the critical part that differs from VS Code documentation.
// Cursor expects a very specific URI format for programmatic dev container opening.
const dockerContext = detectPlatform()
const workspaceFolder = config.workspaceFolder || '/workspace'
const uriConfig = {
hostPath: workspacePath,
localDocker: true,
settings: {
context: dockerContext, // Platform-specific Docker context
},
}
// Convert JSON to hex encoding as expected by Cursor's dev container extension
// This matches the Buffer.from(s,"utf8").toString("hex") pattern from the extension source
const hex = Array.from(new TextEncoder().encode(JSON.stringify(uriConfig)))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
return `vscode-remote://dev-container+${hex}${workspaceFolder}`
}
async function openCursor(uri: string) {
// Launch Cursor with the properly constructed dev container URI
// Format: vscode-remote://dev-container+<hex-encoded-config><workspace-path>
// This bypasses the need for manual "Reopen in Container" command palette action
const result = await run('cursor', ['--folder-uri', uri])
if (!result.success) {
throw new Error(`Cursor launch failed: ${result.stderr}`)
}
}
/**
* Finds parent PIDs of Cursor processes on Unix-like systems.
*/
async function getCursorPidsForUnix(workspace: string): Promise<Set<string>> {
const decoder = new TextDecoder()
const parentPids = new Set<string>()
const lsof = new Deno.Command('lsof', { args: ['-Fpn'], stdout: 'piped' })
const lsofOutput = await lsof.output()
if (!lsofOutput.success) return parentPids
const lines = decoder.decode(lsofOutput.stdout).split('\n')
const pidsWithOpenWorkspaceFile = new Set<string>()
let currentPid = ''
for (const line of lines) {
if (line.startsWith('p')) {
currentPid = line.slice(1)
} else if (line.startsWith('n') && line.includes(workspace) && currentPid) {
pidsWithOpenWorkspaceFile.add(currentPid)
}
}
if (pidsWithOpenWorkspaceFile.size === 0) return parentPids
const checkPidIsCursor = async (pid: string): Promise<string | null> => {
const ps = new Deno.Command('ps', {
args: ['-o', 'comm=', '-p', pid],
stdout: 'piped',
})
const { success, stdout } = await ps.output()
const isCursor = success &&
decoder.decode(stdout).trim().toLowerCase().includes('cursor')
return isCursor ? pid : null
}
const pidChecks = await Promise.all(
Array.from(pidsWithOpenWorkspaceFile).map(checkPidIsCursor),
)
const cursorPids = new Set(pidChecks.filter((p): p is string => p !== null))
if (cursorPids.size === 0) return parentPids
const ppidPs = new Deno.Command('ps', {
args: ['-o', 'ppid=', '-p', Array.from(cursorPids).join(',')],
stdout: 'piped',
})
const ppidOutput = await ppidPs.output()
if (ppidOutput.success) {
for (
const ppid of decoder.decode(ppidOutput.stdout).split('\n').map((l) =>
l.trim()
).filter(Boolean)
) {
parentPids.add(ppid)
}
}
return parentPids
}
/**
* Finds parent PIDs of Cursor processes on Windows.
*/
async function getCursorPidsForWindows(
workspace: string,
): Promise<Set<string>> {
const decoder = new TextDecoder()
const parentPids = new Set<string>()
const powershell = new Deno.Command('powershell', {
args: [
'-Command',
`Get-Process | Where-Object { $_.Name -like "*cursor*" -and $_.MainModule.FileName -ne $null } | ForEach-Object { $proc = $_; try { $handles = Get-Handle -ProcessId $proc.Id -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*${
workspace.replace(/\\/g, '\\\\')
}*" }; if ($handles) { Write-Output "$($proc.Id),$($proc.Parent.Id)" } } catch { } }`,
],
stdout: 'piped',
stderr: 'piped',
})
const result = await powershell.output()
if (result.success) {
for (
const line of decoder.decode(result.stdout).trim().split('\n').filter(
Boolean,
)
) {
const parts = line.split(',')
if (parts.length === 2) parentPids.add(parts[1])
}
return parentPids
}
const tasklist = new Deno.Command('tasklist', {
args: ['/FO', 'CSV', '/V'],
stdout: 'piped',
})
const taskResult = await tasklist.output()
if (taskResult.success) {
for (
const line of decoder.decode(taskResult.stdout).split('\n').slice(1)
.filter(Boolean)
) {
if (
line.toLowerCase().includes('cursor') && line.includes(workspace)
) {
const match = line.match(/"(\d+)"/g)
if (match && match.length >= 2) {
parentPids.add(match[1].replace(/"/g, ''))
}
}
}
}
return parentPids
}
/**
* Finds all parent PIDs of cursor processes that have files open
* in the specified workspace path
*/
async function getCursorPidsForWorkspace(workspace: string): Promise<string> {
const os = Deno.build.os
const pids = os === 'windows'
? await getCursorPidsForWindows(workspace)
: await getCursorPidsForUnix(workspace)
return [...pids].join(',')
}
async function main() {
const parsedCliArgs = parseArgs(Deno.args, {
alias: {
help: 'h',
verbose: 'v',
workspace: 'w',
name: 'n',
'devcontainer-path': 'd',
},
boolean: ['help', 'verbose', 'reload'],
string: ['workspace', 'name', 'devcontainer-path'],
negatable: ['reload'],
default: {
reload: true,
help: false,
verbose: false,
workspace: Deno.cwd(),
'devcontainer-path': '.devcontainer',
},
})
const args: CliArgs = {
help: parsedCliArgs.help,
reload: parsedCliArgs.reload,
verbose: parsedCliArgs.verbose,
workspace: parsedCliArgs.workspace,
containerName: parsedCliArgs.name,
devcontainerPath: parsedCliArgs['devcontainer-path'],
}
if (args.help) {
showHelp()
return
}
let openCursorPids: string[] = []
if (args.reload) {
openCursorPids =
(await getCursorPidsForWorkspace(args.workspace || Deno.cwd())).split(',')
.filter(Boolean)
}
const log = createLogger(args.verbose)
try {
const devcontainerConfigPath = path.join(
args.workspace,
args.devcontainerPath,
'devcontainer.json',
)
const config = await readDevContainerConfig(devcontainerConfigPath)
const containerName = generateContainerName(
config,
args.workspace,
args.containerName,
)
log(`Using workspace: ${args.workspace}`)
log(`Using container: ${containerName}`)
log(`Platform detected: ${detectPlatform()}`)
await startContainer({
containerName,
devcontainerPath: path.join(args.workspace, args.devcontainerPath),
config,
workspacePath: args.workspace,
log,
})
log('Creating dev container URI...')
const uri = createDevContainerUri(args.workspace, config)
log('Opening Cursor...')
await openCursor(uri)
console.log('Success! Cursor is running in dev container')
if (args.reload && openCursorPids.length > 0) {
try {
await Promise.allSettled(
// This will kill the current process
openCursorPids.map((pid) =>
Deno.kill(Number.parseInt(pid), 'SIGKILL')
),
)
} catch {
// Ignore errors
}
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
console.error('Error:', message)
console.log("Fallback: Use 'Dev Containers: Reopen in Container' in Cursor")
Deno.exit(1)
}
}
if (import.meta.main) {
await main()
}
#!/usr/bin/env node
/**
* @module run-cursor-dev-container
* @fileoverview Generic Cursor Dev Container Launcher
*
* Automates the process of launching Cursor directly into any dev container,
* bypassing the need for manual "Reopen in Container" command palette actions.
*
* @description This script manages Docker container lifecycle and constructs the proper
* vscode-remote:// URI format required by Cursor's dev container extension. Key differences
* from VS Code include specific JSON structure requirements and Docker context settings
* for cross-platform Docker Desktop integration.
*
* KEY DIFFERENCES FROM VS CODE:
* 1. Cursor uses the same dev-container URI format as VS Code, but requires specific
* JSON structure and Docker context settings for cross-platform Docker Desktop
* 2. The --folder-uri flag must be used with a properly constructed vscode-remote:// URI
* 3. Docker context varies by platform: desktop-linux (macOS), default (Linux), desktop-windows (Windows)
*
* @author Zachary Iles
* @version 1.0.0
* @since 2025
* @requires deno
* @requires docker
* @requires cursor
*
* @example
* ```bash
* # Use current directory with default settings
* deno run -A run.ts
*
* # Specify workspace and container name
* deno run -A run.ts --workspace /path/to/project --name my-container
*
* # Use custom devcontainer path
* deno run -A run.ts --devcontainer-path .devcontainer-custom
* ```
*
* @see ~/.cursor/extensions/ms-vscode-remote.remote-containers-0.394.0/dist/extension.js
* Contains URI construction logic around lines 50000-60000, including Buffer.from(configJSON,"utf8").toString("hex") pattern
* @see ~/.cursor/extensions/ms-vscode-remote.remote-containers-0.394.0/dist/common/uri.js
* Authority type definitions (dev-container, attached-container, k8s-container) and URI parsing functions
* @see ~/.cursor/extensions/ms-vscode-remote.remote-containers-0.394.0/dist/common/containerConfiguration.js
* JSON config structure requirements for container settings and Docker context handling
*/
"use strict";
const fs = require("fs");
const path = require("path");
const { spawnSync } = require("child_process");
/**
* @typedef {Object} DevContainerConfig
* @property {string=} name
* @property {string=} workspaceFolder
* @property {string=} workspaceMount
* @property {string=} dockerFile
* @property {string=} image
* @property {{ dockerfile?: string, context?: string }=} build
*/
/**
* @typedef {Object} CliArgs
* @property {string} workspace
* @property {string} devcontainerPath
* @property {string=} containerName
* @property {boolean} help
* @property {boolean} verbose
* @property {boolean} reload
*/
function parseArgs(argv) {
const args = {
help: false,
verbose: false,
reload: true,
workspace: process.cwd(),
devcontainerPath: ".devcontainer",
};
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === "-h" || a === "--help") args.help = true;
else if (a === "-v" || a === "--verbose") args.verbose = true;
else if (a === "--no-reload") args.reload = false;
else if (a === "-w" || a === "--workspace") args.workspace = argv[++i];
else if (a === "-d" || a === "--devcontainer-path")
args.devcontainerPath = argv[++i];
else if (a === "-n" || a === "--name") args.containerName = argv[++i];
}
return args;
}
function showHelp() {
console.log(`
Generic Cursor Dev Container Launcher
USAGE:
node run.js [OPTIONS]
OPTIONS:
-w, --workspace <PATH> Workspace directory path (default: current directory)
-d, --devcontainer-path <PATH> Path to devcontainer directory (default: .devcontainer)
-n, --name <NAME> Override container name
-v, --verbose Show detailed output during execution
--no-reload Don't kill existing Cursor instances after launching container
-h, --help Show this help message
EXAMPLES:
node run.js (assume .devcontainer in current directory with Dockerfile + devcontainer.json)
node run.js --workspace /path/to/project
node run.js --name my-custom-container --devcontainer-path .devcontainer-prod
node run.js --verbose
`);
}
function createLogger(verbose) {
return (msg) => {
if (verbose) console.log(msg);
};
}
function run(cmd, args = []) {
const { status, stdout, stderr } = spawnSync(cmd, args, { encoding: "utf8" });
return { success: status === 0, stdout: stdout.trim(), stderr: stderr.trim() };
}
/**
* Reads devcontainer.json with JSONC support (simple comment stripping)
*/
function readDevContainerConfig(configPath) {
const text = fs.readFileSync(configPath, "utf8");
const json = text.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
return JSON.parse(json);
}
function detectPlatform() {
switch (process.platform) {
case "darwin":
return "desktop-linux";
case "win32":
return "desktop-windows";
case "linux":
default:
return "default";
}
}
function generateContainerName(config, workspacePath, override) {
if (override) return override;
if (config.name) return config.name;
return `${path.basename(workspacePath) || "devcontainer"}-devcontainer`;
}
/**
* Checks if a container exists (running or stopped).
* @param {string} containerName
* @returns {string} Status string if found, else empty.
* @description We need to ensure the container is running before launching Cursor
* because Cursor's dev container extension expects an existing container, unlike VS Code
* which can build containers on-demand more reliably.
*/
function getContainerStatus(containerName) {
return run("docker", [
"ps",
"-a",
"--filter",
`name=${containerName}`,
"--format",
"{{.Status}}",
]).stdout;
}
function buildContainer(containerName, devcontainerPath, config) {
const buildArgs = ["build", "-t", containerName.toLowerCase()];
const dockerfile =
config.build?.dockerfile || config.dockerFile || "Dockerfile";
buildArgs.push("-f", path.join(devcontainerPath, dockerfile));
buildArgs.push(config.build?.context || devcontainerPath);
const res = run("docker", buildArgs);
if (!res.success) throw new Error(`Build failed: ${res.stderr}`);
}
function startExistingContainer(containerName, log) {
log("Starting existing container...");
const res = run("docker", ["start", containerName]);
if (!res.success) throw new Error(`Start failed: ${res.stderr}`);
}
function runNewContainer({ containerName, config, workspacePath, log }) {
log("Running new container...");
const runArgs = ["run", "-d", "--name", containerName];
if (config.workspaceMount) {
const mountParts = Object.fromEntries(
config.workspaceMount.split(",").map((p) => p.split("="))
);
const source = mountParts.source?.replace(
"${localWorkspaceFolder}",
workspacePath
);
const target = mountParts.target;
if (source && target) {
runArgs.push("-v", `${source}:${target}`);
} else {
log("Warning: could not parse workspaceMount string, using default.");
runArgs.push("-v", `${workspacePath}:${config.workspaceFolder || "/workspace"}`);
}
} else {
runArgs.push("-v", `${workspacePath}:${config.workspaceFolder || "/workspace"}`);
}
runArgs.push(containerName.toLowerCase(), "sleep", "infinity");
const res = run("docker", runArgs);
if (!res.success) throw new Error(`Run failed: ${res.stderr}`);
}
function startContainer({ containerName, devcontainerPath, config, workspacePath, log }) {
const status = getContainerStatus(containerName);
if (!status) {
if (config.image) {
runNewContainer({ containerName, config, workspacePath, log });
} else {
buildContainer(containerName, devcontainerPath, config);
runNewContainer({ containerName, config, workspacePath, log });
}
return;
}
if (status.startsWith("Up")) {
log("Container already running");
return;
}
startExistingContainer(containerName, log);
}
function createDevContainerUri(workspacePath, config) {
// CURSOR DEV CONTAINER URI CONSTRUCTION
// This is the critical part that differs from VS Code documentation.
// Cursor expects a very specific URI format for programmatic dev container opening.
const dockerContext = detectPlatform();
const workspaceFolder = config.workspaceFolder || "/workspace";
const uriConfig = {
hostPath: workspacePath,
localDocker: true,
settings: {
context: dockerContext, // Platform-specific Docker context
},
};
// Convert JSON to hex encoding as expected by Cursor's dev container extension
// This matches the Buffer.from(s,"utf8").toString("hex") pattern from the extension source
const hex = Buffer.from(JSON.stringify(uriConfig), "utf8").toString("hex");
return `vscode-remote://dev-container+${hex}${workspaceFolder}`;
}
function openCursor(uri) {
// Launch Cursor with the properly constructed dev container URI
// Format: vscode-remote://dev-container+<hex-encoded-config><workspace-path>
// This bypasses the need for manual "Reopen in Container" command palette action
const res = run("cursor", ["--folder-uri", uri]);
if (!res.success) throw new Error(`Cursor launch failed: ${res.stderr}`);
}
/**
* Finds parent PIDs of Cursor processes on Unix-like systems.
*/
function getCursorPidsForUnix(workspace) {
const parentPids = new Set();
const lsof = spawnSync("lsof", ["-Fpn"], { encoding: "utf8" });
if (lsof.status !== 0) return "";
let currentPid = "";
for (const line of lsof.stdout.split("\n")) {
if (line.startsWith("p")) currentPid = line.slice(1);
else if (line.startsWith("n") && line.includes(workspace) && currentPid) {
const cmd = spawnSync("ps", ["-o", "comm=", "-p", currentPid], { encoding: "utf8" });
if (cmd.status === 0 && cmd.stdout.trim().toLowerCase().includes("cursor")) {
const ppid = spawnSync("ps", ["-o", "ppid=", "-p", currentPid], { encoding: "utf8" });
if (ppid.status === 0) parentPids.add(ppid.stdout.trim());
}
}
}
return [...parentPids].join(",");
}
/**
* Finds parent PIDs of Cursor processes on Windows.
*/
function getCursorPidsForWindows(workspace) {
const parentPids = new Set();
const tasklist = spawnSync("tasklist", ["/FO", "CSV", "/V"], { encoding: "utf8" });
if (tasklist.status !== 0) return "";
for (const line of tasklist.stdout.split("\n").slice(1)) {
if (line.toLowerCase().includes("cursor") && line.includes(workspace)) {
const m = line.match(/"(\d+)"/g);
if (m && m.length >= 2) parentPids.add(m[1].replace(/"/g, ""));
}
}
return [...parentPids].join(",");
}
/**
* Finds all parent PIDs of cursor processes that have files open
* in the specified workspace path
*/
function getCursorPidsForWorkspace(workspace) {
return process.platform === "win32"
? getCursorPidsForWindows(workspace)
: getCursorPidsForUnix(workspace);
}
function main() {
const args = parseArgs(process.argv);
if (args.help) {
showHelp();
return;
}
const openCursorPids = args.reload
? getCursorPidsForWorkspace(args.workspace).split(",").filter(Boolean)
: [];
const log = createLogger(args.verbose);
try {
const devcontainerConfigPath = path.join(
args.workspace,
args.devcontainerPath,
"devcontainer.json"
);
const config = readDevContainerConfig(devcontainerConfigPath);
const containerName = generateContainerName(
config,
args.workspace,
args.containerName
);
log(`Using workspace: ${args.workspace}`);
log(`Using container: ${containerName}`);
log(`Platform detected: ${detectPlatform()}`);
startContainer({
containerName,
devcontainerPath: path.join(args.workspace, args.devcontainerPath),
config,
workspacePath: args.workspace,
log,
});
log("Creating dev container URI...");
const uri = createDevContainerUri(args.workspace, config);
log("Opening Cursor...");
openCursor(uri);
console.log("Success! Cursor is running in dev container");
if (args.reload) {
openCursorPids.forEach((pid) => {
try {
process.kill(Number(pid), "SIGKILL");
} catch {
/* ignore */
}
});
}
} catch (error) {
console.error("Error:", error instanceof Error ? error.message : String(error));
console.log("Fallback: Use 'Dev Containers: Reopen in Container' in Cursor");
process.exit(1);
}
}
if (require.main === module) {
main();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment