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
83 changes: 83 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ regex = "1.12.4"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.150"
sha2 = "0.10.9"
toml = "0.9"
shell-use = { path = "crates/shell-use" }
ttf-parser = { version = "0.25.1", default-features = false, features = ["std"] }

Expand Down
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ prints a session's effective timeouts.

| Command | Description |
| ------------------------------------------------------------ | ------------------------------------------- |
| `open [--shell S] [--cols N --rows N] [--cwd D] [--env K=V] [--timeout-<class> MS]` | Spawn a shell session. |
| `open [--shell S] [--cols N --rows N] [--cwd D] [--env K=V] [--config F] [--profile P] [--timeout-<class> MS]` | Spawn a shell session. |
| `run <program> [args...]` | Spawn a session running a program directly. |
| `sessions` | List active sessions. |
| `close [--all]` | Close the current session (or all). |
Expand Down Expand Up @@ -295,6 +295,54 @@ Every command returns a stable exit code so an agent can branch on the failure c

With `--json`, failures also carry a `"kind"` field (`assertion`/`usage`/`no_session`/`internal`).

## Configuration

Settings live in a `shell-use.toml` with named profiles. Everything is
optional, so a file only states what it changes:

```toml
[profiles.default]
scrollback = 10000 # rows kept beyond the visible screen

[profiles.default.colors]
background = "#000000"
foreground = "#c0c0c0"
cursor = "#c0c0c0"
red = "#800000" # any of the 16 ANSI slots, by name

[profiles.ci]
scrollback = 500 # inherits the default palette
```

```bash
shell-use open # profile "default"
shell-use open --profile ci
shell-use open --config ./other.toml --profile ci
```

Looked up nearest first: `./shell-use.toml`, then
`~/.shell-use/shell-use.toml`. `--config` or `SHELL_USE_CONFIG` replaces the
search. Running without a config file is normal; a file that fails to parse is
an error rather than a silent fallback.

Resolution happens in the CLI, not the daemon — the daemon is long-lived and
shared, so it has no working directory to resolve a project-local config
against.

### Colors

A terminal grid stores colour *indices*, not colours. What index 1 looks like
is the profile's choice, and shell-use needs that choice twice: to draw a
screenshot, and to answer `expect --fg "#rrggbb"`. **Both read the same table**,
so a colour an assertion matches is the colour a screenshot paints.

Only the 16 ANSI slots and the three defaults are configurable. Indices 16-255
are the xterm colour cube and grey ramp, fixed by the spec, so `--fg 196` means
the same thing in every profile.

The shipped palette is the classic VGA/xterm one that `TERM=xterm-256color`
promises.

## Supported shells

- bash
Expand Down
24 changes: 23 additions & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ without parsing text:

| Command | Description |
| --- | --- |
| `open [--shell S] [--cols N] [--rows N] [--cwd D] [--env K=V]...` | Spawn a shell session (auto-starts the daemon). `--env` is repeatable. |
| `open [--shell S] [--cols N] [--rows N] [--cwd D] [--env K=V] [--config F] [--profile P]...` | Spawn a shell session (auto-starts the daemon). `--env` is repeatable. |
| `run <program> [args...] [--cols N] [--rows N] [--cwd D] [--env K=V]...` | Spawn a session running a program directly (no shell). |
| `sessions` | List active sessions. |
| `close [--all]` | Close the current session (or every session with `--all`). |
Expand Down Expand Up @@ -299,6 +299,28 @@ of `ShellUseError`. On its first call a client also checks that the daemon's
version matches the package and raises `VersionMismatchError` if they differ;
stop the daemon (`daemon_stop`) so it restarts on the matching binary.

## Configuration

`shell-use.toml` holds named profiles; `--profile NAME` selects one and
`--config PATH` picks the file. Looked up nearest first: `./shell-use.toml`
then `~/.shell-use/shell-use.toml`. No file is fine; an unparseable one errors.

```toml
[profiles.ci]
scrollback = 500

[profiles.ci.colors]
red = "#ff0000"
```

A profile sets `scrollback` (default 10000) and colors: `foreground`,
`background`, `cursor`, and the 16 ANSI slots by name (`red`, `bright_red`,
...). Indices 16-255 are spec-defined and not configurable, so `--fg 196` is
stable across profiles.

The palette is what a screenshot paints **and** what `expect --fg/--bg` matches
a `#rrggbb` against, so the two always agree.

## Supported shells & integration

`open --shell S` accepts: `bash`, `zsh`, `fish`, `powershell`, `pwsh`, `cmd`,
Expand Down
26 changes: 26 additions & 0 deletions crates/shell-use-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,28 @@ impl From<ShellArg> for Shell {
}
}

/// Which terminal profile a session runs with.
#[derive(Args, Clone, Default)]
pub struct ProfileArgs {
/// Config file to read (default: ./shell-use.toml, then
/// ~/.shell-use/shell-use.toml).
#[arg(long, value_name = "PATH")]
pub config: Option<std::path::PathBuf>,
/// Named profile from the config file (default: `default`).
#[arg(long, value_name = "NAME")]
pub profile: Option<String>,
}

impl ProfileArgs {
/// Resolve to concrete settings. Done here, in the client, because the
/// daemon is long-lived and shared and so has no working directory to
/// resolve a project-local config against.
pub fn resolve(&self) -> anyhow::Result<shell_use::profile::Profile> {
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
shell_use::profile::resolve(self.config.as_deref(), self.profile.as_deref(), &cwd)
}
}

/// Per-class default timeouts for a session, in milliseconds.
#[derive(Args, Clone, Copy, Default)]
pub struct TimeoutArgs {
Expand Down Expand Up @@ -114,6 +136,8 @@ pub enum Command {
#[arg(long, conflicts_with = "wait_ready")]
no_wait_ready: bool,
#[command(flatten)]
profile: ProfileArgs,
#[command(flatten)]
timeouts: TimeoutArgs,
},
/// Spawn a session running a program directly.
Expand Down Expand Up @@ -143,6 +167,8 @@ pub enum Command {
#[arg(long, conflicts_with = "wait_ready")]
no_wait_ready: bool,
#[command(flatten)]
profile: ProfileArgs,
#[command(flatten)]
timeouts: TimeoutArgs,
},
/// Close the current session (or all sessions).
Expand Down
4 changes: 4 additions & 0 deletions crates/shell-use-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,12 @@ fn build_request(command: Command) -> anyhow::Result<Request> {
env,
wait_ready,
no_wait_ready,
profile,
timeouts,
} => Request::Open {
shell: shell.map(Into::into),
program: None,
profile: profile.resolve()?,
cols,
rows,
cwd,
Expand All @@ -152,13 +154,15 @@ fn build_request(command: Command) -> anyhow::Result<Request> {
env,
wait_ready,
no_wait_ready,
profile,
timeouts,
} => {
let mut prog = vec![program];
prog.extend(args);
Request::Open {
shell: None,
program: Some(prog),
profile: profile.resolve()?,
cols,
rows,
cwd,
Expand Down
Loading
Loading