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
7 changes: 7 additions & 0 deletions crates/ov_cli/ovcli.conf.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"url": "http://localhost:1933",
"api_key": "your-api-key",
"timeout": 60.0,
"output": "table",
"echo_command": true
}
3 changes: 2 additions & 1 deletion crates/ov_cli/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,14 @@ impl HttpClient {
self.get("/api/v1/fs/ls", &params).await
}

pub async fn tree(&self, uri: &str, output: &str, abs_limit: i32, show_all_hidden: bool, node_limit: i32) -> Result<serde_json::Value> {
pub async fn tree(&self, uri: &str, output: &str, abs_limit: i32, show_all_hidden: bool, node_limit: i32, level_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()),
("level_limit".to_string(), level_limit.to_string()),
];
self.get("/api/v1/fs/tree", &params).await
}
Expand Down
3 changes: 2 additions & 1 deletion crates/ov_cli/src/commands/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ pub async fn tree(
abs_limit: i32,
show_all_hidden: bool,
node_limit: i32,
level_limit: i32,
output_format: OutputFormat,
compact: bool,
) -> Result<()> {
let result = client.tree(uri, output, abs_limit, show_all_hidden, node_limit).await?;
let result = client.tree(uri, output, abs_limit, show_all_hidden, node_limit, level_limit).await?;
output_success(&result, output_format, compact);
Ok(())
}
Expand Down
7 changes: 7 additions & 0 deletions crates/ov_cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub struct Config {
pub timeout: f64,
#[serde(default = "default_output_format")]
pub output: String,
#[serde(default = "default_echo_command")]
pub echo_command: bool,
}

fn default_url() -> String {
Expand All @@ -27,6 +29,10 @@ fn default_output_format() -> String {
"table".to_string()
}

fn default_echo_command() -> bool {
true
}

impl Default for Config {
fn default() -> Self {
Self {
Expand All @@ -35,6 +41,7 @@ impl Default for Config {
agent_id: None,
timeout: 60.0,
output: "table".to_string(),
echo_command: true,
}
}
}
Expand Down
41 changes: 35 additions & 6 deletions crates/ov_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ enum Commands {
#[arg(short, long)]
all: bool,
/// Maximum number of nodes to list
#[arg(long = "node-limit", short = 'n', default_value = "1000")]
#[arg(long = "node-limit", short = 'n', default_value = "256")]
node_limit: i32,
},
/// Get directory tree
Expand All @@ -209,8 +209,11 @@ enum Commands {
#[arg(short, long)]
all: bool,
/// Maximum number of nodes to list
#[arg(long = "node-limit", short = 'n', default_value = "1000")]
#[arg(long = "node-limit", short = 'n', default_value = "256")]
node_limit: i32,
/// Maximum depth level to traverse (default: 3)
#[arg(short = 'L', long = "level-limit", default_value = "3")]
level_limit: i32,
},
/// Create directory
Mkdir {
Expand Down Expand Up @@ -530,8 +533,8 @@ async fn main() {
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, abs_limit, all, node_limit } => {
handle_tree(uri, abs_limit, all, node_limit, ctx).await
Commands::Tree { uri, abs_limit, all, node_limit, level_limit } => {
handle_tree(uri, abs_limit, all, node_limit, level_limit, ctx).await
}
Commands::Mkdir { uri } => {
handle_mkdir(uri, ctx).await
Expand Down Expand Up @@ -877,16 +880,42 @@ async fn handle_search(
commands::search::search(&client, &query, &uri, session_id, limit, threshold, ctx.output_format, ctx.compact).await
}

/// Print command with specified parameters for debugging
fn print_command_echo(command: &str, params: &str, echo_enabled: bool) {
if echo_enabled {
println!("cmd: {} {}", command, params);
}
}

async fn handle_ls(uri: String, simple: bool, recursive: bool, abs_limit: i32, show_all_hidden: bool, node_limit: i32, ctx: CliContext) -> Result<()> {
let mut params = vec![
uri.clone(),
format!("-l {}", abs_limit),
format!("-n {}", node_limit),
];
if simple { params.push("-s".to_string()); }
if recursive { params.push("-r".to_string()); }
if show_all_hidden { params.push("-a".to_string()); }
print_command_echo("ov ls", &params.join(" "), ctx.config.echo_command);

let client = ctx.get_client();
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, abs_limit: i32, show_all_hidden: bool, node_limit: i32, ctx: CliContext) -> Result<()> {
async fn handle_tree(uri: String, abs_limit: i32, show_all_hidden: bool, node_limit: i32, level_limit: i32, ctx: CliContext) -> Result<()> {
let mut params = vec![
uri.clone(),
format!("-l {}", abs_limit),
format!("-n {}", node_limit),
format!("-L {}", level_limit),
];
if show_all_hidden { params.push("-a".to_string()); }
print_command_echo("ov tree", &params.join(" "), ctx.config.echo_command);

let client = ctx.get_client();
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
commands::filesystem::tree(&client, &uri, api_output, abs_limit, show_all_hidden, node_limit, level_limit, ctx.output_format, ctx.compact).await
}

async fn handle_mkdir(uri: String, ctx: CliContext) -> Result<()> {
Expand Down
25 changes: 13 additions & 12 deletions openviking/parse/tree_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,38 +133,39 @@ async def finalize_from_temp(
if original_name != doc_name:
logger.debug(f"[TreeBuilder] Sanitized doc name: {original_name!r} -> {doc_name!r}")

# 2. Determine base_uri and final document name with org/repo for GitHub/GitLab
if base_uri is None:
base_uri = self._get_base_uri(scope, source_path, source_format)

# Check if source_path is a GitHub/GitLab URL and extract org/repo
final_doc_name = doc_name
if source_path and source_format == "repository":
parsed_org_repo = parse_code_hosting_url(source_path)
if parsed_org_repo:
final_doc_name = parsed_org_repo

# 2. Determine base_uri and final document name with org/repo for GitHub/GitLab
auto_base_uri = self._get_base_uri(scope, source_path, source_format)

# 3. Check if base_uri exists - if it does, use it as parent directory
try:
await viking_fs.stat(base_uri)
base_exists = True
except Exception:
base_exists = False
base_exists = False
if base_uri:
try:
await viking_fs.stat(base_uri)
base_exists = True
except Exception:
base_exists = False

if base_exists:
if "/" in final_doc_name:
repo_name_only = final_doc_name.split("/")[-1]
else:
repo_name_only = final_doc_name
candidate_uri = VikingURI(base_uri).join(repo_name_only).uri
candidate_uri = VikingURI(base_uri or auto_base_uri).join(repo_name_only).uri
else:
if "/" in final_doc_name:
parts = final_doc_name.split("/")
sanitized_parts = [VikingURI.sanitize_segment(p) for p in parts if p]
base_viking_uri = VikingURI(base_uri)
base_viking_uri = VikingURI(base_uri or auto_base_uri)
candidate_uri = VikingURI.build(base_viking_uri.scope, *sanitized_parts)
else:
candidate_uri = VikingURI(base_uri).join(doc_name).uri
candidate_uri = VikingURI(base_uri or auto_base_uri).join(doc_name).uri
final_uri = await self._resolve_unique_uri(candidate_uri, ctx=ctx)

if final_uri != candidate_uri:
Expand Down
2 changes: 2 additions & 0 deletions openviking/server/routers/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ async def tree(
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"),
level_limit: int = Query(3, description="Maximum depth level to traverse"),
_ctx: RequestContext = Depends(get_request_context),
):
"""Get directory tree."""
Expand All @@ -59,6 +60,7 @@ async def tree(
abs_limit=abs_limit,
show_all_hidden=show_all_hidden,
node_limit=node_limit,
level_limit=level_limit,
)
return Response(status="ok", result=result)

Expand Down
5 changes: 5 additions & 0 deletions openviking/service/fs_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ async def ls(
abs_limit: int = 256,
show_all_hidden: bool = False,
node_limit: int = 1000,
level_limit: int = 3,
) -> List[Any]:
"""List directory contents.

Expand All @@ -65,6 +66,7 @@ async def ls(
output="original",
show_all_hidden=show_all_hidden,
node_limit=node_limit,
level_limit=level_limit,
)
else:
entries = await viking_fs.ls(
Expand All @@ -80,6 +82,7 @@ async def ls(
abs_limit=abs_limit,
show_all_hidden=show_all_hidden,
node_limit=node_limit,
level_limit=level_limit,
)
else:
entries = await viking_fs.ls(
Expand Down Expand Up @@ -114,6 +117,7 @@ async def tree(
abs_limit: int = 128,
show_all_hidden: bool = False,
node_limit: int = 1000,
level_limit: int = 3,
) -> List[Dict[str, Any]]:
"""Get directory tree."""
viking_fs = self._ensure_initialized()
Expand All @@ -124,6 +128,7 @@ async def tree(
abs_limit=abs_limit,
show_all_hidden=show_all_hidden,
node_limit=node_limit,
level_limit=level_limit,
)

async def stat(self, uri: str, ctx: RequestContext) -> Dict[str, Any]:
Expand Down
27 changes: 17 additions & 10 deletions openviking/storage/viking_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ async def tree(
abs_limit: int = 256,
show_all_hidden: bool = False,
node_limit: int = 1000,
level_limit: int = 3,
ctx: Optional[RequestContext] = None,
) -> List[Dict[str, Any]]:
"""
Expand All @@ -394,6 +395,8 @@ async def tree(
output: str = "original" or "agent"
abs_limit: int = 256 (for agent output abstract truncation)
show_all_hidden: bool = False (list all hidden files, like -a)
node_limit: int = 1000 (maximum number of nodes to list)
level_limit: int = 3 (maximum depth level to traverse)
output="original"
[{'name': '.abstract.md', 'size': 100, 'mode': 420, 'modTime': '2026-02-11T16:52:16.256334192+08:00', 'isDir': False, 'meta': {...}, 'rel_path': '.abstract.md', 'uri': 'viking://resources...'}]
Expand All @@ -403,9 +406,11 @@ async def tree(
"""
self._ensure_access(uri, ctx)
if output == "original":
return await self._tree_original(uri, show_all_hidden, node_limit, ctx=ctx)
return await self._tree_original(uri, show_all_hidden, node_limit, level_limit, ctx=ctx)
elif output == "agent":
return await self._tree_agent(uri, abs_limit, show_all_hidden, node_limit, ctx=ctx)
return await self._tree_agent(
uri, abs_limit, show_all_hidden, node_limit, level_limit, ctx=ctx
)
else:
raise ValueError(f"Invalid output format: {output}")

Expand All @@ -414,15 +419,16 @@ async def _tree_original(
uri: str,
show_all_hidden: bool = False,
node_limit: int = 1000,
level_limit: int = 3,
ctx: Optional[RequestContext] = None,
) -> List[Dict[str, Any]]:
"""Recursively list all contents (original format)."""
path = self._uri_to_path(uri, ctx=ctx)
all_entries = []
real_ctx = self._ctx_or_default(ctx)

async def _walk(current_path: str, current_rel: str):
if len(all_entries) >= node_limit:
async def _walk(current_path: str, current_rel: str, current_depth: int):
if len(all_entries) >= node_limit or current_depth > level_limit:
return
for entry in self._ls_entries(current_path):
if len(all_entries) >= node_limit:
Expand All @@ -438,13 +444,13 @@ async def _walk(current_path: str, current_rel: str):
continue
if entry.get("isDir"):
all_entries.append(new_entry)
await _walk(f"{current_path}/{name}", rel_path)
await _walk(f"{current_path}/{name}", rel_path, current_depth + 1)
elif not name.startswith("."):
all_entries.append(new_entry)
elif show_all_hidden:
all_entries.append(new_entry)

await _walk(path, "")
await _walk(path, "", 0)
return all_entries

async def _tree_agent(
Expand All @@ -453,6 +459,7 @@ async def _tree_agent(
abs_limit: int,
show_all_hidden: bool = False,
node_limit: int = 1000,
level_limit: int = 3,
ctx: Optional[RequestContext] = None,
) -> List[Dict[str, Any]]:
"""Recursively list all contents (agent format with abstracts)."""
Expand All @@ -461,8 +468,8 @@ async def _tree_agent(
now = datetime.now()
real_ctx = self._ctx_or_default(ctx)

async def _walk(current_path: str, current_rel: str):
if len(all_entries) >= node_limit:
async def _walk(current_path: str, current_rel: str, current_depth: int):
if len(all_entries) >= node_limit or current_depth > level_limit:
return
for entry in self._ls_entries(current_path):
if len(all_entries) >= node_limit:
Expand All @@ -482,13 +489,13 @@ async def _walk(current_path: str, current_rel: str):
continue
if entry.get("isDir"):
all_entries.append(new_entry)
await _walk(f"{current_path}/{name}", rel_path)
await _walk(f"{current_path}/{name}", rel_path, current_depth + 1)
elif not name.startswith("."):
all_entries.append(new_entry)
elif show_all_hidden:
all_entries.append(new_entry)

await _walk(path, "")
await _walk(path, "", 0)

await self._batch_fetch_abstracts(all_entries, abs_limit, ctx=ctx)

Expand Down
Loading