Skip to content
Merged
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
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,14 @@ for version pinning, custom directories, and source-based installation.

#### 2. Enable Local Observability Output

From the project directory ready to be observed, open the project-scoped plugin
editor:
Open the user-scoped plugin editor:

```bash
nemo-relay plugins edit --project
nemo-relay plugins edit
```

The editor creates or updates the nearest project plugin file at
`.nemo-relay/plugins.toml`. In the top-level menu, select **Observability**,
The editor creates or updates `$XDG_CONFIG_HOME/nemo-relay/plugins.toml` (or
`~/.config/nemo-relay/plugins.toml`). In the top-level menu, select **Observability**,
then configure these sections:

1. Toggle the Observability component on.
Expand All @@ -112,8 +111,9 @@ then configure these sections:
5. Press `s` to save.

> [!NOTE]
> Run `nemo-relay plugins edit` without `--project` only when you want
> user-level exporter settings that apply across projects.
> Repository-local `.nemo-relay/plugins.toml` files are ignored. To use a
> configuration stored elsewhere, pass `--config path/to/config.toml`; Relay
> also selects the sibling `path/to/plugins.toml`.
Comment thread
willkill07 marked this conversation as resolved.

#### 3. Run a Coding Agent Through Relay

Expand Down
40 changes: 20 additions & 20 deletions crates/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ nemo-relay run --agent codex --dry-run

## Configuration

Project config lives at `./.nemo-relay/config.toml`; user config lives at
`~/.config/nemo-relay/config.toml` or `$XDG_CONFIG_HOME/nemo-relay/config.toml`.
Runtime files layer from lowest to highest precedence as explicit-or-user,
nearest project, then system. An explicit `--config` replaces the ambient user
file without suppressing project or system configuration.
User config lives at `~/.config/nemo-relay/config.toml` or
`$XDG_CONFIG_HOME/nemo-relay/config.toml`. Runtime files layer from lowest to
highest precedence as explicit-or-user, then system. An explicit `--config`
replaces the ambient user file without suppressing system configuration.
Repository-local `.nemo-relay/config.toml` files are ignored.

Set up agent entries in the top-level config with:

Expand All @@ -163,15 +163,14 @@ structured user-config editor:
nemo-relay config edit
```

Use `--project` for the nearest project `config.toml`, or `--global` for
`/etc/nemo-relay/config.toml`. Global saves are system-readable (`0644` on
Unix) and reject authorization headers; use the corresponding environment
variables or a user config for credentials.
Use `--global` for system configuration: `/etc/nemo-relay/config.toml` on Unix
or `%ProgramData%\nemo-relay\config.toml` on Windows. Global saves are
system-readable (`0644` on Unix) and reject authorization headers; use the
corresponding environment variables or a user config for credentials.

When the top-level CLI receives `--config path/to/config.toml`, the config
editor uses that exact file as its user target, so the default editor and
`config edit --user` both open it. Use `--project` or `--global` to edit the
other active layers.
`config edit --user` both open it. Use `--global` to edit the system layer.

Observability exporters are configured through the plugin config. Edit the user
plugin config with:
Expand All @@ -183,8 +182,8 @@ nemo-relay plugins edit
When the top-level CLI receives `--plugin-config-path`, the editor uses that
exact file. Otherwise, `--config path/to/config.toml` makes the editor use the
sibling `path/to/plugins.toml`, matching runtime selection. The explicit file
replaces the user layer, so `--user` keeps that inherited target.
`--project` and `--global` edit the other active layers.
replaces the user layer, so `--user` keeps that inherited target. `--global`
edits the system layer.

The top-level editor menu contains one entry per supported built-in, followed by
the dynamic plugin references in the selected physical `plugins.toml`. Dynamic
Expand All @@ -193,16 +192,17 @@ Other dynamic plugins use a raw JSON object editor.

The canonical plugin file is `plugins.toml`; user config lives at
`~/.config/nemo-relay/plugins.toml` or
`$XDG_CONFIG_HOME/nemo-relay/plugins.toml`. Project config lives at
`.nemo-relay/plugins.toml`. Use `nemo-relay plugins edit --global` to edit
`/etc/nemo-relay/plugins.toml`; it is system-readable (`0644` on Unix), so do
not store credentials there. The editor rejects schema-declared secret values
in global plugin configuration.
`$XDG_CONFIG_HOME/nemo-relay/plugins.toml`. Use
`nemo-relay plugins edit --global` to edit `/etc/nemo-relay/plugins.toml` on
Unix or `%ProgramData%\nemo-relay\plugins.toml` on Windows. It is
system-readable (`0644` on Unix), so do not store credentials there. The editor
rejects schema-declared secret values in global plugin configuration.

Runtime plugin files layer from lowest to highest precedence as
explicit-or-user, nearest project, then system. An explicit
explicit-or-user, then system. An explicit
`--plugin-config-path`, or a `plugins.toml` beside `--config`, replaces the
ambient XDG user file without suppressing project or system policy. Missing
ambient XDG user file without suppressing system policy. Repository-local
`.nemo-relay/plugins.toml` files are ignored. Missing
files are skipped, and symlink aliases to one physical file are loaded once.

Minimal ATIF example:
Expand Down
3 changes: 1 addition & 2 deletions crates/cli/src/agents/hermes/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ use crate::installation::generation::{
GENERATION_FILE_ENV, GENERATION_TOKEN_ENV, GenerationRetirement, InstallGeneration,
};

/// Hermes host configuration is user-owned even when Relay itself uses project configuration.
/// Project-specific Relay behavior remains available through transparent `nemo-relay run`.
/// Hermes host configuration is user-owned.
pub(crate) fn user_config_path(default_home: &Path) -> PathBuf {
user_config_path_with_override(default_home, env::var_os("HERMES_HOME"))
}
Expand Down
22 changes: 1 addition & 21 deletions crates/cli/src/bootstrap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ pub(crate) struct GatewaySpec {
bind: SocketAddr,
launch_args: Vec<OsString>,
bootstrap_fingerprint: Option<String>,
user_config_scope: bool,
}

impl GatewaySpec {
Expand All @@ -65,7 +64,6 @@ impl GatewaySpec {
bind,
launch_args: Vec::new(),
bootstrap_fingerprint: None,
user_config_scope: false,
}
}

Expand All @@ -79,11 +77,6 @@ impl GatewaySpec {
self
}

pub(crate) fn with_user_config_scope(mut self) -> Self {
self.user_config_scope = true;
self
}

pub(crate) fn bind(&self) -> SocketAddr {
self.bind
}
Expand Down Expand Up @@ -335,18 +328,6 @@ fn start_gateway(spec: &GatewaySpec, state: &Path) -> Result<GatewayEndpoint, St
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
if spec.user_config_scope {
command.env("NEMO_RELAY_CONFIG_SCOPE", "user");
if let Some(config_dir) = crate::configuration::user_config_dir() {
fs::create_dir_all(&config_dir).map_err(|error| {
format!(
"failed to create gateway working directory {}: {error}",
config_dir.display()
)
})?;
command.current_dir(config_dir);
}
}
detached::configure_detached(&mut command);
let child = detached::spawn_detached(&mut command)
.map_err(|error| format!("failed to spawn nemo-relay gateway: {error}"))?;
Expand Down Expand Up @@ -549,8 +530,7 @@ pub(crate) fn resolve_plugin_gateway(
Ok(PluginGatewaySpec {
gateway: GatewaySpec::new(bind)
.with_launch_args(launch_args)
.with_fingerprint(bootstrap_fingerprint)
.with_user_config_scope(),
.with_fingerprint(bootstrap_fingerprint),
max_hook_payload_bytes,
})
}
Expand Down
29 changes: 4 additions & 25 deletions crates/cli/src/commands/configure/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,12 @@ mod prompt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TargetScope {
User,
Project,
Global,
}

impl From<&ConfigEditCommand> for TargetScope {
fn from(command: &ConfigEditCommand) -> Self {
if command.project {
Self::Project
} else if command.global {
if command.global {
Self::Global
} else {
Self::User
Expand All @@ -47,7 +44,7 @@ fn resolve_edit_target(
explicit_path: Option<PathBuf>,
) -> Result<(TargetScope, PathBuf), CliError> {
let scope = TargetScope::from(command);
let path = if command.project || command.global {
let path = if command.global {
target_path(scope)?
} else {
match explicit_path {
Expand Down Expand Up @@ -100,7 +97,7 @@ impl ConfigDocument {
}
crate::filesystem::atomic_write_system_readable(&self.path, contents.as_bytes())
}
TargetScope::User | TargetScope::Project => {
TargetScope::User => {
crate::filesystem::atomic_write_private(&self.path, contents.as_bytes())
}
}
Expand Down Expand Up @@ -550,28 +547,10 @@ fn target_path(scope: TargetScope) -> Result<PathBuf, CliError> {
"cannot determine user config directory; set HOME or XDG_CONFIG_HOME".into(),
)
}),
TargetScope::Project => Ok(project_config_path(&std::env::current_dir()?)),
TargetScope::Global => Ok(PathBuf::from("/etc/nemo-relay/config.toml")),
TargetScope::Global => Ok(crate::configuration::system_config_dir().join("config.toml")),
}
}

fn project_config_path(start: &Path) -> PathBuf {
project_config_path_with_boundary(start, None)
}

fn project_config_path_with_boundary(start: &Path, boundary: Option<&Path>) -> PathBuf {
for ancestor in start.ancestors() {
let candidate = ancestor.join(".nemo-relay/config.toml");
if candidate.exists() {
return candidate;
}
if boundary == Some(ancestor) {
break;
}
}
start.join(".nemo-relay/config.toml")
}

#[cfg(test)]
#[path = "../../../tests/coverage/commands/configure_editor_tests.rs"]
mod tests;
14 changes: 4 additions & 10 deletions crates/cli/src/commands/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,10 @@ pub(crate) struct ConfigCommand {
pub(crate) command: Option<ConfigSubcommand>,
#[arg(value_enum)]
pub(crate) agent: Option<AgentArg>,
/// Reset Relay configuration for the selected scope. Persistent Hermes integration state is
/// Reset user Relay configuration. Persistent Hermes integration state is
/// managed separately with `nemo-relay uninstall hermes`.
#[arg(long)]
pub(crate) reset: bool,
/// Configuration scope to reset. Defaults to the project configuration.
#[arg(long, value_enum, requires = "reset")]
pub(crate) scope: Option<model::ConfigScope>,
}

#[derive(Debug, Clone, Subcommand)]
Expand All @@ -40,17 +37,14 @@ pub(crate) enum ConfigSubcommand {
#[derive(Debug, Clone, Default, Args)]
#[command(group(
ArgGroup::new("scope")
.args(["user", "project", "global"])
.args(["user", "global"])
.multiple(false)
))]
pub(crate) struct ConfigEditCommand {
/// Edit explicit `--config`, otherwise `$XDG_CONFIG_HOME/nemo-relay/config.toml`.
#[arg(long)]
pub(crate) user: bool,
/// Edit the nearest project config at `.nemo-relay/config.toml`.
#[arg(long)]
pub(crate) project: bool,
/// Edit the system config at `/etc/nemo-relay/config.toml`.
/// Edit system config (`/etc/nemo-relay` on Unix; `%ProgramData%\nemo-relay` on Windows).
#[arg(long)]
pub(crate) global: bool,
}
Expand All @@ -65,7 +59,7 @@ pub(super) async fn execute(
}
let agent = command.agent.map(Into::into);
if command.reset {
model::reset(command.scope.unwrap_or(model::ConfigScope::Project), agent)?;
model::reset(agent)?;
} else {
let overrides = server.to_runtime();
let explicit_plugin_path = crate::configuration::explicit_plugin_config_path(
Expand Down
Loading
Loading