From 5d85536707bbf7d451875c055d777ee8b0b7817b Mon Sep 17 00:00:00 2001 From: openviking Date: Mon, 16 Feb 2026 11:49:00 +0800 Subject: [PATCH 1/2] fix: make rust CLI (ov) commands match python CLI (openviking) exactly - add top-level wait/status/health commands --- crates/ov_cli/src/main.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/crates/ov_cli/src/main.rs b/crates/ov_cli/src/main.rs index 0d2ac0c..e164974 100644 --- a/crates/ov_cli/src/main.rs +++ b/crates/ov_cli/src/main.rs @@ -125,6 +125,16 @@ enum Commands { #[arg(long)] no_vectorize: bool, }, + /// Wait for queued async processing to complete + Wait { + /// Wait timeout in seconds + #[arg(long)] + timeout: Option, + }, + /// Show OpenViking component status + Status, + /// Quick health check + Health, /// System utility commands System { #[command(subcommand)] @@ -363,6 +373,15 @@ async fn main() { Commands::Import { file_path, target_uri, force, no_vectorize } => { handle_import(file_path, target_uri, force, no_vectorize, ctx).await } + Commands::Wait { timeout } => { + let client = ctx.get_client(); + commands::system::wait(&client, timeout, ctx.output_format, ctx.compact).await + }, + Commands::Status => { + let client = ctx.get_client(); + commands::observer::system(&client, ctx.output_format, ctx.compact).await + }, + Commands::Health => handle_health(ctx).await, Commands::System { action } => handle_system(action, ctx).await, Commands::Observer { action } => handle_observer(action, ctx).await, Commands::Session { action } => handle_session(action, ctx).await, @@ -651,3 +670,14 @@ async fn handle_glob(pattern: String, uri: String, ctx: CliContext) -> Result<() let client = ctx.get_client(); commands::search::glob(&client, &pattern, &uri, ctx.output_format, ctx.compact).await } + +async fn handle_health(ctx: CliContext) -> Result<()> { + let client = ctx.get_client(); + let system_status: serde_json::Value = client.get("/api/v1/observer/system", &[]).await?; + let is_healthy = system_status.get("is_healthy").and_then(|v| v.as_bool()).unwrap_or(false); + output::output_success(&serde_json::json!({ "healthy": is_healthy }), ctx.output_format, ctx.compact); + if !is_healthy { + std::process::exit(1); + } + Ok(()) +} From 0e08354febd487cd2e3b874102a1ae97ae31c4d8 Mon Sep 17 00:00:00 2001 From: openviking Date: Mon, 16 Feb 2026 12:32:43 +0800 Subject: [PATCH 2/2] fix: ov cli plays same as py cli (ls, tree) --- README.md | 2 +- README_CN.md | 2 +- crates/ov_cli/src/client.rs | 16 ++++++++-- crates/ov_cli/src/commands/filesystem.rs | 12 ++++++-- crates/ov_cli/src/main.rs | 36 ++++++++++++++++++----- openviking/async_client.py | 7 ++++- openviking/client/local.py | 7 ++++- openviking/server/routers/filesystem.py | 9 +++++- openviking/service/fs_service.py | 15 ++++++++-- openviking/storage/viking_fs.py | 23 +++++++++++---- openviking_cli/cli/commands/filesystem.py | 13 +++++++- openviking_cli/client/base.py | 2 ++ openviking_cli/client/http.py | 4 +++ openviking_cli/client/sync_http.py | 11 +++++-- 14 files changed, 130 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index b8107d2..8f67481 100644 --- a/README.md +++ b/README.md @@ -581,7 +581,7 @@ Let's work together to define and build the future of AI Agent context managemen ### Star Trend -[![Star History Chart](https://api.star-history.com/svg?repos=volcengine/OpenViking&type=Timeline)](https://www.star-history.com/#volcengine/OpenViking&Timeline) +[![Star History Chart](https://api.star-history.com/svg?repos=volcengine/OpenViking&type=timeline&legend=top-left)](https://www.star-history.com/#volcengine/OpenViking&type=timeline&legend=top-left) --- diff --git a/README_CN.md b/README_CN.md index 0f2e76c..c09354e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -457,7 +457,7 @@ OpenViking 目前还处于早期阶段,有许多需要完善和探索的地方 ### Star 趋势 -[![Star History Chart](https://api.star-history.com/svg?repos=volcengine/OpenViking&type=Timeline)](https://www.star-history.com/#volcengine/OpenViking&Timeline) +[![Star History Chart](https://api.star-history.com/svg?repos=volcengine/OpenViking&type=timeline&legend=top-left)](https://www.star-history.com/#volcengine/OpenViking&type=timeline&legend=top-left) --- diff --git a/crates/ov_cli/src/client.rs b/crates/ov_cli/src/client.rs index 53dc3b7..2c2bcc8 100644 --- a/crates/ov_cli/src/client.rs +++ b/crates/ov_cli/src/client.rs @@ -191,17 +191,27 @@ impl HttpClient { // ============ Filesystem Methods ============ - pub async fn ls(&self, uri: &str, simple: bool, recursive: bool) -> Result { + pub async fn ls(&self, uri: &str, simple: bool, recursive: bool, output: &str, abs_limit: i32, show_all_hidden: bool, node_limit: i32) -> Result { let params = vec![ ("uri".to_string(), uri.to_string()), ("simple".to_string(), simple.to_string()), ("recursive".to_string(), recursive.to_string()), + ("output".to_string(), output.to_string()), + ("abs_limit".to_string(), abs_limit.to_string()), + ("show_all_hidden".to_string(), show_all_hidden.to_string()), + ("node_limit".to_string(), node_limit.to_string()), ]; self.get("/api/v1/fs/ls", ¶ms).await } - pub async fn tree(&self, uri: &str) -> Result { - let params = vec![("uri".to_string(), uri.to_string())]; + pub async fn tree(&self, uri: &str, output: &str, abs_limit: i32, show_all_hidden: bool, node_limit: i32) -> Result { + let params = vec![ + ("uri".to_string(), uri.to_string()), + ("output".to_string(), output.to_string()), + ("abs_limit".to_string(), abs_limit.to_string()), + ("show_all_hidden".to_string(), show_all_hidden.to_string()), + ("node_limit".to_string(), node_limit.to_string()), + ]; self.get("/api/v1/fs/tree", ¶ms).await } diff --git a/crates/ov_cli/src/commands/filesystem.rs b/crates/ov_cli/src/commands/filesystem.rs index 0034c1a..b281f1c 100644 --- a/crates/ov_cli/src/commands/filesystem.rs +++ b/crates/ov_cli/src/commands/filesystem.rs @@ -7,10 +7,14 @@ pub async fn ls( uri: &str, simple: bool, recursive: bool, + output: &str, + abs_limit: i32, + show_all_hidden: bool, + node_limit: i32, output_format: OutputFormat, compact: bool, ) -> Result<()> { - let result = client.ls(uri, simple, recursive).await?; + let result = client.ls(uri, simple, recursive, output, abs_limit, show_all_hidden, node_limit).await?; output_success(&result, output_format, compact); Ok(()) } @@ -18,10 +22,14 @@ pub async fn ls( pub async fn tree( client: &HttpClient, uri: &str, + output: &str, + abs_limit: i32, + show_all_hidden: bool, + node_limit: i32, output_format: OutputFormat, compact: bool, ) -> Result<()> { - let result = client.tree(uri).await?; + let result = client.tree(uri, output, abs_limit, show_all_hidden, node_limit).await?; output_success(&result, output_format, compact); Ok(()) } diff --git a/crates/ov_cli/src/main.rs b/crates/ov_cli/src/main.rs index e164974..b98aaf4 100644 --- a/crates/ov_cli/src/main.rs +++ b/crates/ov_cli/src/main.rs @@ -162,11 +162,29 @@ enum Commands { /// List all subdirectories recursively #[arg(short, long)] recursive: bool, + /// Abstract content limit (only for agent output) + #[arg(long = "abs-limit", short = 'l', default_value = "256")] + abs_limit: i32, + /// Show all hidden files + #[arg(short, long)] + all: bool, + /// Maximum number of nodes to list + #[arg(long = "node-limit", short = 'n', default_value = "1000")] + node_limit: i32, }, /// Get directory tree Tree { /// Viking URI to get tree for uri: String, + /// Abstract content limit (only for agent output) + #[arg(long = "abs-limit", short = 'l', default_value = "128")] + abs_limit: i32, + /// Show all hidden files + #[arg(short, long)] + all: bool, + /// Maximum number of nodes to list + #[arg(long = "node-limit", short = 'n', default_value = "1000")] + node_limit: i32, }, /// Create directory Mkdir { @@ -385,11 +403,11 @@ async fn main() { Commands::System { action } => handle_system(action, ctx).await, Commands::Observer { action } => handle_observer(action, ctx).await, Commands::Session { action } => handle_session(action, ctx).await, - Commands::Ls { uri, simple, recursive } => { - handle_ls(uri, simple, recursive, ctx).await + Commands::Ls { uri, simple, recursive, abs_limit, all, node_limit } => { + handle_ls(uri, simple, recursive, abs_limit, all, node_limit, ctx).await } - Commands::Tree { uri } => { - handle_tree(uri, ctx).await + Commands::Tree { uri, abs_limit, all, node_limit } => { + handle_tree(uri, abs_limit, all, node_limit, ctx).await } Commands::Mkdir { uri } => { handle_mkdir(uri, ctx).await @@ -631,14 +649,16 @@ async fn handle_search( commands::search::search(&client, &query, &uri, session_id, limit, threshold, ctx.output_format, ctx.compact).await } -async fn handle_ls(uri: String, simple: bool, recursive: bool, ctx: CliContext) -> Result<()> { +async fn handle_ls(uri: String, simple: bool, recursive: bool, abs_limit: i32, show_all_hidden: bool, node_limit: i32, ctx: CliContext) -> Result<()> { let client = ctx.get_client(); - commands::filesystem::ls(&client, &uri, simple, recursive, ctx.output_format, ctx.compact).await + let api_output = if ctx.compact { "agent" } else { "original" }; + commands::filesystem::ls(&client, &uri, simple, recursive, api_output, abs_limit, show_all_hidden, node_limit, ctx.output_format, ctx.compact).await } -async fn handle_tree(uri: String, ctx: CliContext) -> Result<()> { +async fn handle_tree(uri: String, abs_limit: i32, show_all_hidden: bool, node_limit: i32, ctx: CliContext) -> Result<()> { let client = ctx.get_client(); - commands::filesystem::tree(&client, &uri, ctx.output_format, ctx.compact).await + let api_output = if ctx.compact { "agent" } else { "original" }; + commands::filesystem::tree(&client, &uri, api_output, abs_limit, show_all_hidden, node_limit, ctx.output_format, ctx.compact).await } async fn handle_mkdir(uri: String, ctx: CliContext) -> Result<()> { diff --git a/openviking/async_client.py b/openviking/async_client.py index b4f48ed..84e3c7e 100644 --- a/openviking/async_client.py +++ b/openviking/async_client.py @@ -306,8 +306,13 @@ async def tree(self, uri: str, **kwargs) -> Dict: output = kwargs.get("output", "original") abs_limit = kwargs.get("abs_limit", 128) show_all_hidden = kwargs.get("show_all_hidden", True) + node_limit = kwargs.get("node_limit", 1000) return await self._client.tree( - uri, output=output, abs_limit=abs_limit, show_all_hidden=show_all_hidden + uri, + output=output, + abs_limit=abs_limit, + show_all_hidden=show_all_hidden, + node_limit=node_limit, ) async def mkdir(self, uri: str) -> None: diff --git a/openviking/client/local.py b/openviking/client/local.py index 46acca9..389439a 100644 --- a/openviking/client/local.py +++ b/openviking/client/local.py @@ -113,10 +113,15 @@ async def tree( output: str = "original", abs_limit: int = 128, show_all_hidden: bool = False, + node_limit: int = 1000, ) -> List[Dict[str, Any]]: """Get directory tree.""" return await self._service.fs.tree( - uri, output=output, abs_limit=abs_limit, show_all_hidden=show_all_hidden + uri, + output=output, + abs_limit=abs_limit, + show_all_hidden=show_all_hidden, + node_limit=node_limit, ) async def stat(self, uri: str) -> Dict[str, Any]: diff --git a/openviking/server/routers/filesystem.py b/openviking/server/routers/filesystem.py index 5e71fe6..a24e64c 100644 --- a/openviking/server/routers/filesystem.py +++ b/openviking/server/routers/filesystem.py @@ -22,6 +22,7 @@ async def ls( output: str = Query("agent", description="Output format: original or agent"), abs_limit: int = Query(256, description="Abstract limit (only for agent output)"), show_all_hidden: bool = Query(False, description="List all hidden files, like -a"), + node_limit: int = Query(1000, description="Maximum number of nodes to list"), _: bool = Depends(verify_api_key), ): """List directory contents.""" @@ -33,6 +34,7 @@ async def ls( output=output, abs_limit=abs_limit, show_all_hidden=show_all_hidden, + node_limit=node_limit, ) return Response(status="ok", result=result) @@ -43,12 +45,17 @@ async def tree( output: str = Query("agent", description="Output format: original or agent"), abs_limit: int = Query(256, description="Abstract limit (only for agent output)"), show_all_hidden: bool = Query(False, description="List all hidden files, like -a"), + node_limit: int = Query(1000, description="Maximum number of nodes to list"), _: bool = Depends(verify_api_key), ): """Get directory tree.""" service = get_service() result = await service.fs.tree( - uri, output=output, abs_limit=abs_limit, show_all_hidden=show_all_hidden + uri, + output=output, + abs_limit=abs_limit, + show_all_hidden=show_all_hidden, + node_limit=node_limit, ) return Response(status="ok", result=result) diff --git a/openviking/service/fs_service.py b/openviking/service/fs_service.py index 72e5526..598e0fc 100644 --- a/openviking/service/fs_service.py +++ b/openviking/service/fs_service.py @@ -39,6 +39,7 @@ async def ls( output: str = "original", abs_limit: int = 256, show_all_hidden: bool = False, + node_limit: int = 1000, ) -> List[Any]: """List directory contents. @@ -49,12 +50,17 @@ async def ls( output: str = "original" or "agent" abs_limit: int = 256 if output == "agent" else ignore show_all_hidden: bool = False (list all hidden files, like -a) + node_limit: int = 1000 (maximum number of nodes to list) """ viking_fs = self._ensure_initialized() if recursive: entries = await viking_fs.tree( - uri, output=output, abs_limit=abs_limit, show_all_hidden=show_all_hidden + uri, + output=output, + abs_limit=abs_limit, + show_all_hidden=show_all_hidden, + node_limit=node_limit, ) else: entries = await viking_fs.ls( @@ -86,11 +92,16 @@ async def tree( output: str = "original", abs_limit: int = 128, show_all_hidden: bool = False, + node_limit: int = 1000, ) -> List[Dict[str, Any]]: """Get directory tree.""" viking_fs = self._ensure_initialized() return await viking_fs.tree( - uri, output=output, abs_limit=abs_limit, show_all_hidden=show_all_hidden + uri, + output=output, + abs_limit=abs_limit, + show_all_hidden=show_all_hidden, + node_limit=node_limit, ) async def stat(self, uri: str) -> Dict[str, Any]: diff --git a/openviking/storage/viking_fs.py b/openviking/storage/viking_fs.py index 3f3f173..a8e52c7 100644 --- a/openviking/storage/viking_fs.py +++ b/openviking/storage/viking_fs.py @@ -201,9 +201,9 @@ async def stat(self, uri: str) -> Dict[str, Any]: path = self._uri_to_path(uri) return self.agfs.stat(path) - async def glob(self, pattern: str, uri: str = "viking://") -> Dict: + async def glob(self, pattern: str, uri: str = "viking://", node_limit: int = 1000) -> Dict: """File pattern matching, supports **/*.md recursive.""" - entries = await self.tree(uri) + entries = await self.tree(uri, node_limit=node_limit) base_uri = uri.rstrip("/") matches = [] for entry in entries: @@ -248,6 +248,7 @@ async def tree( output: str = "original", abs_limit: int = 256, show_all_hidden: bool = False, + node_limit: int = 1000, ) -> List[Dict[str, Any]]: """ Recursively list all contents (includes rel_path). @@ -265,19 +266,25 @@ async def tree( [{'name': '.abstract.md', 'size': 100, 'modTime': '2026-02-11 16:52:16', 'isDir': False, 'rel_path': '.abstract.md', 'uri': 'viking://resources...', 'abstract': "..."}] """ if output == "original": - return await self._tree_original(uri, show_all_hidden) + return await self._tree_original(uri, show_all_hidden, node_limit) elif output == "agent": - return await self._tree_agent(uri, abs_limit, show_all_hidden) + return await self._tree_agent(uri, abs_limit, show_all_hidden, node_limit) else: raise ValueError(f"Invalid output format: {output}") - async def _tree_original(self, uri: str, show_all_hidden: bool = False) -> List[Dict[str, Any]]: + async def _tree_original( + self, uri: str, show_all_hidden: bool = False, node_limit: int = 1000 + ) -> List[Dict[str, Any]]: """Recursively list all contents (original format).""" path = self._uri_to_path(uri) all_entries = [] async def _walk(current_path: str, current_rel: str): + if len(all_entries) >= node_limit: + return for entry in self.agfs.ls(current_path): + if len(all_entries) >= node_limit: + break name = entry.get("name", "") if name in [".", ".."]: continue @@ -297,7 +304,7 @@ async def _walk(current_path: str, current_rel: str): return all_entries async def _tree_agent( - self, uri: str, abs_limit: int, show_all_hidden: bool = False + self, uri: str, abs_limit: int, show_all_hidden: bool = False, node_limit: int = 1000 ) -> List[Dict[str, Any]]: """Recursively list all contents (agent format with abstracts).""" path = self._uri_to_path(uri) @@ -305,7 +312,11 @@ async def _tree_agent( now = datetime.now() async def _walk(current_path: str, current_rel: str): + if len(all_entries) >= node_limit: + return for entry in self.agfs.ls(current_path): + if len(all_entries) >= node_limit: + break name = entry.get("name", "") if name in [".", ".."]: continue diff --git a/openviking_cli/cli/commands/filesystem.py b/openviking_cli/cli/commands/filesystem.py index 9b77656..27c7c44 100644 --- a/openviking_cli/cli/commands/filesystem.py +++ b/openviking_cli/cli/commands/filesystem.py @@ -26,6 +26,9 @@ def ls_command( ), abs_limit: int = typer.Option(256, "--abs-limit", "-l", help="Abstract content limit"), show_all_hidden: bool = typer.Option(False, "--all", "-a", help="Show all hidden files"), + node_limit: int = typer.Option( + 1000, "--node-limit", "-n", help="Maximum number of nodes to list" + ), ) -> None: """List directory contents.""" run( @@ -37,6 +40,7 @@ def ls_command( output=output_format, abs_limit=abs_limit, show_all_hidden=show_all_hidden, + node_limit=node_limit, ), ) @@ -49,6 +53,9 @@ def tree_command( ), abs_limit: int = typer.Option(128, "--abs-limit", "-l", help="Abstract content limit"), show_all_hidden: bool = typer.Option(False, "--all", "-a", help="Show all hidden files"), + node_limit: int = typer.Option( + 1000, "--node-limit", "-n", help="Maximum number of nodes to list" + ), ) -> None: """ Get directory tree info. @@ -56,7 +63,11 @@ def tree_command( run( ctx, lambda client: client.tree( - uri, output=output_format, abs_limit=abs_limit, show_all_hidden=show_all_hidden + uri, + output=output_format, + abs_limit=abs_limit, + show_all_hidden=show_all_hidden, + node_limit=node_limit, ), ) diff --git a/openviking_cli/client/base.py b/openviking_cli/client/base.py index b72d4f3..7882b58 100644 --- a/openviking_cli/client/base.py +++ b/openviking_cli/client/base.py @@ -68,6 +68,7 @@ async def ls( output: str = "original", abs_limit: int = 256, show_all_hidden: bool = False, + node_limit: int = 1000, ) -> List[Any]: """List directory contents.""" ... @@ -79,6 +80,7 @@ async def tree( output: str = "original", abs_limit: int = 128, show_all_hidden: bool = False, + node_limit: int = 1000, ) -> List[Dict[str, Any]]: """Get directory tree.""" ... diff --git a/openviking_cli/client/http.py b/openviking_cli/client/http.py index a8ae372..3952660 100644 --- a/openviking_cli/client/http.py +++ b/openviking_cli/client/http.py @@ -278,6 +278,7 @@ async def ls( output: str = "original", abs_limit: int = 256, show_all_hidden: bool = False, + node_limit: int = 1000, ) -> List[Any]: """List directory contents.""" response = await self._http.get( @@ -289,6 +290,7 @@ async def ls( "output": output, "abs_limit": abs_limit, "show_all_hidden": show_all_hidden, + "node_limit": node_limit, }, ) return self._handle_response(response) @@ -299,6 +301,7 @@ async def tree( output: str = "original", abs_limit: int = 128, show_all_hidden: bool = False, + node_limit: int = 1000, ) -> List[Dict[str, Any]]: """Get directory tree.""" response = await self._http.get( @@ -308,6 +311,7 @@ async def tree( "output": output, "abs_limit": abs_limit, "show_all_hidden": show_all_hidden, + "node_limit": node_limit, }, ) return self._handle_response(response) diff --git a/openviking_cli/client/sync_http.py b/openviking_cli/client/sync_http.py index fdfa450..97a9e0f 100644 --- a/openviking_cli/client/sync_http.py +++ b/openviking_cli/client/sync_http.py @@ -160,6 +160,7 @@ def ls( output: str = "original", abs_limit: int = 256, show_all_hidden: bool = False, + node_limit: int = 1000, ) -> List[Any]: """List directory contents.""" return run_async( @@ -170,6 +171,7 @@ def ls( output=output, abs_limit=abs_limit, show_all_hidden=show_all_hidden, + node_limit=node_limit, ) ) @@ -179,11 +181,16 @@ def tree( output: str = "original", abs_limit: int = 128, show_all_hidden: bool = False, - ) -> Dict: + node_limit: int = 1000, + ) -> List[Dict[str, Any]]: """Get directory tree.""" return run_async( self._async_client.tree( - uri, output=output, abs_limit=abs_limit, show_all_hidden=show_all_hidden + uri, + output=output, + abs_limit=abs_limit, + show_all_hidden=show_all_hidden, + node_limit=node_limit, ) )