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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Both the `open` and `recent` commands share the same set of launch arguments:
- `--command`: Specify which editor command to use (e.g., "code", "code-insiders", "cursor")
- `--behavior`: Set the launch behavior ("detect", "force-container", "force-classic")
- `--config`: Override the path to the dev container config file, or pass a config name to resolve from the config directory
- `--remote-host`: Open the given path on a remote SSH host alias configured for VS Code Remote SSH
- Additional arguments can be passed to the editor executable by specifying them after `--`

The `recent` command additionally supports:
Expand Down Expand Up @@ -245,6 +246,8 @@ vscli open /path/to/project # open vscode in the specified directory

The default behavior tries to detect whether the project is a [dev container](https://containers.dev/) project. If it is, it will launch the dev container instead - if not it will launch vscode normally.

These behaviors apply to local workspaces. Remote SSH workspaces always open as remote folders; `--behavior detect` and `--behavior force-container` are not supported with `--remote-host`.

You can change the launch behavior using the `--behavior` flag:

```sh
Expand All @@ -261,6 +264,7 @@ You can specify which editor command to use with the `--command` flag:
vscli open --command cursor . # open using cursor editor
vscli open --command code . # open using vscode (default)
vscli open --command code-insiders . # open using vscode insiders
vscli open --remote-host my-ec2 /home/ec2-user/app # open a remote folder over SSH
```

Additional arguments can be passed to the editor executable, by specifying them after `--`:
Expand All @@ -281,6 +285,7 @@ vscli recent --command cursor # open the selected project with
vscli recent --behavior force-container # force open the selected project in a dev container
vscli recent --command cursor --behavior detect # open with cursor and detect if dev container should be used
vscli recent --config .devcontainer/custom.json # open with a specific dev container config
vscli recent --remote-host my-ec2 # reopen the selected project on a remembered remote host
vscli recent -- --disable-gpu # pass additional arguments to the editor
vscli recent --hide-instructions # hide the keybinding instructions from the UI
vscli recent --hide-info # hide additional information like strategy, command, args and dev container path
Expand All @@ -306,11 +311,25 @@ vscli config copy rust-dev # copy into the current direct
vscli config copy rust-dev ~/projects/my-app # copy into another project directory
```

#### Remote SSH Hosts

If you already use VS Code Remote SSH, you can point `vscli` at a remote host alias and remote path:

```sh
vscli open --remote-host my-ec2 /home/ec2-user/app
vscli recent --remote-host my-ec2
```

This opens the workspace using a `vscode-remote://ssh-remote+...` folder URI and stores the remote host in `recent` history so you can reopen it from the UI later.

`vscli` does not manage dev containers on remote SSH hosts. Remote workspaces are opened as SSH folders; if the remote folder contains a `.devcontainer` setup, VS Code Dev Containers may offer to reopen it in a container afterward.

#### Environment Variables

| Variable | Description |
| --- | --- |
| `VSCLI_CONFIG_DIR` | Override the config directory (default: `~/.local/share/vscli/configs`) |
| `VSCLI_EDITOR` | Editor command for `config ui` and `container ui` (default: `code`) |
| `VSCLI_REMOTE_HOST` | Default remote SSH host alias for `open` and `recent` |
| `HISTORY_PATH` | Override the history file path |
| `DRY_RUN` | Enable dry-run mode |
60 changes: 60 additions & 0 deletions src/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub struct Entry {
pub config_name: Option<String>,
/// The path to the vscode workspace
pub workspace_path: PathBuf,
/// The remote SSH host alias, if the workspace was opened remotely.
#[serde(default)]
pub remote_host: Option<String>,
/// The path to the dev container config, if it exists
pub config_path: Option<PathBuf>,
/// The launch behavior
Expand All @@ -41,6 +44,7 @@ pub struct Entry {
impl PartialEq for Entry {
fn eq(&self, other: &Self) -> bool {
self.workspace_path == other.workspace_path
&& self.remote_host == other.remote_host
&& self.config_path == other.config_path
&& self.behavior == other.behavior
}
Expand Down Expand Up @@ -225,3 +229,59 @@ impl Tracker {
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::{Entry, History, Tracker};
use crate::launch::{Behavior, ContainerStrategy};
use chrono::Utc;
use std::ffi::OsString;
use std::path::PathBuf;

fn unique_test_path(name: &str) -> PathBuf {
let unique = format!(
"vscli-history-{name}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
);
std::env::temp_dir().join(unique).join("history.json")
}

#[test]
fn tracker_store_and_load_preserve_remote_host_entries() {
let path = unique_test_path("remote-host");
let mut tracker = Tracker {
path: path.clone(),
history: History::default(),
};

tracker.history.upsert(Entry {
workspace_name: "workspace".to_string(),
dev_container_name: None,
config_name: None,
workspace_path: PathBuf::from("/home/dev/workspace"),
remote_host: Some("vscli-remote-test".to_string()),
config_path: None,
behavior: Behavior {
strategy: ContainerStrategy::ForceClassic,
args: vec![OsString::from("--reuse-window")],
command: "code".to_string(),
},
last_opened: Utc::now(),
});

tracker.store().unwrap();

let loaded = Tracker::load(path).unwrap();
let entries = loaded.history.into_entries();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].remote_host.as_deref(), Some("vscli-remote-test"));
assert_eq!(
entries[0].behavior.strategy,
ContainerStrategy::ForceClassic
);
}
}
11 changes: 11 additions & 0 deletions src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,17 @@ impl Setup {
) -> Result<Option<DevContainer>> {
let editor_name = format_editor_name(&self.behavior.command);

if self.workspace.remote_host.is_some() {
info!("Opening remote workspace over SSH with {editor_name}...");

self.workspace.open_classic(
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
self.behavior.args,
self.dry_run,
&self.behavior.command,
)?;
return Ok(None);
}

match self.behavior.strategy {
ContainerStrategy::Detect => {
let dev_container = self.detect(config)?;
Expand Down
Loading