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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

---

Expand Down
2 changes: 1 addition & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

---

Expand Down
16 changes: 13 additions & 3 deletions crates/ov_cli/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,17 +191,27 @@ impl HttpClient {

// ============ Filesystem Methods ============

pub async fn ls(&self, uri: &str, simple: bool, recursive: bool) -> Result<serde_json::Value> {
pub async fn ls(&self, uri: &str, simple: bool, recursive: bool, output: &str, abs_limit: i32, show_all_hidden: bool, node_limit: i32) -> Result<serde_json::Value> {
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", &params).await
}

pub async fn tree(&self, uri: &str) -> Result<serde_json::Value> {
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<serde_json::Value> {
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", &params).await
}

Expand Down
12 changes: 10 additions & 2 deletions crates/ov_cli/src/commands/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,29 @@ 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(())
}

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(())
}
Expand Down
66 changes: 58 additions & 8 deletions crates/ov_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<f64>,
},
/// Show OpenViking component status
Status,
/// Quick health check
Health,
/// System utility commands
System {
#[command(subcommand)]
Expand Down Expand Up @@ -152,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 {
Expand Down Expand Up @@ -363,14 +391,23 @@ 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,
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
Expand Down Expand Up @@ -612,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<()> {
Expand Down Expand Up @@ -651,3 +690,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(())
}
7 changes: 6 additions & 1 deletion openviking/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 6 additions & 1 deletion openviking/client/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
9 changes: 8 additions & 1 deletion openviking/server/routers/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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)

Expand All @@ -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)

Expand Down
15 changes: 13 additions & 2 deletions openviking/service/fs_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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(
Expand Down Expand Up @@ -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]:
Expand Down
23 changes: 17 additions & 6 deletions openviking/storage/viking_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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).
Expand All @@ -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
Expand All @@ -297,15 +304,19 @@ 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)
all_entries = []
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
Expand Down
Loading
Loading