mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
Move cp, ssh, preview under fabro sandbox namespace
These commands all operate on a run's sandbox environment. Grouping them under `fabro sandbox` makes the mental model clear and avoids confusion with `fabro asset cp`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
69510cd016
commit
615636c4e7
19 changed files with 93 additions and 58 deletions
|
|
@ -47,7 +47,7 @@ curl -fsSL https://fabro.sh/install.sh | bash
|
|||
| 🙋 | Human-in-the-loop | Approval gates pause for human decisions. Steer running agents mid-turn. Interview steps collect structured input |
|
||||
| 🎨 | Multi-model routing | CSS-like stylesheets route each node to the right model and provider, with automatic fallback chains |
|
||||
| ☁️ | Cloud sandboxes | Run agents in isolated Daytona cloud VMs with snapshot-based setup, network controls, and automatic cleanup |
|
||||
| 🔌 | SSH access and preview links | Shell into running sandboxes with `fabro ssh` and expose ports with `fabro preview` for live debugging |
|
||||
| 🔌 | SSH access and preview links | Shell into running sandboxes with `fabro sandbox ssh` and expose ports with `fabro sandbox preview` for live debugging |
|
||||
| 🌲 | Git checkpointing | Every stage commits code changes and execution metadata to Git branches. Resume, revert, or trace any change |
|
||||
| 📊 | Automatic retros | Each run generates a retrospective with cost, duration, files touched, and an LLM-written narrative |
|
||||
| ⚡ | Comprehensive API | REST API with SSE event streaming and a React web UI. Run workflows programmatically or as a service |
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ When using server defaults, labels are merged — run config labels override def
|
|||
Connect to a running Daytona sandbox via SSH for live debugging:
|
||||
|
||||
```bash
|
||||
fabro ssh <run-id>
|
||||
fabro sandbox ssh <run-id>
|
||||
```
|
||||
|
||||
This creates temporary SSH credentials (valid for 60 minutes) and connects directly.
|
||||
|
|
@ -306,7 +306,7 @@ image = "my-custom-image:latest"
|
|||
Connect to a running exe.dev sandbox via SSH for live debugging:
|
||||
|
||||
```bash
|
||||
fabro ssh <run-id>
|
||||
fabro sandbox ssh <run-id>
|
||||
```
|
||||
|
||||
This prints the SSH connection command so you can connect to the VM while the workflow runs.
|
||||
|
|
|
|||
|
|
@ -20,15 +20,15 @@ When you request a preview, Fabro generates a time-limited, token-authenticated
|
|||
|
||||
## Using preview from the CLI
|
||||
|
||||
Use `fabro preview` to generate a preview URL for any run with an active Daytona sandbox:
|
||||
Use `fabro sandbox preview` to generate a preview URL for any run with an active Daytona sandbox:
|
||||
|
||||
```bash
|
||||
fabro preview <run-id> 3000 # URL + token + curl example
|
||||
fabro preview <run-id> 3000 --signed # self-contained signed URL
|
||||
fabro preview <run-id> 3000 --open # open in browser (implies --signed)
|
||||
fabro sandbox preview <run-id> 3000 # URL + token + curl example
|
||||
fabro sandbox preview <run-id> 3000 --signed # self-contained signed URL
|
||||
fabro sandbox preview <run-id> 3000 --open # open in browser (implies --signed)
|
||||
```
|
||||
|
||||
See [`fabro preview`](/reference/cli#fabro-preview) for the full flag reference.
|
||||
See [`fabro sandbox preview`](/reference/cli#fabro-sandbox-preview) for the full flag reference.
|
||||
|
||||
## Using preview from the web UI
|
||||
|
||||
|
|
|
|||
|
|
@ -11,17 +11,17 @@ SSH access is only available with the Daytona sandbox provider. Local, Docker, a
|
|||
|
||||
## Connecting to a run's sandbox
|
||||
|
||||
Use `fabro ssh` to connect to the Daytona sandbox from any completed or in-progress run:
|
||||
Use `fabro sandbox ssh` to connect to the Daytona sandbox from any completed or in-progress run:
|
||||
|
||||
```bash
|
||||
fabro ssh <run-id>
|
||||
fabro sandbox ssh <run-id>
|
||||
```
|
||||
|
||||
Fabro creates temporary SSH credentials and connects directly. Use `--print` to print the SSH command instead of connecting, or `--ttl` to set the credential expiry:
|
||||
|
||||
```bash
|
||||
fabro ssh <run-id> --print
|
||||
fabro ssh <run-id> --ttl 120
|
||||
fabro sandbox ssh <run-id> --print
|
||||
fabro sandbox ssh <run-id> --ttl 120
|
||||
```
|
||||
|
||||
## Keeping the sandbox alive
|
||||
|
|
@ -56,7 +56,7 @@ Once connected, you have a full shell inside the sandbox VM:
|
|||
|
||||
## Credential lifetime
|
||||
|
||||
SSH credentials are temporary and expire after **60 minutes** by default. With `fabro ssh`, you can set a custom TTL with `--ttl <MINUTES>`. If your session expires, run `fabro ssh` again to get fresh credentials.
|
||||
SSH credentials are temporary and expire after **60 minutes** by default. With `fabro sandbox ssh`, you can set a custom TTL with `--ttl <MINUTES>`. If your session expires, run `fabro sandbox ssh` again to get fresh credentials.
|
||||
|
||||
## Limitations
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ VS Code remote access requires [SSH access](/human-tools/ssh-access), which is o
|
|||
fabro run workflow.fabro --sandbox daytona --preserve-sandbox
|
||||
```
|
||||
|
||||
2. Use `fabro ssh` to get the connection command:
|
||||
2. Use `fabro sandbox ssh` to get the connection command:
|
||||
|
||||
```bash
|
||||
fabro ssh <run-id> --print
|
||||
fabro sandbox ssh <run-id> --print
|
||||
```
|
||||
|
||||
3. In VS Code, open the Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`) and run **Remote-SSH: Connect to Host...**
|
||||
|
|
@ -50,4 +50,4 @@ Once connected, VS Code operates as if the sandbox filesystem were local:
|
|||
|
||||
- **Use `--preserve-sandbox`** — Without it, the sandbox is destroyed when the workflow finishes and your VS Code session disconnects. Combine with `auto_stop_interval` in your [run config](/execution/run-configuration) to control idle timeout.
|
||||
- **Pair with human gates** — When a workflow pauses at a [human gate](/workflows/human-in-the-loop), connect via VS Code to review the agent's changes before approving.
|
||||
- **SSH credential lifetime** — Daytona SSH credentials expire after 60 minutes by default. If your VS Code session disconnects, run `fabro ssh <run-id>` again to get fresh credentials (use `--ttl` to set a custom expiry).
|
||||
- **SSH credential lifetime** — Daytona SSH credentials expire after 60 minutes by default. If your VS Code session disconnects, run `fabro sandbox ssh <run-id>` again to get fresh credentials (use `--ttl` to set a custom expiry).
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ for your organization.
|
|||
Connect to a running Daytona sandbox via SSH for live debugging:
|
||||
|
||||
```bash
|
||||
fabro ssh <run-id>
|
||||
fabro sandbox ssh <run-id>
|
||||
```
|
||||
|
||||
This creates temporary SSH credentials (valid for 60 minutes) and connects directly. Use `--print` to print the SSH command instead of connecting, or `--ttl` to set the credential expiry.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ image = "my-custom-image:latest"
|
|||
Connect to a running exe.dev sandbox via SSH for live debugging:
|
||||
|
||||
```bash
|
||||
fabro ssh <run-id>
|
||||
fabro sandbox ssh <run-id>
|
||||
```
|
||||
|
||||
This prints the SSH connection command so you can connect to the VM while the workflow runs.
|
||||
|
|
|
|||
|
|
@ -354,14 +354,14 @@ If no LLM provider API keys are configured, the server automatically falls back
|
|||
|
||||
---
|
||||
|
||||
## `fabro cp`
|
||||
## `fabro sandbox cp`
|
||||
|
||||
Copy files between a run's sandbox and the local filesystem. The run must have a persisted sandbox record (Daytona, exe.dev, or a preserved local/Docker sandbox).
|
||||
|
||||
```bash
|
||||
fabro cp <run-id>:/path/in/sandbox ./local-dir # download
|
||||
fabro cp ./local-file <run-id>:/path/in/sandbox # upload
|
||||
fabro cp -r <run-id>:/output ./results # recursive download
|
||||
fabro sandbox cp <run-id>:/path/in/sandbox ./local-dir # download
|
||||
fabro sandbox cp ./local-file <run-id>:/path/in/sandbox # upload
|
||||
fabro sandbox cp -r <run-id>:/output ./results # recursive download
|
||||
```
|
||||
|
||||
| Argument / Flag | Description |
|
||||
|
|
@ -679,14 +679,14 @@ fabro diff <run-id> --stat
|
|||
|
||||
Output is colorized when writing to a terminal.
|
||||
|
||||
## `fabro ssh`
|
||||
## `fabro sandbox ssh`
|
||||
|
||||
SSH into a Daytona sandbox from a completed or in-progress run. Creates temporary SSH credentials and connects directly, or prints the command with `--print`.
|
||||
|
||||
```bash
|
||||
fabro ssh <run-id>
|
||||
fabro ssh <run-id> --print
|
||||
fabro ssh <run-id> --ttl 120
|
||||
fabro sandbox ssh <run-id>
|
||||
fabro sandbox ssh <run-id> --print
|
||||
fabro sandbox ssh <run-id> --ttl 120
|
||||
```
|
||||
|
||||
| Argument / Flag | Description |
|
||||
|
|
@ -697,14 +697,14 @@ fabro ssh <run-id> --ttl 120
|
|||
|
||||
See [SSH Access](/human-tools/ssh-access) for more details on SSH workflows.
|
||||
|
||||
## `fabro preview`
|
||||
## `fabro sandbox preview`
|
||||
|
||||
Generate a preview URL for a port exposed by a Daytona sandbox. Useful for accessing web servers, dev tools, or APIs running inside the sandbox.
|
||||
|
||||
```bash
|
||||
fabro preview <run-id> <port>
|
||||
fabro preview <run-id> 3000 --signed
|
||||
fabro preview <run-id> 3000 --open
|
||||
fabro sandbox preview <run-id> <port>
|
||||
fabro sandbox preview <run-id> 3000 --signed
|
||||
fabro sandbox preview <run-id> 3000 --open
|
||||
```
|
||||
|
||||
| Argument / Flag | Description |
|
||||
|
|
|
|||
|
|
@ -671,12 +671,6 @@ pub(crate) enum RunCommands {
|
|||
#[arg(long)]
|
||||
resume: bool,
|
||||
},
|
||||
/// Copy files to/from a run's sandbox
|
||||
Cp(CpArgs),
|
||||
/// Get a preview URL for a port on a run's sandbox
|
||||
Preview(PreviewArgs),
|
||||
/// SSH into a run's Daytona sandbox
|
||||
Ssh(SshArgs),
|
||||
/// Show the diff of changes from a workflow run
|
||||
#[command(hide = true)]
|
||||
Diff(DiffArgs),
|
||||
|
|
@ -700,9 +694,6 @@ impl RunCommands {
|
|||
Self::Start { .. } => "start",
|
||||
Self::Attach { .. } => "attach",
|
||||
Self::Detached { .. } => "__detached",
|
||||
Self::Cp(_) => "cp",
|
||||
Self::Preview(_) => "preview",
|
||||
Self::Ssh(_) => "ssh",
|
||||
Self::Diff(_) => "diff",
|
||||
Self::Logs(_) => "logs",
|
||||
Self::Resume(_) => "resume",
|
||||
|
|
@ -713,6 +704,26 @@ impl RunCommands {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum SandboxCommand {
|
||||
/// Copy files to/from a run's sandbox
|
||||
Cp(CpArgs),
|
||||
/// Get a preview URL for a port on a run's sandbox
|
||||
Preview(PreviewArgs),
|
||||
/// SSH into a run's sandbox
|
||||
Ssh(SshArgs),
|
||||
}
|
||||
|
||||
impl SandboxCommand {
|
||||
pub(crate) fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Cp(_) => "sandbox cp",
|
||||
Self::Preview(_) => "sandbox preview",
|
||||
Self::Ssh(_) => "sandbox ssh",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum RunsCommands {
|
||||
/// List workflow runs
|
||||
|
|
@ -807,6 +818,11 @@ pub(crate) enum Commands {
|
|||
Repo(RepoNamespace),
|
||||
/// Provider operations
|
||||
Provider(ProviderNamespace),
|
||||
/// Sandbox operations (cp, ssh, preview)
|
||||
Sandbox {
|
||||
#[command(subcommand)]
|
||||
command: SandboxCommand,
|
||||
},
|
||||
/// System maintenance commands
|
||||
System(SystemNamespace),
|
||||
/// Send a queued analytics event (internal)
|
||||
|
|
@ -885,6 +901,7 @@ impl Commands {
|
|||
Self::Provider(ns) => match &ns.command {
|
||||
ProviderCommand::Login(_) => "provider login",
|
||||
},
|
||||
Self::Sandbox { command } => command.name(),
|
||||
Self::System(ns) => match &ns.command {
|
||||
SystemCommand::Prune(_) => "system prune",
|
||||
SystemCommand::Df(_) => "system df",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ pub(crate) mod provider;
|
|||
pub(crate) mod repo;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod runs;
|
||||
pub(crate) mod sandbox;
|
||||
pub(crate) mod secret;
|
||||
pub(crate) mod skill;
|
||||
pub(crate) mod store;
|
||||
|
|
|
|||
|
|
@ -68,9 +68,6 @@ pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<(
|
|||
launcher_path,
|
||||
resume,
|
||||
} => detached::execute(run_dir, launcher_path, resume).await,
|
||||
RunCommands::Cp(args) => cp::cp_command(args, globals).await,
|
||||
RunCommands::Preview(args) => preview::run(args, globals).await,
|
||||
RunCommands::Ssh(args) => ssh::run(args, globals).await,
|
||||
RunCommands::Diff(args) => diff::run(args, globals).await,
|
||||
RunCommands::Logs(args) => {
|
||||
let styles = Styles::detect_stdout();
|
||||
|
|
|
|||
11
lib/crates/fabro-cli/src/commands/sandbox/mod.rs
Normal file
11
lib/crates/fabro-cli/src/commands/sandbox/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use anyhow::Result;
|
||||
|
||||
use crate::args::{GlobalArgs, SandboxCommand};
|
||||
|
||||
pub(crate) async fn dispatch(command: SandboxCommand, globals: &GlobalArgs) -> Result<()> {
|
||||
match command {
|
||||
SandboxCommand::Cp(args) => super::run::cp::cp_command(args, globals).await,
|
||||
SandboxCommand::Preview(args) => super::run::preview::run(args, globals).await,
|
||||
SandboxCommand::Ssh(args) => super::run::ssh::run(args, globals).await,
|
||||
}
|
||||
}
|
||||
|
|
@ -217,6 +217,7 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
commands::upgrade::run_upgrade(args).await?;
|
||||
}
|
||||
Commands::Provider(ns) => commands::provider::dispatch(ns).await?,
|
||||
Commands::Sandbox { command } => commands::sandbox::dispatch(command, &globals).await?,
|
||||
Commands::System(ns) => commands::system::dispatch(ns, &globals).await?,
|
||||
Commands::SendAnalytics { path } => {
|
||||
let result = sender::upload(&path).await;
|
||||
|
|
@ -382,11 +383,13 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn parse_cp_command() {
|
||||
let cli = Cli::try_parse_from(["fabro", "cp", "ABC123:/tmp/file", "./file"])
|
||||
fn parse_sandbox_cp_command() {
|
||||
let cli = Cli::try_parse_from(["fabro", "sandbox", "cp", "ABC123:/tmp/file", "./file"])
|
||||
.expect("should parse");
|
||||
match *cli.command {
|
||||
Commands::RunCmd(RunCommands::Cp(args)) => {
|
||||
Commands::Sandbox {
|
||||
command: args::SandboxCommand::Cp(args),
|
||||
} => {
|
||||
assert_eq!(args.src, "ABC123:/tmp/file");
|
||||
assert_eq!(args.dst, "./file");
|
||||
assert!(!args.recursive);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ fn help() {
|
|||
----- stdout -----
|
||||
Copy files to/from a run's sandbox
|
||||
|
||||
Usage: fabro cp [OPTIONS] <SRC> <DST>
|
||||
Usage: fabro sandbox cp [OPTIONS] <SRC> <DST>
|
||||
|
||||
Arguments:
|
||||
<SRC> Source: <run-id>:<path> or local path
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ fn help() {
|
|||
create Create a workflow run (allocate run dir, persist spec)
|
||||
start Start a created workflow run (spawn engine process)
|
||||
attach Attach to a running or finished workflow run
|
||||
cp Copy files to/from a run's sandbox
|
||||
preview Get a preview URL for a port on a run's sandbox
|
||||
ssh SSH into a run's Daytona sandbox
|
||||
logs View the event log of a workflow run
|
||||
resume Resume an interrupted workflow run
|
||||
rewind Rewind a workflow run to an earlier checkpoint
|
||||
|
|
@ -43,6 +40,7 @@ fn help() {
|
|||
upgrade Upgrade fabro to the latest version
|
||||
repo Repository commands
|
||||
provider Provider operations
|
||||
sandbox Sandbox operations (cp, ssh, preview)
|
||||
system System maintenance commands
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ fn help() {
|
|||
----- stdout -----
|
||||
Get a preview URL for a port on a run's sandbox
|
||||
|
||||
Usage: fabro preview [OPTIONS] <RUN> <PORT>
|
||||
Usage: fabro sandbox preview [OPTIONS] <RUN> <PORT>
|
||||
|
||||
Arguments:
|
||||
<RUN> Run ID or prefix
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ fn help() {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
SSH into a run's Daytona sandbox
|
||||
SSH into a run's sandbox
|
||||
|
||||
Usage: fabro ssh [OPTIONS] <RUN>
|
||||
Usage: fabro sandbox ssh [OPTIONS] <RUN>
|
||||
|
||||
Arguments:
|
||||
<RUN> Run ID or prefix
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ fn local_run_lifecycle() {
|
|||
.unwrap();
|
||||
let cp_dest = context.temp_dir.join("cp_download.txt");
|
||||
cmd(&[
|
||||
"sandbox",
|
||||
"cp",
|
||||
&format!("{run_id}:cp_test.txt"),
|
||||
cp_dest.to_str().unwrap(),
|
||||
|
|
|
|||
|
|
@ -161,10 +161,17 @@ impl TestContext {
|
|||
cmd
|
||||
}
|
||||
|
||||
/// Build a `cp` subcommand.
|
||||
/// Build a `sandbox` subcommand.
|
||||
pub fn sandbox(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("sandbox");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `sandbox cp` subcommand.
|
||||
pub fn cp(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("cp");
|
||||
cmd.args(["sandbox", "cp"]);
|
||||
cmd
|
||||
}
|
||||
|
||||
|
|
@ -189,10 +196,10 @@ impl TestContext {
|
|||
cmd
|
||||
}
|
||||
|
||||
/// Build a `preview` subcommand.
|
||||
/// Build a `sandbox preview` subcommand.
|
||||
pub fn preview(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("preview");
|
||||
cmd.args(["sandbox", "preview"]);
|
||||
cmd
|
||||
}
|
||||
|
||||
|
|
@ -203,10 +210,10 @@ impl TestContext {
|
|||
cmd
|
||||
}
|
||||
|
||||
/// Build an `ssh` subcommand.
|
||||
/// Build a `sandbox ssh` subcommand.
|
||||
pub fn ssh(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("ssh");
|
||||
cmd.args(["sandbox", "ssh"]);
|
||||
cmd
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue