From 2b33ab64f2a33260c917bbe3bfe13f1abacfbc5e Mon Sep 17 00:00:00 2001 From: aaltshuler Date: Thu, 11 Jun 2026 22:19:25 +0300 Subject: [PATCH] feat(cli): --server targeting (RFC-007 PR 3, part 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Global flags --server (operator-defined server name) and --graph (graph id on a multi-graph server, requires --server) resolve to the effective remote URI through one helper and feed the ordinary uri slot — graph resolution and the PR-2 keyed-token URL match work unchanged; the flag is sugar for a URI the operator already owns. Exclusive with a positional URI and --target (loud error, never silent precedence). Unknown names fail listing the servers that ARE defined. Co-Authored-By: Claude Fable 5 --- crates/omnigraph-cli/src/cli.rs | 11 ++++++ crates/omnigraph-cli/src/helpers.rs | 51 ++++++++++++++++++++++++++++ crates/omnigraph-cli/src/main.rs | 28 +++++++++++++++ crates/omnigraph-cli/src/operator.rs | 19 +++++++++++ 4 files changed, 109 insertions(+) diff --git a/crates/omnigraph-cli/src/cli.rs b/crates/omnigraph-cli/src/cli.rs index 7708c0a..feb08e8 100644 --- a/crates/omnigraph-cli/src/cli.rs +++ b/crates/omnigraph-cli/src/cli.rs @@ -21,6 +21,17 @@ pub(crate) struct Cli { #[arg(long = "as", global = true, value_name = "ACTOR")] pub(crate) as_actor: Option, + /// Target an operator-defined server by name (RFC-007): resolves to + /// its `url` from `servers:` in ~/.omnigraph/config.yaml. Exclusive + /// with a positional URI or `--target`. + #[arg(long, global = true, value_name = "NAME")] + pub(crate) server: Option, + + /// Graph id on a multi-graph `--server` (appends `/graphs/` to + /// the server url). Requires --server. + #[arg(long, global = true, value_name = "GRAPH_ID", requires = "server")] + pub(crate) graph: Option, + #[command(subcommand)] pub(crate) command: Command, } diff --git a/crates/omnigraph-cli/src/helpers.rs b/crates/omnigraph-cli/src/helpers.rs index b837192..7adac16 100644 --- a/crates/omnigraph-cli/src/helpers.rs +++ b/crates/omnigraph-cli/src/helpers.rs @@ -264,6 +264,57 @@ pub(crate) fn resolve_remote_bearer_token( Ok(None) } +/// `--server ` (RFC-007 PR 3): resolve an operator-defined server +/// name (+ optional `--graph` for multi-graph servers) to the effective +/// remote URI. The result feeds the ordinary `uri` slot, so graph +/// resolution and the keyed-token URL match work unchanged — the flag is +/// sugar for a URI the operator already owns. Unknown names fail loudly, +/// listing what IS defined. +pub(crate) fn resolve_server_flag( + server: Option<&str>, + graph: Option<&str>, +) -> Result> { + let Some(server) = server else { + return Ok(None); + }; + let operator_config = operator::load_operator_config()?; + let Some(entry) = operator_config.servers.get(server) else { + let known = operator_config + .servers + .keys() + .map(String::as_str) + .collect::>() + .join(", "); + color_eyre::eyre::bail!( + "unknown server '{server}' — servers defined in the operator config: [{known}] (add it under servers: in ~/.omnigraph/config.yaml)" + ); + }; + let base = entry.url.trim_end_matches('/'); + Ok(Some(match graph { + Some(graph) => format!("{base}/graphs/{graph}"), + None => base.to_string(), + })) +} + +/// Apply `--server`/`--graph` to a command's uri/target slots: exclusive +/// with both (loud error, not silent precedence), no-op when absent. +pub(crate) fn apply_server_flag( + server: Option<&str>, + graph: Option<&str>, + uri: Option, + target: Option<&str>, +) -> Result> { + if server.is_none() { + return Ok(uri); + } + if uri.is_some() || target.is_some() { + color_eyre::eyre::bail!( + "--server is exclusive with a positional URI and --target — pick one way to address the graph" + ); + } + resolve_server_flag(server, graph) +} + /// The remote base URL a token resolution is FOR — the same scoping /// `graph_bearer_token_env` uses: an explicit http(s) `--uri` wins, else /// the config-resolved target's uri (when remote). Local URIs → None. diff --git a/crates/omnigraph-cli/src/main.rs b/crates/omnigraph-cli/src/main.rs index 85fe537..0ee3851 100644 --- a/crates/omnigraph-cli/src/main.rs +++ b/crates/omnigraph-cli/src/main.rs @@ -130,6 +130,8 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let graph = resolve_cli_graph(&config, uri, target.as_deref())?; @@ -198,6 +200,8 @@ async fn main() -> Result<()> { use `omnigraph load --from --mode ` (ingest defaults: --from main --mode merge)" ); let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let graph = resolve_cli_graph(&config, uri, target.as_deref())?; @@ -250,6 +254,8 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let graph = resolve_cli_graph(&config, uri, target.as_deref())?; @@ -293,6 +299,8 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let graph = resolve_cli_graph(&config, uri, target.as_deref())?; @@ -328,6 +336,8 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let graph = resolve_cli_graph(&config, uri, target.as_deref())?; @@ -367,6 +377,8 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let graph = resolve_cli_graph(&config, uri, target.as_deref())?; @@ -417,6 +429,8 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let uri = resolve_uri(&config, uri, target.as_deref())?; @@ -456,6 +470,8 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let uri = resolve_uri(&config, uri, target.as_deref())?; @@ -519,6 +535,8 @@ async fn main() -> Result<()> { allow_data_loss, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let graph = resolve_cli_graph(&config, uri, target.as_deref())?; @@ -576,6 +594,8 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let uri = resolve_uri(&config, uri, target.as_deref())?; @@ -640,6 +660,8 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let uri = resolve_uri(&config, uri, target.as_deref())?; @@ -675,6 +697,8 @@ async fn main() -> Result<()> { table_keys, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let uri = resolve_uri(&config, uri, target.as_deref())?; @@ -736,6 +760,7 @@ async fn main() -> Result<()> { let target_name = target .as_deref() .or_else(|| alias_config.and_then(|alias| alias.graph.as_deref())); + let uri = apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target_name)?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target_name)?; let graph = resolve_cli_graph(&config, uri, target_name)?; let uri = graph.uri.clone(); @@ -822,6 +847,7 @@ async fn main() -> Result<()> { let target_name = target .as_deref() .or_else(|| alias_config.and_then(|alias| alias.graph.as_deref())); + let uri = apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target_name)?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target_name)?; let graph = resolve_cli_graph(&config, uri, target_name)?; let uri = graph.uri.clone(); @@ -1177,6 +1203,8 @@ async fn main() -> Result<()> { json, } => { let config = load_cli_config(config.as_ref())?; + let uri = + apply_server_flag(cli.server.as_deref(), cli.graph.as_deref(), uri, target.as_deref())?; let bearer_token = resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?; let uri = resolve_uri(&config, uri, target.as_deref())?; diff --git a/crates/omnigraph-cli/src/operator.rs b/crates/omnigraph-cli/src/operator.rs index 1b95e24..64b5756 100644 --- a/crates/omnigraph-cli/src/operator.rs +++ b/crates/omnigraph-cli/src/operator.rs @@ -467,6 +467,25 @@ mod tests { assert_eq!(config.find_server_for_url("http://other:9999"), None); } + #[test] + fn server_lookup_supports_targeting() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("config.yaml"); + fs::write( + &path, + "servers:\n intel-dev:\n url: http://127.0.0.1:8080/\n", + ) + .unwrap(); + let config = load_operator_config_at(&path).unwrap(); + // the --server resolution shape: bare url and graph-scoped url + let base = config.servers["intel-dev"].url.trim_end_matches('/'); + assert_eq!(base, "http://127.0.0.1:8080"); + assert_eq!( + format!("{base}/graphs/spike"), + "http://127.0.0.1:8080/graphs/spike" + ); + } + #[test] fn token_env_name_uppercases_and_underscores() { assert_eq!(token_env_name("intel-dev"), "OMNIGRAPH_TOKEN_INTEL_DEV");