From b395757e21d56724863af0a1800e439a4deb87fd Mon Sep 17 00:00:00 2001 From: Andrew Altshuler Date: Mon, 15 Jun 2026 15:23:03 +0300 Subject: [PATCH] feat(cli): alias subcommand; remove --alias flag (RFC-011 D4) (#244) Operator aliases move from the --alias flag on query/mutate to a dedicated 'omnigraph alias [args]' subcommand, so an alias can never shadow or be shadowed by a built-in verb. Unknown name errors listing defined aliases. Removes the legacy alias machinery from query/mutate (net -156 lines); legacy omnigraph.yaml aliases lose their CLI entry point. --- crates/omnigraph-cli/src/cli.rs | 42 +++-- crates/omnigraph-cli/src/helpers.rs | 75 -------- crates/omnigraph-cli/src/main.rs | 189 ++++++--------------- crates/omnigraph-cli/src/planes.rs | 2 + crates/omnigraph-cli/tests/cli_data.rs | 7 +- crates/omnigraph-cli/tests/cli_queries.rs | 156 +++-------------- crates/omnigraph-cli/tests/system_local.rs | 5 +- docs/user/cli/index.md | 5 +- docs/user/cli/reference.md | 26 +-- 9 files changed, 128 insertions(+), 379 deletions(-) diff --git a/crates/omnigraph-cli/src/cli.rs b/crates/omnigraph-cli/src/cli.rs index 3e7f394..86d08f4 100644 --- a/crates/omnigraph-cli/src/cli.rs +++ b/crates/omnigraph-cli/src/cli.rs @@ -99,12 +99,10 @@ pub(crate) enum Command { legacy_uri: Option, #[arg(long)] config: Option, - #[arg(long, conflicts_with_all = ["query", "query_string"])] - alias: Option, - #[arg(long, conflicts_with_all = ["alias", "query_string"])] + #[arg(long, conflicts_with = "query_string")] query: Option, - /// Inline GQ source — alternative to `--query ` and `--alias `. - #[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with_all = ["query", "alias"])] + /// Inline GQ source — alternative to `--query `. + #[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with = "query")] query_string: Option, #[arg(long)] name: Option, @@ -118,8 +116,6 @@ pub(crate) enum Command { format: Option, #[arg(long, conflicts_with = "format")] json: bool, - #[arg()] - alias_args: Vec, }, /// Execute a graph mutation query against a branch. /// @@ -135,12 +131,10 @@ pub(crate) enum Command { legacy_uri: Option, #[arg(long)] config: Option, - #[arg(long, conflicts_with_all = ["query", "query_string"])] - alias: Option, - #[arg(long, conflicts_with_all = ["alias", "query_string"])] + #[arg(long, conflicts_with = "query_string")] query: Option, - /// Inline GQ source — alternative to `--query ` and `--alias `. - #[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with_all = ["query", "alias"])] + /// Inline GQ source — alternative to `--query `. + #[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with = "query")] query_string: Option, #[arg(long)] name: Option, @@ -150,8 +144,28 @@ pub(crate) enum Command { branch: Option, #[arg(long)] json: bool, - #[arg()] - alias_args: Vec, + }, + /// Invoke an operator alias (RFC-011 Decision 4). + /// + /// An alias is a personal binding under `aliases:` in + /// ~/.omnigraph/config.yaml — name → (server, graph, stored-query name, + /// default params). `omnigraph alias [args]` invokes the bound + /// stored query on its server. Living in its own namespace, an alias can + /// never shadow or be shadowed by a built-in verb. Replaces the removed + /// `--alias` flag on `query`/`mutate`. + Alias { + /// Alias name (a key under `aliases:` in ~/.omnigraph/config.yaml). + name: String, + /// Positional args bound to the alias's declared `args` params, in order. + args: Vec, + #[arg(long)] + config: Option, + #[command(flatten)] + params: ParamsArgs, + #[arg(long, conflicts_with = "json")] + format: Option, + #[arg(long, conflicts_with = "format")] + json: bool, }, /// Load data into a graph (local or remote) Load { diff --git a/crates/omnigraph-cli/src/helpers.rs b/crates/omnigraph-cli/src/helpers.rs index 58817da..a8bcab8 100644 --- a/crates/omnigraph-cli/src/helpers.rs +++ b/crates/omnigraph-cli/src/helpers.rs @@ -729,44 +729,6 @@ pub(crate) fn parse_alias_value(value: &str) -> Value { serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_string())) } -pub(crate) fn merged_params_json( - alias_name: Option<&str>, - alias_arg_names: &[String], - alias_arg_values: &[String], - explicit: Option, -) -> Result> { - if alias_arg_values.len() > alias_arg_names.len() { - let alias = alias_name.unwrap_or(""); - bail!( - "alias '{}' expects at most {} args but got {}", - alias, - alias_arg_names.len(), - alias_arg_values.len() - ); - } - - let mut merged = serde_json::Map::new(); - for (arg_name, arg_value) in alias_arg_names.iter().zip(alias_arg_values.iter()) { - merged.insert(arg_name.clone(), parse_alias_value(arg_value)); - } - - match explicit { - Some(Value::Object(object)) => { - for (key, value) in object { - merged.insert(key, value); - } - } - Some(_) => bail!("params JSON must be an object"), - None => {} - } - - if merged.is_empty() { - Ok(None) - } else { - Ok(Some(Value::Object(merged))) - } -} - /// The format cascade (RFC-007 §D3): `--json` > `--format` > alias format > /// legacy `cli.output_format` (RFC-008 window) > operator `defaults.output` /// > table. @@ -790,43 +752,6 @@ pub(crate) fn resolve_read_format( .unwrap_or_default() } -pub(crate) fn resolve_alias<'a>( - config: &'a OmnigraphConfig, - alias_name: Option<&'a str>, - expected: AliasCommand, -) -> Result> { - let Some(alias_name) = alias_name else { - return Ok(None); - }; - let alias = config.alias(alias_name)?; - if alias.command != expected { - bail!( - "alias '{}' is a {:?} alias, not a {:?} alias", - alias_name, - alias.command, - expected - ); - } - Ok(Some((alias_name, alias))) -} - -pub(crate) fn normalize_legacy_alias_uri( - uri: Option, - target_available: bool, - alias_name: Option<&str>, - mut alias_args: Vec, -) -> (Option, Vec) { - let Some(candidate) = uri else { - return (None, alias_args); - }; - - if alias_name.is_some() && target_available { - alias_args.insert(0, candidate); - return (None, alias_args); - } - - (Some(candidate), alias_args) -} pub(crate) fn read_target_from_cli(branch: Option, snapshot: Option) -> ReadTarget { diff --git a/crates/omnigraph-cli/src/main.rs b/crates/omnigraph-cli/src/main.rs index 7d50c0a..a2c30c5 100644 --- a/crates/omnigraph-cli/src/main.rs +++ b/crates/omnigraph-cli/src/main.rs @@ -28,7 +28,7 @@ use omnigraph_api_types::{ }; use omnigraph_server::queries::{QueryRegistry, check, format_check_breakages}; use omnigraph_server::{ - AliasCommand, OmnigraphConfig, PolicyAction, PolicyDecision, PolicyEngine, PolicyRequest, + OmnigraphConfig, PolicyAction, PolicyDecision, PolicyEngine, PolicyRequest, PolicyTestConfig, ReadOutputFormat, graph_resource_id_for_selection, load_config, }; use reqwest::Method; @@ -569,7 +569,6 @@ async fn main() -> Result<()> { uri, legacy_uri, config, - alias, query, query_string, name, @@ -578,182 +577,61 @@ async fn main() -> Result<()> { snapshot, format, json, - alias_args, } => { - if alias.is_none() && query.is_none() && query_string.is_none() { - bail!("exactly one of --query, --query-string, or --alias must be provided"); + if query.is_none() && query_string.is_none() { + bail!("provide a query: --query or -e ''"); } let config = load_cli_config(config.as_ref())?; - // Operator aliases (RFC-007 PR 3): pure bindings to stored - // queries. A legacy file-alias with the same name wins during - // the RFC-008 window (with a warning); an alias name found - // only in the operator layer takes the invoke path here. - if let Some(alias_name) = alias.as_deref() { - let operator_config = crate::operator::load_operator_config()?; - if let Some(operator_alias) = operator_config.aliases.get(alias_name) { - if config.alias(alias_name).is_ok() { - eprintln!( - "warning: alias '{alias_name}' is defined in both omnigraph.yaml (legacy, wins during the deprecation window) and the operator config; the legacy definition applies" - ); - } else { - // The hidden legacy-uri positional swallows the first - // bare arg; an operator alias always knows its target, - // so reclaim it as the first positional param. - let (_, alias_args) = normalize_legacy_alias_uri( - legacy_uri.clone(), - true, - Some(alias_name), - alias_args.clone(), - ); - let output = execute_operator_alias( - &http_client, - &config, - alias_name, - operator_alias, - &alias_args, - load_params_json(¶ms)?, - ) - .await?; - let format = - resolve_read_format(&config, format, json, operator_alias.format); - print_read_output(&output, format, &config)?; - return Ok(()); - } - } - } - let alias = resolve_alias(&config, alias.as_deref(), AliasCommand::Read)?; - let alias_name = alias.as_ref().map(|(name, _)| *name); - let alias_config = alias.as_ref().map(|(_, alias)| *alias); - let alias_graph = alias_config.and_then(|alias| alias.graph.as_deref()); - let target_available = alias_graph.is_some() || config.cli_graph_name().is_some(); - let (legacy_uri, alias_args) = - normalize_legacy_alias_uri(legacy_uri, target_available, alias_name, alias_args); - // `--target` is gone; resolve an alias's legacy `graph` name to its - // URI (a positional URI still wins). - let uri = match uri.or(legacy_uri) { - Some(uri) => Some(uri), - None => match alias_graph { - Some(name) => Some(config.resolve_target_uri(None, Some(name), None)?), - None => None, - }, - }; let client = client::GraphClient::resolve( &config, cli.server.as_deref(), cli.graph.as_deref(), - uri, + uri.or(legacy_uri), cli.profile.as_deref(), cli.store.as_deref(), )?; - let query_source = resolve_query_source( - &config, - query.as_ref(), - query_string.as_deref(), - alias_config.map(|a| a.query.as_str()), - )?; - let params_json = merged_params_json( - alias_name, - alias_config - .map(|alias| alias.args.as_slice()) - .unwrap_or(&[]), - &alias_args, - load_params_json(¶ms)?, - )?; - let target = resolve_read_target( - &config, - branch, - snapshot, - alias_config.and_then(|alias| alias.branch.clone()), - )?; - let query_name = name.or_else(|| alias_config.and_then(|alias| alias.name.clone())); + let query_source = + resolve_query_source(&config, query.as_ref(), query_string.as_deref(), None)?; + let params_json = load_params_json(¶ms)?; + let target = resolve_read_target(&config, branch, snapshot, None)?; let output = client - .query( - target, - &query_source, - query_name.as_deref(), - params_json.as_ref(), - ) + .query(target, &query_source, name.as_deref(), params_json.as_ref()) .await?; - let format = resolve_read_format( - &config, - format, - json, - alias_config.and_then(|alias| alias.format), - ); + let format = resolve_read_format(&config, format, json, None); print_read_output(&output, format, &config)?; } Command::Mutate { uri, legacy_uri, config, - alias, query, query_string, name, params, branch, json, - alias_args, } => { - if alias.is_none() && query.is_none() && query_string.is_none() { - bail!("exactly one of --query, --query-string, or --alias must be provided"); + if query.is_none() && query_string.is_none() { + bail!("provide a mutation query: --query or -e ''"); } let config = load_cli_config(config.as_ref())?; - let alias = resolve_alias(&config, alias.as_deref(), AliasCommand::Change)?; - let alias_name = alias.as_ref().map(|(name, _)| *name); - let alias_config = alias.as_ref().map(|(_, alias)| *alias); - let alias_graph = alias_config.and_then(|alias| alias.graph.as_deref()); - let target_available = alias_graph.is_some() || config.cli_graph_name().is_some(); - let (legacy_uri, alias_args) = - normalize_legacy_alias_uri(legacy_uri, target_available, alias_name, alias_args); - // `--target` is gone; resolve an alias's legacy `graph` name to its - // URI (a positional URI still wins). - let uri = match uri.or(legacy_uri) { - Some(uri) => Some(uri), - None => match alias_graph { - Some(name) => Some(config.resolve_target_uri(None, Some(name), None)?), - None => None, - }, - }; let client = client::GraphClient::resolve_with_policy( &config, cli.server.as_deref(), cli.graph.as_deref(), - uri, + uri.or(legacy_uri), cli.as_actor.as_deref(), cli.profile.as_deref(), cli.store.as_deref(), )?; - let query_source = resolve_query_source( - &config, - query.as_ref(), - query_string.as_deref(), - alias_config.map(|a| a.query.as_str()), - )?; - let params_json = merged_params_json( - alias_name, - alias_config - .map(|alias| alias.args.as_slice()) - .unwrap_or(&[]), - &alias_args, - load_params_json(¶ms)?, - )?; - let branch = resolve_branch( - &config, - branch, - alias_config.and_then(|alias| alias.branch.clone()), - "main", - ); - let query_name = name.or_else(|| alias_config.and_then(|alias| alias.name.clone())); + let query_source = + resolve_query_source(&config, query.as_ref(), query_string.as_deref(), None)?; + let params_json = load_params_json(¶ms)?; + let branch = resolve_branch(&config, branch, None, "main"); let output = client - .mutate( - &branch, - &query_source, - query_name.as_deref(), - params_json.as_ref(), - ) + .mutate(&branch, &query_source, name.as_deref(), params_json.as_ref()) .await?; if json { print_json(&output)?; @@ -761,6 +639,37 @@ async fn main() -> Result<()> { print_change_human(&output); } } + Command::Alias { + name, + args, + config, + params, + format, + json, + } => { + let config = load_cli_config(config.as_ref())?; + let operator_config = crate::operator::load_operator_config()?; + let Some(operator_alias) = operator_config.aliases.get(&name) else { + let defined: Vec<&str> = + operator_config.aliases.keys().map(String::as_str).collect(); + bail!( + "unknown alias '{name}'; defined aliases: [{}] \ + (add it under `aliases:` in ~/.omnigraph/config.yaml)", + defined.join(", ") + ); + }; + let output = execute_operator_alias( + &http_client, + &config, + &name, + operator_alias, + &args, + load_params_json(¶ms)?, + ) + .await?; + let format = resolve_read_format(&config, format, json, operator_alias.format); + print_read_output(&output, format, &config)?; + } Command::Policy { command } => match command { PolicyCommand::Validate { config } => { let config = load_cli_config(config.as_ref())?; diff --git a/crates/omnigraph-cli/src/planes.rs b/crates/omnigraph-cli/src/planes.rs index 792cab4..1cfefc1 100644 --- a/crates/omnigraph-cli/src/planes.rs +++ b/crates/omnigraph-cli/src/planes.rs @@ -106,6 +106,7 @@ pub(crate) fn command_plane(cmd: &Command) -> Plane { match cmd { Command::Query { .. } | Command::Mutate { .. } + | Command::Alias { .. } | Command::Load { .. } | Command::Ingest { .. } | Command::Branch { .. } @@ -168,6 +169,7 @@ pub(crate) fn command_label(cmd: &Command) -> &'static str { Command::Commit { .. } => "commit", Command::Query { .. } => "query", Command::Mutate { .. } => "mutate", + Command::Alias { .. } => "alias", Command::Policy { .. } => "policy", Command::Optimize { .. } => "optimize", Command::Repair { .. } => "repair", diff --git a/crates/omnigraph-cli/tests/cli_data.rs b/crates/omnigraph-cli/tests/cli_data.rs index 7240ad2..edb9c6d 100644 --- a/crates/omnigraph-cli/tests/cli_data.rs +++ b/crates/omnigraph-cli/tests/cli_data.rs @@ -505,10 +505,9 @@ query list_people() { #[test] fn deprecated_read_and_change_subcommands_emit_warnings() { - // Both subcommands require `--query`/`--query-string`/`--alias`, so - // invoking them with no args will exit non-zero. That's fine -- - // we only care that the deprecation warning is printed before the - // argument-required error. + // Both subcommands require `--query`/`--query-string`, so invoking them + // with no args will exit non-zero. That's fine -- we only care that the + // deprecation warning is printed before the argument-required error. let output = cli().arg("read").output().unwrap(); let stderr = String::from_utf8(output.stderr).unwrap(); assert!( diff --git a/crates/omnigraph-cli/tests/cli_queries.rs b/crates/omnigraph-cli/tests/cli_queries.rs index 2f2ff00..3bb9463 100644 --- a/crates/omnigraph-cli/tests/cli_queries.rs +++ b/crates/omnigraph-cli/tests/cli_queries.rs @@ -2,7 +2,6 @@ //! Moved verbatim from tests/cli.rs in the modularization. -use serde_json::Value; use tempfile::tempdir; mod support; @@ -57,141 +56,42 @@ query list_people() { assert_eq!(stdout_string(&lint_output), stdout_string(&check_output)); } +// Legacy `omnigraph.yaml` `aliases:` invoked via the `--alias` flag were +// removed in RFC-011 D4 — operator aliases now live under `omnigraph alias +// ` (the happy path is covered by system_local's operator-alias e2e). +// The legacy file-alias path has no CLI entry point. + #[test] -fn read_alias_from_yaml_config_runs_with_kv_output() { - let temp = tempdir().unwrap(); - let graph = graph_path(temp.path()); - let config = temp.path().join("omnigraph.yaml"); - let query = temp.path().join("aliases.gq"); - init_graph(&graph); - load_fixture(&graph); - write_query_file( - &query, - &std::fs::read_to_string(fixture("test.gq")).unwrap(), +fn alias_flag_is_removed_from_query() { + // RFC-011 D4: `--alias` no longer exists on query/mutate; use `alias `. + let output = output_failure(cli().arg("query").arg("--alias").arg("who")); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("unexpected argument") && stderr.contains("--alias"), + "expected clap to reject --alias on query; got: {stderr}" ); - write_config( - &config, - &format!( - "{}aliases:\n owner:\n command: read\n query: aliases.gq\n name: get_person\n args: [name]\n format: kv\n", - local_yaml_config(&graph) - ), - ); - - let output = output_success( - cli() - .arg("read") - .arg("--config") - .arg(&config) - .arg("--alias") - .arg("owner") - .arg("Alice"), - ); - let stdout = stdout_string(&output); - - assert!(stdout.contains("row 1")); - assert!(stdout.contains("p.name: Alice")); } #[test] -fn read_alias_uses_alias_target_without_cli_default_and_accepts_url_like_arg() { - let temp = tempdir().unwrap(); - let graph = graph_path(temp.path()); - let config = temp.path().join("omnigraph.yaml"); - let query = temp.path().join("aliases.gq"); - let data = temp.path().join("url-like.jsonl"); - init_graph(&graph); - write_jsonl( - &data, - r#"{"type":"Person","data":{"name":"https://example.com","age":30}}"#, - ); - output_success( +fn alias_unknown_name_errors_listing_defined() { + // Hermetic: an unknown alias fails before any network, listing defined ones. + let home = tempdir().unwrap(); + std::fs::write( + home.path().join("config.yaml"), + "servers:\n dev:\n url: https://x\naliases:\n who:\n server: dev\n query: find_person\n", + ) + .unwrap(); + let output = output_failure( cli() - .arg("load") - .arg("--mode") - .arg("overwrite") - .arg("--data") - .arg(&data) - .arg(&graph), + .env("OMNIGRAPH_HOME", home.path()) + .arg("alias") + .arg("nope"), ); - write_query_file( - &query, - &std::fs::read_to_string(fixture("test.gq")).unwrap(), + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("unknown alias 'nope'") && stderr.contains("who"), + "expected an unknown-alias error listing defined aliases; got: {stderr}" ); - write_config( - &config, - &format!( - "graphs:\n local:\n uri: '{}'\nquery:\n roots:\n - .\npolicy: {{}}\naliases:\n owner:\n command: read\n query: aliases.gq\n name: get_person\n args: [name]\n graph: local\n format: kv\n", - graph.to_string_lossy() - ), - ); - - let output = output_success( - cli() - .arg("read") - .arg("--config") - .arg(&config) - .arg("--alias") - .arg("owner") - .arg("https://example.com"), - ); - let stdout = stdout_string(&output); - - assert!(stdout.contains("row 1")); - assert!(stdout.contains("p.name: https://example.com")); -} - -#[test] -fn change_alias_from_yaml_config_persists_changes() { - let temp = tempdir().unwrap(); - let graph = graph_path(temp.path()); - let config = temp.path().join("omnigraph.yaml"); - let query = temp.path().join("mutations.gq"); - init_graph(&graph); - load_fixture(&graph); - write_query_file( - &query, - r#" -query insert_person($name: String, $age: I32) { - insert Person { name: $name, age: $age } -} -"#, - ); - write_config( - &config, - &format!( - "{}aliases:\n add_person:\n command: change\n query: mutations.gq\n name: insert_person\n args: [name, age]\n", - local_yaml_config(&graph) - ), - ); - - let output = output_success( - cli() - .arg("change") - .arg("--config") - .arg(&config) - .arg("--alias") - .arg("add_person") - .arg("Eve") - .arg("29") - .arg("--json"), - ); - let payload: Value = serde_json::from_slice(&output.stdout).unwrap(); - assert_eq!(payload["affected_nodes"], 1); - - let verify = output_success( - cli() - .arg("read") - .arg(&graph) - .arg("--query") - .arg(fixture("test.gq")) - .arg("--name") - .arg("get_person") - .arg("--params") - .arg(r#"{"name":"Eve"}"#) - .arg("--json"), - ); - let verify_payload: Value = serde_json::from_slice(&verify.stdout).unwrap(); - assert_eq!(verify_payload["row_count"], 1); } #[test] diff --git a/crates/omnigraph-cli/tests/system_local.rs b/crates/omnigraph-cli/tests/system_local.rs index b6a87f1..b357c74 100644 --- a/crates/omnigraph-cli/tests/system_local.rs +++ b/crates/omnigraph-cli/tests/system_local.rs @@ -2480,12 +2480,11 @@ fn local_cli_operator_alias_and_server_flag_invoke_stored_query() { .unwrap(); } - // The operator alias: name + positional arg, nothing else — server, + // The operator alias (RFC-011 D4): `alias [args]` — server, // graph, stored query, and token all resolve from the operator layer. let output = cli() .env("OMNIGRAPH_HOME", operator_home.path()) - .arg("query") - .arg("--alias") + .arg("alias") .arg("who") .arg("Alice") .arg("--json") diff --git a/docs/user/cli/index.md b/docs/user/cli/index.md index 6df606c..7eb50cf 100644 --- a/docs/user/cli/index.md +++ b/docs/user/cli/index.md @@ -30,8 +30,9 @@ omnigraph mutate --uri graph.omni \ --params '{"name":"Inline","age":42}' ``` -`-e` is mutually exclusive with `--query ` and `--alias `; exactly -one of the three must be provided. The inline source travels through the same +`-e` is mutually exclusive with `--query `; exactly one of the two must be +provided. (Operator aliases moved to their own `omnigraph alias ` +subcommand — RFC-011 D4.) The inline source travels through the same parser, lint, params binding, and commit machinery as a file-based query — only the source loader changes. diff --git a/docs/user/cli/reference.md b/docs/user/cli/reference.md index 1e87e46..711a59d 100644 --- a/docs/user/cli/reference.md +++ b/docs/user/cli/reference.md @@ -11,8 +11,9 @@ Top-level command families and subcommands. Graph-targeting commands accept a po | `init` | `--schema ` → initialize a graph (no longer scaffolds `omnigraph.yaml`; start cluster configs from the [cluster.md](../clusters/index.md) quick-start or `config migrate`) | | `load` | bulk load a branch, local or remote (`--mode overwrite\|append\|merge` is **required** — overwrite is destructive, so there is no default). Without `--from` the target branch must exist; `--from ` forks a missing `--branch` from `` first | | `ingest` | deprecated alias of `load --from ` (defaults: `--from main --mode merge`); prints a one-line warning to stderr | -| `query` (alias: `read`) | run named read query; source via `--query `, `-e`/`--query-string `, or `--alias ` (exactly one). `read` is the deprecated previous name and prints a one-line warning to stderr | -| `mutate` (alias: `change`) | run mutation query; same `--query` / `-e` / `--alias` mutual-exclusion as `query`. `change` is the deprecated previous name and prints a one-line warning to stderr | +| `query` (alias: `read`) | run a read query; source via `--query ` or `-e`/`--query-string `. `read` is the deprecated previous name and prints a one-line warning to stderr | +| `mutate` (alias: `change`) | run a mutation query; same `--query` / `-e` source as `query`. `change` is the deprecated previous name and prints a one-line warning to stderr | +| `alias [args]` | invoke an operator alias — a personal binding (under `aliases:` in `~/.omnigraph/config.yaml`) to a stored query on a named server (RFC-011 D4; replaces the removed `--alias` flag) | | `snapshot` | print current snapshot (per-table version + row count) | | `export` | dump to JSONL on stdout (`--type T`, `--table K` filters) | | `branch create \| list \| delete \| merge` | branching ops | @@ -146,10 +147,12 @@ aliases: format: table ``` -`omnigraph query --alias triage 2026-06-01` invokes +`omnigraph alias triage 2026-06-01` invokes `POST /graphs/spike/queries/weekly_triage` with the keyed -credential. A legacy `omnigraph.yaml` alias with the same name wins during -the deprecation window (with a warning). +credential. Aliases live in their own `alias` namespace (RFC-011 Decision 4), +so an alias can never shadow — or be shadowed by — a built-in verb. (The old +`--alias ` flag on `query`/`mutate` was removed; legacy `omnigraph.yaml` +`aliases:` no longer have a CLI entry point.) A remote command whose URL prefix-matches an operator server's `url` (the `gh` host model — no flags needed) resolves its token through: @@ -199,14 +202,11 @@ query: roots: [, …] # search path for .gq files auth: env_file: .env.omni -aliases: - : - # accepted values: `read` / `query` (read alias), `change` / `mutate` - # (write alias). `query` and `mutate` are recommended; `read` and - # `change` remain accepted forever for back-compat. - command: read|change|query|mutate - query: - name: +aliases: # legacy file-aliases — parsed but no longer + : # reachable from the CLI (RFC-011 D4 removed + command: read|change|query|mutate # the `--alias` flag). Use operator + query: # aliases (`~/.omnigraph/config.yaml` + name: # `aliases:`) via `omnigraph alias `. args: [, …] graph: branch: