use std::process::Command;
fn orca() -> Command {
let bin = std::env::var("ORCA_CLI_COMMAND").unwrap_or_else(|_| "orca".into());
Command::new(bin)
}
fn run_json(mut cmd: Command) -> Option<serde_json::Value> {
cmd.arg("--json");
let out = cmd.output().ok()?;
if !out.status.success() {
return None;
}
serde_json::from_slice(&out.stdout).ok()
}
pub fn reachable() -> bool {
let mut cmd = orca();
cmd.args(["status"]);
run_json(cmd).is_some()
}
pub fn show_worktree_id(dir: &std::path::Path) -> Option<String> {
let canon = std::fs::canonicalize(dir).unwrap_or_else(|_| dir.to_path_buf());
let mut cmd = orca();
cmd.args(["worktree", "show", "--worktree"]);
cmd.arg(format!("path:{}", canon.display()));
let v = run_json(cmd)?;
if v.get("ok").and_then(|o| o.as_bool()) != Some(true) {
return None;
}
v.pointer("/result/worktree/id")
.and_then(|id| id.as_str())
.map(String::from)
}
pub fn node_display_name(tree_path: &str) -> String {
if tree_path.is_empty() {
"swarm:.".to_string()
} else {
format!("swarm:{tree_path}")
}
}
pub fn ensure_node(
project: &str,
dir: &std::path::Path,
display_name: &str,
) -> anyhow::Result<String> {
if let Some(id) = show_worktree_id(dir) {
let mut cmd = orca();
cmd.args(["worktree", "set", "--worktree"]);
cmd.arg(format!("path:{}", dir.display()));
cmd.args(["--display-name", display_name]);
let _ = run_json(cmd); return Ok(id);
}
let mut cmd = orca();
cmd.args([
"project",
"setup-existing-folder",
"--project",
project,
"--host",
"local",
"--path",
]);
cmd.arg(dir);
cmd.args(["--kind", "folder", "--display-name", display_name]);
let v = run_json(cmd).unwrap_or(serde_json::Value::Null);
if v.get("ok").and_then(|o| o.as_bool()) != Some(true) {
anyhow::bail!("setup-existing-folder failed for {}: {v}", dir.display());
}
show_worktree_id(dir)
.ok_or_else(|| anyhow::anyhow!("registered {} but not addressable", dir.display()))
}
pub enum HierarchyOutcome {
Attached { worktree_id: String },
Unsupported,
Skipped,
}
pub fn ensure_child(project: Option<&str>, dir: &std::path::Path, display_name: &str) -> HierarchyOutcome {
if !reachable() {
return HierarchyOutcome::Skipped;
}
let project = project.unwrap_or("github:dbydd/onlyne");
match ensure_node(project, dir, display_name) {
Ok(worktree_id) => HierarchyOutcome::Attached { worktree_id },
Err(_) => HierarchyOutcome::Unsupported,
}
}
pub fn root_project(root: &std::path::Path) -> Option<String> {
let out = Command::new("git")
.args(["-C"])
.arg(root)
.args(["remote", "get-url", "origin"])
.output()
.ok()?;
if !out.status.success() {
return None;
}
let url = String::from_utf8_lossy(&out.stdout).trim().to_string();
let path = url
.strip_prefix("https://github.com/")
.or_else(|| url.strip_prefix("http://github.com/"))
.or_else(|| url.strip_prefix("git@github.com:"))
.or_else(|| url.strip_prefix("ssh://git@github.com/"))?;
let path = path.strip_suffix(".git").unwrap_or(path);
let path = path.trim_matches('/');
if path.is_empty() || !path.contains('/') {
return None;
}
Some(format!("github:{path}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn node_display_name_shapes() {
assert_eq!(node_display_name(""), "swarm:.");
assert_eq!(node_display_name("model"), "swarm:model");
assert_eq!(node_display_name("a/b"), "swarm:a/b");
}
#[test]
fn root_project_parses_github_origins() {
let dir = tempfile::tempdir().unwrap();
assert_eq!(root_project(dir.path()), None);
}
}