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
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ shared runtime for scopes, policy, plugins, and lifecycle events.

| Goal | Start With... |
|---|---|
| Observe Codex, Claude Code, Cursor, or Hermes locally via CLI | [Quick Start CLI](https://docs.nvidia.com/nemo/relay/nemo-relay-cli/about) |
| Observe Codex, Claude Code, or Hermes locally via CLI | [Quick Start CLI](https://docs.nvidia.com/nemo/relay/nemo-relay-cli/about) |
| Instrument app-owned LLM or tool calls | [Quick Start Application](https://docs.nvidia.com/nemo/relay/getting-started/quick-start) |
| Use LangChain, LangGraph, Deep Agents, or OpenClaw | [Supported Integrations](https://docs.nvidia.com/nemo/relay/supported-integrations/about) |
| Build a framework or provider integration | [Integrate into Frameworks](https://docs.nvidia.com/nemo/relay/integrate-into-frameworks/about) |
Expand Down Expand Up @@ -282,7 +282,6 @@ coverage.
| Claude Code | Yes | Yes | Partial | Hook forwarding, pre-tool blocking, and gateway-routed LLM observability are supported. |
| Codex | Yes | Yes | Partial | Hook activation is required; missing session-end behavior limits trajectory finalization and full optimization coverage. |
| Hermes Agent | Yes | Yes | Partial | Hook forwarding, pre-tool blocking, and gateway-routed or hook-backed LLM observability are supported. |
| Cursor | Partial | Limited | No | Missing hooks under `cursor-agent` and manual gateway routing limit full feature coverage. |

### Public API Integrations

Expand Down
8 changes: 4 additions & 4 deletions crates/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ with the installed `nemo-relay` command rather than link against the crate.

## Why Use It?

- 🧭 **Observe existing coding agents**: Run Claude Code, Codex, Cursor, or
Hermes Agent through a local NeMo Relay gateway without changing the agent
- 🧭 **Observe existing coding agents**: Run Claude Code, Codex, or Hermes
Agent through a local NeMo Relay gateway without changing the agent
itself.
- 🛠️ **Configure hooks interactively**: Use the setup wizard to write project or
user config and install the hook files needed by supported agents.
Expand All @@ -43,8 +43,8 @@ with the installed `nemo-relay` command rather than link against the crate.
Cargo package.
- ✅ **First-run setup**: Bare `nemo-relay` launches setup when no config exists,
then runs doctor once config is present.
- ✅ **Agent shortcuts**: `nemo-relay claude`, `nemo-relay codex`,
`nemo-relay cursor`, and `nemo-relay hermes` start observed agent runs.
- ✅ **Agent shortcuts**: `nemo-relay claude`, `nemo-relay codex`, and
`nemo-relay hermes` start observed agent runs.
- ✅ **Config-driven launch**: `nemo-relay run` resolves config, environment, and
CLI overrides for deterministic non-interactive use.
- ✅ **Hook forwarding server**: A local gateway accepts agent hook events and
Expand Down
44 changes: 0 additions & 44 deletions crates/cli/src/adapters/cursor.rs

This file was deleted.

8 changes: 3 additions & 5 deletions crates/cli/src/adapters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

pub(crate) mod claude_code;
pub(crate) mod codex;
pub(crate) mod cursor;
pub(crate) mod hermes;

use axum::http::HeaderMap;
Expand Down Expand Up @@ -375,10 +374,9 @@ fn value_at(payload: &Value, path: &[&str]) -> Option<Value> {
// closing the agent scope. Codex 0.129 has no `SessionEnd`-equivalent hook — without this dual
// emission, codex transparent runs would never trigger an ATIF write.
//
// If the primary event is already terminal (e.g., Cursor classifies `stop` as `AgentEnded`),
// the snapshot is skipped to avoid double-writing — `flush_observers` already writes ATIF on
// agent-end, and a follow-up `TurnEnded` on a removed session would recreate an empty session
// and overwrite the freshly-written ATIF with an empty trajectory.
// If the primary event is already terminal, the snapshot is skipped to avoid double-writing —
// `flush_observers` already writes ATIF on agent-end, and a follow-up `TurnEnded` on a removed
// session would recreate an empty session and overwrite the freshly-written ATIF.
fn classify(
payload: &Value,
headers: &HeaderMap,
Expand Down
57 changes: 3 additions & 54 deletions crates/cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,6 @@ pub(crate) enum Command {
nemo-relay --openai-base-url https://inference-api.nvidia.com codex"
)]
Codex(EasyPathCommand),
/// Run Cursor with observability (setup on first use)
#[command(
long_about = "Run Cursor's `cursor-agent` CLI under an ephemeral NeMo Relay gateway. The \
launcher temporarily patches `.cursor/hooks.json` in the project root \
during the run and restores it on exit. Disable that via \
`[agents.cursor] patch_restore_hooks = false` in config.toml if you \
maintain `.cursor/hooks.json` yourself.",
after_help = "Examples:\n \
nemo-relay cursor\n \
nemo-relay cursor -- agent --resume <session-id>"
)]
Cursor(EasyPathCommand),
/// Run Hermes with observability (setup on first use)
#[command(
long_about = "Run NVIDIA's Hermes agent under a NeMo Relay gateway. Hermes reads hooks \
Expand Down Expand Up @@ -538,7 +526,6 @@ pub(crate) enum CodingAgent {
#[value(name = "claude", alias = "claude-code")]
ClaudeCode,
Codex,
Cursor,
Hermes,
}

Expand Down Expand Up @@ -626,7 +613,6 @@ impl ResolvedDynamicPluginConfig {
pub(crate) struct AgentConfigs {
pub(crate) claude: AgentCommandConfig,
pub(crate) codex: AgentCommandConfig,
pub(crate) cursor: CursorAgentConfig,
pub(crate) hermes: AgentCommandConfig,
}

Expand All @@ -638,24 +624,6 @@ pub(crate) struct AgentCommandConfig {
pub(crate) hooks_path: Option<PathBuf>,
}

#[derive(Debug, Clone)]
pub(crate) struct CursorAgentConfig {
pub(crate) command: Option<String>,
pub(crate) patch_restore_hooks: bool,
}

impl Default for CursorAgentConfig {
// Keeps Cursor run-mode patching enabled unless a config file opts out. Cursor's CLI discovers
// hooks from project files, so the launcher needs permission to temporarily patch and restore
// `.cursor/hooks.json` by default.
fn default() -> Self {
Self {
command: None,
patch_restore_hooks: true,
}
}
}

// TOML file shape grouped by user intent. Sections map 1:1 onto fields already present on
// `GatewayConfig` / `AgentConfigs`; plugin config is passed through to the runtime's generic
// `PluginConfig` activation path.
Expand Down Expand Up @@ -687,12 +655,11 @@ struct FilePluginsConfig {

#[derive(Debug, Clone, Default, Deserialize)]
struct FileAgentsConfig {
// Keys match the agent's CLI invocation name (`claude`, `codex`, `cursor`, `hermes`) — the
// Keys match the agent's CLI invocation name (`claude`, `codex`, `hermes`) — the
// word the user types at the shell — not the product name ("Claude Code") or the internal
// `CodingAgent` enum kebab spelling. Same convention as the bare-agent shortcut in Phase 2.
claude: Option<FileAgentCommandConfig>,
codex: Option<FileAgentCommandConfig>,
cursor: Option<FileCursorAgentConfig>,
hermes: Option<FileAgentCommandConfig>,
}

Expand All @@ -702,12 +669,6 @@ struct FileAgentCommandConfig {
hooks_path: Option<PathBuf>,
}

#[derive(Debug, Clone, Default, Deserialize)]
struct FileCursorAgentConfig {
command: Option<String>,
patch_restore_hooks: Option<bool>,
}

impl Default for GatewayConfig {
// Supplies conservative local gateway defaults: bind only to loopback, route OpenAI and
// Anthropic requests to their public bases, and leave plugins disabled until config,
Expand Down Expand Up @@ -988,8 +949,7 @@ pub(crate) fn user_config_dir() -> Option<PathBuf> {
}

// Applies the typed TOML config model to the resolved runtime config. Missing sections and fields
// are ignored, preserving defaults and prior merge layers; Cursor's patch-restore flag is only
// changed when explicitly present.
// are ignored, preserving defaults and prior merge layers.
fn apply_file_config(resolved: &mut ResolvedConfig, value: toml::Value) -> Result<(), CliError> {
let config: FileConfig = value.try_into().map_err(|error| {
CliError::Config(format!("invalid gateway configuration shape: {error}"))
Expand Down Expand Up @@ -1266,9 +1226,7 @@ fn apply_cli_plugin_config(config: &mut GatewayConfig, value: &str) -> Result<()
Ok(())
}

// Applies configured agent commands and Cursor's temporary-hook behavior. Cursor's
// `patch_restore_hooks` flag is intentionally tri-state in file config so omitted values preserve
// the safe default while explicit `false` disables temporary hook mutation.
// Applies configured agent commands from the merged file configuration.
fn apply_file_agents_config(agents: &mut AgentConfigs, file_agents: Option<FileAgentsConfig>) {
let Some(file_agents) = file_agents else {
return;
Expand All @@ -1279,12 +1237,6 @@ fn apply_file_agents_config(agents: &mut AgentConfigs, file_agents: Option<FileA
if let Some(value) = file_agents.codex {
agents.codex.command = value.command;
}
if let Some(value) = file_agents.cursor {
agents.cursor.command = value.command;
if let Some(patch_restore_hooks) = value.patch_restore_hooks {
agents.cursor.patch_restore_hooks = patch_restore_hooks;
}
}
if let Some(value) = file_agents.hermes {
agents.hermes.command = value.command;
agents.hermes.hooks_path = value.hooks_path;
Expand Down Expand Up @@ -1411,7 +1363,6 @@ impl CodingAgent {
match self {
Self::ClaudeCode => "/hooks/claude-code",
Self::Codex => "/hooks/codex",
Self::Cursor => "/hooks/cursor",
Self::Hermes => "/hooks/hermes",
}
}
Expand All @@ -1423,7 +1374,6 @@ impl CodingAgent {
match self {
Self::ClaudeCode => "claude",
Self::Codex => "codex",
Self::Cursor => "cursor",
Self::Hermes => "hermes",
}
}
Expand All @@ -1438,7 +1388,6 @@ impl CodingAgent {
match name {
"claude" | "claude-code" => Some(Self::ClaudeCode),
"codex" => Some(Self::Codex),
"cursor" | "cursor-agent" => Some(Self::Cursor),
"hermes" | "hermes-agent" => Some(Self::Hermes),
_ => None,
}
Expand Down
Loading
Loading