mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Enable clippy pedantic lints and restriction lints workspace-wide
Adopts uv's clippy lint configuration: pedantic group at warn priority, with noisy lints allowed, plus restriction lints for print/dbg/exit/use_self. Fixes all violations across the workspace. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5c215033c1
commit
1dc0ebce52
172 changed files with 1120 additions and 993 deletions
46
Cargo.toml
46
Cargo.toml
|
|
@ -65,7 +65,53 @@ exec = "0.3"
|
|||
slatedb = "0.11.2"
|
||||
object_store = "0.12.5"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_code = "warn"
|
||||
unreachable_pub = "warn"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
pedantic = { level = "warn", priority = -2 }
|
||||
# Allowed pedantic lints
|
||||
char_lit_as_u8 = "allow"
|
||||
collapsible_else_if = "allow"
|
||||
collapsible_if = "allow"
|
||||
implicit_hasher = "allow"
|
||||
map_unwrap_or = "allow"
|
||||
match_same_arms = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
module_name_repetitions = "allow"
|
||||
must_use_candidate = "allow"
|
||||
similar_names = "allow"
|
||||
struct_excessive_bools = "allow"
|
||||
too_many_arguments = "allow"
|
||||
too_many_lines = "allow"
|
||||
used_underscore_binding = "allow"
|
||||
if_not_else = "allow"
|
||||
cast_possible_truncation = "allow"
|
||||
cast_possible_wrap = "allow"
|
||||
cast_precision_loss = "allow"
|
||||
cast_sign_loss = "allow"
|
||||
doc_markdown = "allow"
|
||||
items_after_statements = "allow"
|
||||
needless_pass_by_value = "allow"
|
||||
return_self_not_must_use = "allow"
|
||||
uninlined_format_args = "allow"
|
||||
unreadable_literal = "allow"
|
||||
unnested_or_patterns = "allow"
|
||||
# Disallowed restriction lints
|
||||
print_stdout = "warn"
|
||||
print_stderr = "warn"
|
||||
dbg_macro = "warn"
|
||||
empty_drop = "warn"
|
||||
empty_structs_with_brackets = "warn"
|
||||
exit = "warn"
|
||||
get_unwrap = "warn"
|
||||
rc_buffer = "warn"
|
||||
rc_mutex = "warn"
|
||||
rest_pat_in_fully_bound_structs = "warn"
|
||||
use_self = "warn"
|
||||
# Project-specific lints
|
||||
wildcard_imports = "warn"
|
||||
absolute_paths = "warn"
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ fn is_auto_approved(level: PermissionLevel, category: &str) -> bool {
|
|||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
fn build_tool_approval(
|
||||
permissions: PermissionLevel,
|
||||
is_interactive: bool,
|
||||
|
|
@ -237,6 +238,7 @@ fn format_tool_args(args: &serde_json::Value, cwd: &str) -> String {
|
|||
.join(", ")
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stdout)]
|
||||
fn print_output(session: &Session, styles: &Styles) {
|
||||
for turn in session.history().turns() {
|
||||
if let Turn::Assistant { content, .. } = turn {
|
||||
|
|
@ -247,6 +249,7 @@ fn print_output(session: &Session, styles: &Styles) {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
fn print_summary(session: &Session, styles: &Styles) {
|
||||
let (mut turn_count, mut tool_call_count, mut total_tokens) = (0usize, 0usize, 0i64);
|
||||
for turn in session.history().turns() {
|
||||
|
|
@ -281,6 +284,7 @@ struct DebugMiddleware {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl Middleware for DebugMiddleware {
|
||||
#[allow(clippy::print_stderr)]
|
||||
async fn handle_complete(&self, request: Request, next: NextFn) -> Result<Response, SdkError> {
|
||||
let s = self.styles;
|
||||
eprintln!(
|
||||
|
|
@ -323,6 +327,7 @@ struct VerboseMiddleware {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl Middleware for VerboseMiddleware {
|
||||
#[allow(clippy::print_stderr)]
|
||||
async fn handle_complete(&self, request: Request, next: NextFn) -> Result<Response, SdkError> {
|
||||
let s = self.styles;
|
||||
eprintln!(
|
||||
|
|
@ -357,6 +362,7 @@ pub async fn run_with_args(
|
|||
run_with_args_and_client(args, None, mcp_servers).await
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stdout, clippy::print_stderr)]
|
||||
pub async fn run_with_args_and_client(
|
||||
args: AgentArgs,
|
||||
llm_client: Option<Client>,
|
||||
|
|
@ -429,7 +435,7 @@ pub async fn run_with_args_and_client(
|
|||
)));
|
||||
let manager_for_callback = manager.clone();
|
||||
let factory_client = client.clone();
|
||||
let factory_model = model.to_string();
|
||||
let factory_model = model.clone();
|
||||
let factory_env = Arc::clone(&env);
|
||||
let factory_hooks = config.tool_hooks.clone();
|
||||
let factory: SessionFactory = Arc::new(move || {
|
||||
|
|
@ -490,7 +496,9 @@ pub async fn run_with_args_and_client(
|
|||
tokio::spawn(async move {
|
||||
signal::ctrl_c().await.ok();
|
||||
{
|
||||
let mut guard = abort_reason.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut guard = abort_reason
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if guard.is_none() {
|
||||
*guard = Some(AbortReason::Cancelled);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
use crate::agent_profile::AgentProfile;
|
||||
use crate::error::AgentError;
|
||||
use crate::event::EventEmitter;
|
||||
|
|
@ -198,7 +200,7 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String {
|
|||
for turn in turns {
|
||||
match turn {
|
||||
Turn::User { content, .. } => {
|
||||
out.push_str(&format!("User: {content}\n"));
|
||||
let _ = writeln!(out, "User: {content}");
|
||||
}
|
||||
Turn::Assistant {
|
||||
content,
|
||||
|
|
@ -206,7 +208,7 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String {
|
|||
..
|
||||
} => {
|
||||
if !content.is_empty() {
|
||||
out.push_str(&format!("Assistant: {content}\n"));
|
||||
let _ = writeln!(out, "Assistant: {content}");
|
||||
}
|
||||
for tc in tool_calls {
|
||||
let args_str = tc.arguments.to_string();
|
||||
|
|
@ -218,7 +220,7 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String {
|
|||
} else {
|
||||
args_str
|
||||
};
|
||||
out.push_str(&format!("[Tool call: {}] {truncated}\n", tc.name));
|
||||
let _ = writeln!(out, "[Tool call: {}] {truncated}", tc.name);
|
||||
}
|
||||
}
|
||||
Turn::ToolResults { results, .. } => {
|
||||
|
|
@ -232,14 +234,14 @@ pub fn render_turns_for_summary(turns: &[Turn]) -> String {
|
|||
} else {
|
||||
content_str
|
||||
};
|
||||
out.push_str(&format!("[Tool result: {}] {truncated}\n", r.tool_call_id));
|
||||
let _ = writeln!(out, "[Tool result: {}] {truncated}", r.tool_call_id);
|
||||
}
|
||||
}
|
||||
Turn::System { content, .. } => {
|
||||
out.push_str(&format!("System: {content}\n"));
|
||||
let _ = writeln!(out, "System: {content}");
|
||||
}
|
||||
Turn::Steering { content, .. } => {
|
||||
out.push_str(&format!("Steering: {content}\n"));
|
||||
let _ = writeln!(out, "Steering: {content}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ impl std::fmt::Debug for SessionConfig {
|
|||
.field("max_command_timeout_ms", &self.max_command_timeout_ms)
|
||||
.field("max_tokens", &self.max_tokens)
|
||||
.field("reasoning_effort", &self.reasoning_effort)
|
||||
.field("speed", &self.speed)
|
||||
.field("tool_output_limits", &self.tool_output_limits)
|
||||
.field("tool_line_limits", &self.tool_line_limits)
|
||||
.field("enable_loop_detection", &self.enable_loop_detection)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use fabro_llm::types::{ToolCall, ToolResult};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Write;
|
||||
|
||||
use fabro_llm::types::{ToolCall, ToolResult};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
struct FileOps {
|
||||
|
|
@ -47,7 +49,7 @@ impl FileTracker {
|
|||
if ops.edited {
|
||||
labels.push("edited");
|
||||
}
|
||||
output.push_str(&format!("- {path} ({})\n", labels.join(", ")));
|
||||
let _ = writeln!(output, "- {path} ({})", labels.join(", "));
|
||||
}
|
||||
output
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ pub async fn discover_memory(
|
|||
}
|
||||
}
|
||||
|
||||
let total_bytes: usize = results.iter().map(|d| d.len()).sum();
|
||||
let total_bytes: usize = results.iter().map(std::string::String::len).sum();
|
||||
info!(files = results.len(), total_bytes, "Project docs loaded");
|
||||
|
||||
results
|
||||
|
|
|
|||
|
|
@ -113,12 +113,11 @@ impl Session {
|
|||
.await;
|
||||
|
||||
// Discover skills
|
||||
let skill_dirs = match &self.config.skill_dirs {
|
||||
Some(dirs) => dirs.clone(),
|
||||
None => {
|
||||
let home = dirs::home_dir().map(|p| p.to_string_lossy().to_string());
|
||||
default_skill_dirs(home.as_deref(), self.config.git_root.as_deref())
|
||||
}
|
||||
let skill_dirs = if let Some(dirs) = &self.config.skill_dirs {
|
||||
dirs.clone()
|
||||
} else {
|
||||
let home = dirs::home_dir().map(|p| p.to_string_lossy().to_string());
|
||||
default_skill_dirs(home.as_deref(), self.config.git_root.as_deref())
|
||||
};
|
||||
self.skills = discover_skills(self.sandbox.as_ref(), &skill_dirs).await;
|
||||
debug!(skill_count = self.skills.len(), "Skills discovered");
|
||||
|
|
@ -291,15 +290,14 @@ impl Session {
|
|||
}
|
||||
|
||||
// Get the preview URL for the port, or fall back to localhost for local sandboxes
|
||||
match sandbox.get_preview_url(port).await? {
|
||||
Some(url_and_headers) => Ok(url_and_headers),
|
||||
None => {
|
||||
info!(port, "No preview URL available, using localhost");
|
||||
Ok((
|
||||
format!("http://localhost:{port}"),
|
||||
std::collections::HashMap::new(),
|
||||
))
|
||||
}
|
||||
if let Some(url_and_headers) = sandbox.get_preview_url(port).await? {
|
||||
Ok(url_and_headers)
|
||||
} else {
|
||||
info!(port, "No preview URL available, using localhost");
|
||||
Ok((
|
||||
format!("http://localhost:{port}"),
|
||||
std::collections::HashMap::new(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -389,7 +387,10 @@ impl Session {
|
|||
}
|
||||
|
||||
fn set_abort_reason(&self, reason: AbortReason) {
|
||||
let mut guard = self.abort_reason.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut guard = self
|
||||
.abort_reason
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if guard.is_none() {
|
||||
*guard = Some(reason);
|
||||
}
|
||||
|
|
@ -399,7 +400,7 @@ impl Session {
|
|||
let reason = self
|
||||
.abort_reason
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone()
|
||||
.unwrap_or(AbortReason::Cancelled);
|
||||
AgentError::Aborted(reason)
|
||||
|
|
@ -544,7 +545,9 @@ impl Session {
|
|||
tokio::spawn(async move {
|
||||
time::sleep(duration).await;
|
||||
{
|
||||
let mut guard = reason_handle.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let mut guard = reason_handle
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if guard.is_none() {
|
||||
*guard = Some(AbortReason::WallClockTimeout);
|
||||
}
|
||||
|
|
@ -777,14 +780,11 @@ impl Session {
|
|||
}
|
||||
}
|
||||
|
||||
let response = match response {
|
||||
Some(response) => response,
|
||||
None => {
|
||||
return Err(self.emit_llm_error(SdkError::Stream {
|
||||
message: "Stream ended without a Finish event (after retries)".into(),
|
||||
source: None,
|
||||
}));
|
||||
}
|
||||
let Some(response) = response else {
|
||||
return Err(self.emit_llm_error(SdkError::Stream {
|
||||
message: "Stream ended without a Finish event (after retries)".into(),
|
||||
source: None,
|
||||
}));
|
||||
};
|
||||
|
||||
// Record assistant turn
|
||||
|
|
|
|||
|
|
@ -225,22 +225,17 @@ pub async fn discover_skills(env: &dyn Sandbox, dirs: &[String]) -> Vec<Skill> {
|
|||
std::collections::HashMap::new();
|
||||
|
||||
for dir in dirs {
|
||||
let paths = match env.glob("*/SKILL.md", Some(dir)).await {
|
||||
Ok(paths) => paths,
|
||||
Err(_) => continue,
|
||||
let Ok(paths) = env.glob("*/SKILL.md", Some(dir)).await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for path in paths {
|
||||
let content = match env.read_file(&path, None, None).await {
|
||||
Ok(c) => c,
|
||||
Err(_) => continue,
|
||||
let Ok(content) = env.read_file(&path, None, None).await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match parse_skill(&content) {
|
||||
Ok(skill) => {
|
||||
skills_by_name.insert(skill.name.clone(), skill);
|
||||
}
|
||||
Err(_) => continue,
|
||||
if let Ok(skill) = parse_skill(&content) {
|
||||
skills_by_name.insert(skill.name.clone(), skill);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,12 +238,11 @@ async fn execute_and_emit_one_tool_with_lookup(
|
|||
// Post-tool-use hooks
|
||||
if let Some(hooks) = tool_hooks {
|
||||
let fallback;
|
||||
let content_str = match result.content.as_str() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
fallback = result.content.to_string();
|
||||
&fallback
|
||||
}
|
||||
let content_str = if let Some(s) = result.content.as_str() {
|
||||
s
|
||||
} else {
|
||||
fallback = result.content.to_string();
|
||||
&fallback
|
||||
};
|
||||
if result.is_error {
|
||||
debug!(tool = %tc.name, hook_event = "post_tool_use_failure", "Calling tool hook");
|
||||
|
|
|
|||
|
|
@ -221,7 +221,10 @@ pub fn make_shell_tool_with_config(config: &SessionConfig) -> RegisteredTool {
|
|||
.min(max_timeout);
|
||||
|
||||
tracing::debug!(
|
||||
env_var_count = ctx.tool_env.as_ref().map_or(0, |e| e.len()),
|
||||
env_var_count = ctx
|
||||
.tool_env
|
||||
.as_ref()
|
||||
.map_or(0, std::collections::HashMap::len),
|
||||
"Injecting sandbox env vars into tool execution"
|
||||
);
|
||||
let result = ctx
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ mod system_time_iso8601 {
|
|||
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||
use std::time::SystemTime;
|
||||
|
||||
pub fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
pub(super) fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
|
|
@ -18,7 +18,7 @@ mod system_time_iso8601 {
|
|||
serializer.serialize_str(&dt.to_rfc3339_opts(SecondsFormat::Millis, true))
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
|
||||
pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
|
|
@ -67,7 +67,7 @@ impl Turn {
|
|||
/// `provider_parts`, if any.
|
||||
#[must_use]
|
||||
pub fn reasoning_text(&self) -> Option<&str> {
|
||||
let Turn::Assistant { provider_parts, .. } = self else {
|
||||
let Self::Assistant { provider_parts, .. } = self else {
|
||||
return None;
|
||||
};
|
||||
provider_parts.iter().find_map(|p| match p {
|
||||
|
|
@ -188,7 +188,7 @@ pub enum AgentEvent {
|
|||
SubAgentEvent {
|
||||
agent_id: String,
|
||||
depth: usize,
|
||||
event: Box<AgentEvent>,
|
||||
event: Box<Self>,
|
||||
},
|
||||
McpServerReady {
|
||||
server_name: String,
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ fn apply_hunks(content: &str, hunks: &[Hunk]) -> Result<String, String> {
|
|||
}
|
||||
|
||||
// Calculate total lines consumed from original
|
||||
let explicit_context_count = if has_explicit_context { 1 } else { 0 };
|
||||
let explicit_context_count = usize::from(has_explicit_context);
|
||||
let total_original_lines = explicit_context_count
|
||||
+ hunk
|
||||
.changes
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ description = "Generated Rust types from the Fabro API OpenAPI spec"
|
|||
[lib]
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
[lints.clippy]
|
||||
# Auto-generated crate; only enforce project-specific lints
|
||||
wildcard_imports = "warn"
|
||||
|
||||
[dependencies]
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Demo mode handlers that return static data for all API endpoints.
|
||||
//! Activated per-request via the `X-Fabro-Demo: 1` header to showcase the UI without a real backend.
|
||||
#![allow(clippy::default_trait_access)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -14,7 +15,7 @@ use crate::jwt_auth::AuthenticatedService;
|
|||
use crate::server::{AppState, PaginationParams};
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct RetroListParams {
|
||||
pub(crate) struct RetroListParams {
|
||||
#[serde(rename = "page[limit]", default = "crate::server::default_page_limit")]
|
||||
limit: u32,
|
||||
#[serde(rename = "page[offset]", default)]
|
||||
|
|
@ -41,7 +42,7 @@ fn paginated_response<T: serde::Serialize>(
|
|||
|
||||
// ── Runs ───────────────────────────────────────────────────────────────
|
||||
|
||||
pub async fn list_runs(
|
||||
pub(crate) async fn list_runs(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
|
|
@ -49,7 +50,7 @@ pub async fn list_runs(
|
|||
paginated_response(runs::list_items(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn start_run_stub(
|
||||
pub(crate) async fn start_run_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
) -> Response {
|
||||
|
|
@ -60,7 +61,7 @@ pub async fn start_run_stub(
|
|||
.into_response()
|
||||
}
|
||||
|
||||
pub async fn get_run_stages(
|
||||
pub(crate) async fn get_run_stages(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -69,7 +70,7 @@ pub async fn get_run_stages(
|
|||
paginated_response(runs::stages(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn get_stage_turns(
|
||||
pub(crate) async fn get_stage_turns(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path((_id, _stage_id)): Path<(String, String)>,
|
||||
|
|
@ -78,7 +79,7 @@ pub async fn get_stage_turns(
|
|||
paginated_response(runs::turns(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn get_run_files(
|
||||
pub(crate) async fn get_run_files(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -87,7 +88,7 @@ pub async fn get_run_files(
|
|||
paginated_response(runs::files(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn get_run_usage(
|
||||
pub(crate) async fn get_run_usage(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -95,7 +96,7 @@ pub async fn get_run_usage(
|
|||
(StatusCode::OK, Json(runs::usage())).into_response()
|
||||
}
|
||||
|
||||
pub async fn get_run_verification(
|
||||
pub(crate) async fn get_run_verification(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -104,7 +105,7 @@ pub async fn get_run_verification(
|
|||
paginated_response(runs::verifications(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn get_run_settings(
|
||||
pub(crate) async fn get_run_settings(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -112,7 +113,7 @@ pub async fn get_run_settings(
|
|||
(StatusCode::OK, Json(runs::settings())).into_response()
|
||||
}
|
||||
|
||||
pub async fn steer_run_stub(
|
||||
pub(crate) async fn steer_run_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -120,7 +121,7 @@ pub async fn steer_run_stub(
|
|||
StatusCode::ACCEPTED.into_response()
|
||||
}
|
||||
|
||||
pub async fn generate_preview_url_stub(
|
||||
pub(crate) async fn generate_preview_url_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -132,7 +133,7 @@ pub async fn generate_preview_url_stub(
|
|||
.into_response()
|
||||
}
|
||||
|
||||
pub async fn get_run_status(
|
||||
pub(crate) async fn get_run_status(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
|
|
@ -153,7 +154,7 @@ pub async fn get_run_status(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn get_questions_stub(
|
||||
pub(crate) async fn get_questions_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -162,7 +163,7 @@ pub async fn get_questions_stub(
|
|||
paginated_response(runs::questions(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn answer_stub(
|
||||
pub(crate) async fn answer_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path((_id, _qid)): Path<(String, String)>,
|
||||
|
|
@ -170,7 +171,7 @@ pub async fn answer_stub(
|
|||
StatusCode::NO_CONTENT.into_response()
|
||||
}
|
||||
|
||||
pub async fn run_events_stub(
|
||||
pub(crate) async fn run_events_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -178,7 +179,7 @@ pub async fn run_events_stub(
|
|||
ApiError::new(StatusCode::GONE, "Event stream closed.").into_response()
|
||||
}
|
||||
|
||||
pub async fn checkpoint_stub(
|
||||
pub(crate) async fn checkpoint_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -186,7 +187,7 @@ pub async fn checkpoint_stub(
|
|||
(StatusCode::OK, Json(serde_json::json!(null))).into_response()
|
||||
}
|
||||
|
||||
pub async fn context_stub(
|
||||
pub(crate) async fn context_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -194,7 +195,7 @@ pub async fn context_stub(
|
|||
(StatusCode::OK, Json(serde_json::json!({}))).into_response()
|
||||
}
|
||||
|
||||
pub async fn cancel_stub(
|
||||
pub(crate) async fn cancel_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -202,7 +203,7 @@ pub async fn cancel_stub(
|
|||
(StatusCode::OK, Json(serde_json::json!({"id": _id, "status": "cancelled", "created_at": "2026-03-06T14:30:00Z"}))).into_response()
|
||||
}
|
||||
|
||||
pub async fn pause_stub(
|
||||
pub(crate) async fn pause_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -210,7 +211,7 @@ pub async fn pause_stub(
|
|||
(StatusCode::OK, Json(serde_json::json!({"id": _id, "status": "paused", "created_at": "2026-03-06T14:30:00Z"}))).into_response()
|
||||
}
|
||||
|
||||
pub async fn unpause_stub(
|
||||
pub(crate) async fn unpause_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -218,7 +219,7 @@ pub async fn unpause_stub(
|
|||
(StatusCode::OK, Json(serde_json::json!({"id": _id, "status": "running", "created_at": "2026-03-06T14:30:00Z"}))).into_response()
|
||||
}
|
||||
|
||||
pub async fn get_run_graph(
|
||||
pub(crate) async fn get_run_graph(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -229,7 +230,7 @@ pub async fn get_run_graph(
|
|||
crate::server::render_dot_svg(dot_source).await
|
||||
}
|
||||
|
||||
pub async fn get_run_retro(
|
||||
pub(crate) async fn get_run_retro(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
|
|
@ -242,7 +243,7 @@ pub async fn get_run_retro(
|
|||
|
||||
// ── Workflows ──────────────────────────────────────────────────────────
|
||||
|
||||
pub async fn list_workflows(
|
||||
pub(crate) async fn list_workflows(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
|
|
@ -250,7 +251,7 @@ pub async fn list_workflows(
|
|||
paginated_response(workflows::list_items(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn get_workflow(
|
||||
pub(crate) async fn get_workflow(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(name): Path<String>,
|
||||
|
|
@ -261,7 +262,7 @@ pub async fn get_workflow(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn list_workflow_runs(
|
||||
pub(crate) async fn list_workflow_runs(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(name): Path<String>,
|
||||
|
|
@ -276,7 +277,7 @@ pub async fn list_workflow_runs(
|
|||
|
||||
// ── Verification ──────────────────────────────────────────────────────
|
||||
|
||||
pub async fn list_verification_criteria(
|
||||
pub(crate) async fn list_verification_criteria(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
|
|
@ -284,7 +285,7 @@ pub async fn list_verification_criteria(
|
|||
paginated_response(verifications::criteria(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn get_verification_criterion(
|
||||
pub(crate) async fn get_verification_criterion(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
|
|
@ -295,7 +296,7 @@ pub async fn get_verification_criterion(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn list_verification_controls(
|
||||
pub(crate) async fn list_verification_controls(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
|
|
@ -303,7 +304,7 @@ pub async fn list_verification_controls(
|
|||
paginated_response(verifications::controls(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn get_verification_control(
|
||||
pub(crate) async fn get_verification_control(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
|
|
@ -317,7 +318,7 @@ pub async fn get_verification_control(
|
|||
// ── Signoffs ──────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct SignoffListParams {
|
||||
pub(crate) struct SignoffListParams {
|
||||
#[serde(rename = "page[limit]", default = "crate::server::default_page_limit")]
|
||||
limit: u32,
|
||||
#[serde(rename = "page[offset]", default)]
|
||||
|
|
@ -327,7 +328,7 @@ pub struct SignoffListParams {
|
|||
commit_sha: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn list_signoffs(
|
||||
pub(crate) async fn list_signoffs(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Query(params): Query<SignoffListParams>,
|
||||
|
|
@ -346,7 +347,7 @@ pub async fn list_signoffs(
|
|||
)
|
||||
}
|
||||
|
||||
pub async fn get_signoff(
|
||||
pub(crate) async fn get_signoff(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
|
|
@ -357,7 +358,7 @@ pub async fn get_signoff(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn create_signoff_stub(
|
||||
pub(crate) async fn create_signoff_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
) -> Response {
|
||||
|
|
@ -366,7 +367,7 @@ pub async fn create_signoff_stub(
|
|||
|
||||
// ── Retros ─────────────────────────────────────────────────────────────
|
||||
|
||||
pub async fn list_retros(
|
||||
pub(crate) async fn list_retros(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Query(params): Query<RetroListParams>,
|
||||
|
|
@ -397,7 +398,7 @@ pub async fn list_retros(
|
|||
|
||||
// ── Sessions ───────────────────────────────────────────────────────────
|
||||
|
||||
pub async fn list_sessions(
|
||||
pub(crate) async fn list_sessions(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
|
|
@ -405,7 +406,7 @@ pub async fn list_sessions(
|
|||
paginated_response(sessions::list_items(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn create_session_stub(
|
||||
pub(crate) async fn create_session_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
) -> Response {
|
||||
|
|
@ -418,7 +419,7 @@ pub async fn create_session_stub(
|
|||
.into_response()
|
||||
}
|
||||
|
||||
pub async fn get_session(
|
||||
pub(crate) async fn get_session(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
|
|
@ -429,7 +430,7 @@ pub async fn get_session(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn send_message_stub(
|
||||
pub(crate) async fn send_message_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -441,7 +442,7 @@ pub async fn send_message_stub(
|
|||
.into_response()
|
||||
}
|
||||
|
||||
pub async fn session_events_stub(
|
||||
pub(crate) async fn session_events_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
headers: axum::http::HeaderMap,
|
||||
|
|
@ -449,9 +450,8 @@ pub async fn session_events_stub(
|
|||
) -> Response {
|
||||
use axum::response::sse::{Event, Sse};
|
||||
|
||||
let session = match sessions::detail(&id) {
|
||||
Some(s) => s,
|
||||
None => return ApiError::not_found("Session not found.").into_response(),
|
||||
let Some(session) = sessions::detail(&id) else {
|
||||
return ApiError::not_found("Session not found.").into_response();
|
||||
};
|
||||
|
||||
let last_event_id: Option<usize> = headers
|
||||
|
|
@ -495,7 +495,7 @@ pub async fn session_events_stub(
|
|||
|
||||
// ── Insights ───────────────────────────────────────────────────────────
|
||||
|
||||
pub async fn list_saved_queries(
|
||||
pub(crate) async fn list_saved_queries(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
|
|
@ -503,7 +503,7 @@ pub async fn list_saved_queries(
|
|||
paginated_response(insights::saved_queries(), &pagination)
|
||||
}
|
||||
|
||||
pub async fn save_query_stub(
|
||||
pub(crate) async fn save_query_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
) -> Response {
|
||||
|
|
@ -514,7 +514,7 @@ pub async fn save_query_stub(
|
|||
.into_response()
|
||||
}
|
||||
|
||||
pub async fn get_saved_query(
|
||||
pub(crate) async fn get_saved_query(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
|
|
@ -525,7 +525,7 @@ pub async fn get_saved_query(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn update_query_stub(
|
||||
pub(crate) async fn update_query_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -537,7 +537,7 @@ pub async fn update_query_stub(
|
|||
.into_response()
|
||||
}
|
||||
|
||||
pub async fn delete_query_stub(
|
||||
pub(crate) async fn delete_query_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
|
|
@ -545,7 +545,7 @@ pub async fn delete_query_stub(
|
|||
StatusCode::NO_CONTENT.into_response()
|
||||
}
|
||||
|
||||
pub async fn execute_query_stub(
|
||||
pub(crate) async fn execute_query_stub(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
) -> Response {
|
||||
|
|
@ -561,7 +561,7 @@ pub async fn execute_query_stub(
|
|||
.into_response()
|
||||
}
|
||||
|
||||
pub async fn list_query_history(
|
||||
pub(crate) async fn list_query_history(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
|
|
@ -571,7 +571,7 @@ pub async fn list_query_history(
|
|||
|
||||
// ── Models ────────────────────────────────────────────────────────────
|
||||
|
||||
pub async fn list_models(
|
||||
pub(crate) async fn list_models(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
|
|
@ -588,7 +588,7 @@ pub async fn list_models(
|
|||
|
||||
// ── Settings ───────────────────────────────────────────────────────────
|
||||
|
||||
pub async fn get_server_settings(
|
||||
pub(crate) async fn get_server_settings(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
) -> Response {
|
||||
|
|
@ -597,7 +597,7 @@ pub async fn get_server_settings(
|
|||
|
||||
// ── Usage ──────────────────────────────────────────────────────────────
|
||||
|
||||
pub async fn get_aggregate_usage(
|
||||
pub(crate) async fn get_aggregate_usage(
|
||||
_auth: AuthenticatedService,
|
||||
State(_state): State<Arc<AppState>>,
|
||||
) -> Response {
|
||||
|
|
@ -616,7 +616,7 @@ mod runs {
|
|||
use super::ts;
|
||||
use fabro_api_types::*;
|
||||
|
||||
pub fn list_items() -> Vec<RunListItem> {
|
||||
pub(super) fn list_items() -> Vec<RunListItem> {
|
||||
vec![
|
||||
RunListItem {
|
||||
id: "run-1".into(),
|
||||
|
|
@ -1062,7 +1062,7 @@ mod runs {
|
|||
]
|
||||
}
|
||||
|
||||
pub fn stages() -> Vec<RunStage> {
|
||||
pub(super) fn stages() -> Vec<RunStage> {
|
||||
vec![
|
||||
RunStage {
|
||||
id: "detect-drift".into(),
|
||||
|
|
@ -1095,7 +1095,7 @@ mod runs {
|
|||
]
|
||||
}
|
||||
|
||||
pub fn turns() -> Vec<StageTurn> {
|
||||
pub(super) fn turns() -> Vec<StageTurn> {
|
||||
vec![
|
||||
StageTurn::SystemStageTurn(SystemStageTurn { kind: SystemStageTurnKind::System, content: "You are a drift detection agent. Compare the production and staging environments and identify any configuration or code drift.".into() }),
|
||||
StageTurn::AssistantStageTurn(AssistantStageTurn { kind: AssistantStageTurnKind::Assistant, content: "I'll start by loading the environment configurations for both production and staging to compare them.".into() }),
|
||||
|
|
@ -1110,14 +1110,14 @@ mod runs {
|
|||
]
|
||||
}
|
||||
|
||||
pub fn files() -> Vec<FileDiff> {
|
||||
pub(super) fn files() -> Vec<FileDiff> {
|
||||
vec![
|
||||
FileDiff {
|
||||
old_file: DiffFile { name: "src/commands/run.ts".into(), contents: "import { parseArgs } from \"node:util\";\nimport { loadConfig } from \"../config.js\";\nimport { execute } from \"../executor.js\";\n\ninterface RunOptions {\n config: string;\n dryRun: boolean;\n}\n\nexport async function run(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\", default: \"fabro.toml\" },\n \"dry-run\": { type: \"boolean\", default: false },\n },\n });\n\n const opts: RunOptions = {\n config: values.config ?? \"fabro.toml\",\n dryRun: values[\"dry-run\"] ?? false,\n };\n\n const config = await loadConfig(opts.config);\n const result = await execute(config, { dryRun: opts.dryRun });\n\n if (result.success) {\n console.log(\"Run completed successfully.\");\n } else {\n console.error(\"Run failed:\", result.error);\n process.exitCode = 1;\n }\n}\n".into() },
|
||||
new_file: DiffFile { name: "src/commands/run.ts".into(), contents: "import { parseArgs } from \"node:util\";\nimport { loadConfig } from \"../config.js\";\nimport { execute } from \"../executor.js\";\nimport { createLogger, type Logger } from \"../logger.js\";\n\ninterface RunOptions {\n config: string;\n dryRun: boolean;\n verbose: boolean;\n}\n\nexport async function run(argv: string[]) {\n const { values } = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\", short: \"c\", default: \"fabro.toml\" },\n \"dry-run\": { type: \"boolean\", default: false },\n verbose: { type: \"boolean\", short: \"v\", default: false },\n },\n });\n\n const opts: RunOptions = {\n config: values.config ?? \"fabro.toml\",\n dryRun: values[\"dry-run\"] ?? false,\n verbose: values.verbose ?? false,\n };\n\n const logger: Logger = createLogger({ verbose: opts.verbose });\n\n const config = await loadConfig(opts.config);\n logger.debug(\"Loaded config from %s\", opts.config);\n\n const result = await execute(config, { dryRun: opts.dryRun, logger });\n logger.debug(\"Execution finished in %dms\", result.elapsed);\n\n if (result.success) {\n console.log(\"Run completed successfully.\");\n } else {\n console.error(\"Run failed:\", result.error);\n process.exitCode = 1;\n }\n}\n".into() },
|
||||
},
|
||||
FileDiff {
|
||||
old_file: DiffFile { name: "src/logger.ts".into(), contents: "".into() },
|
||||
old_file: DiffFile { name: "src/logger.ts".into(), contents: String::new() },
|
||||
new_file: DiffFile { name: "src/logger.ts".into(), contents: "export interface Logger {\n info(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n}\n\ninterface LoggerOptions {\n verbose: boolean;\n}\n\nexport function createLogger({ verbose }: LoggerOptions): Logger {\n return {\n info(message, ...args) {\n console.log(message, ...args);\n },\n debug(message, ...args) {\n if (verbose) {\n console.log(\"[debug]\", message, ...args);\n }\n },\n error(message, ...args) {\n console.error(message, ...args);\n },\n };\n}\n".into() },
|
||||
},
|
||||
FileDiff {
|
||||
|
|
@ -1127,7 +1127,7 @@ mod runs {
|
|||
]
|
||||
}
|
||||
|
||||
pub fn usage() -> RunUsage {
|
||||
pub(super) fn usage() -> RunUsage {
|
||||
RunUsage {
|
||||
stages: vec![
|
||||
UsageStage {
|
||||
|
|
@ -1235,11 +1235,11 @@ mod runs {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn verifications() -> Vec<fabro_api_types::RunVerification> {
|
||||
pub(super) fn verifications() -> Vec<fabro_api_types::RunVerification> {
|
||||
super::verifications::run_verifications()
|
||||
}
|
||||
|
||||
pub fn questions() -> Vec<ApiQuestion> {
|
||||
pub(super) fn questions() -> Vec<ApiQuestion> {
|
||||
vec![
|
||||
ApiQuestion {
|
||||
id: "q-001".into(),
|
||||
|
|
@ -1276,7 +1276,7 @@ mod runs {
|
|||
]
|
||||
}
|
||||
|
||||
pub fn settings() -> serde_json::Value {
|
||||
pub(super) fn settings() -> serde_json::Value {
|
||||
serde_json::to_value(fabro_config::FabroSettings {
|
||||
version: Some(1),
|
||||
goal: Some("Add rate limiting to auth endpoints".into()),
|
||||
|
|
@ -1338,7 +1338,7 @@ mod runs {
|
|||
mod usage {
|
||||
use fabro_api_types::*;
|
||||
|
||||
pub fn aggregate() -> AggregateUsage {
|
||||
pub(super) fn aggregate() -> AggregateUsage {
|
||||
AggregateUsage {
|
||||
totals: AggregateUsageTotals {
|
||||
runs: 9,
|
||||
|
|
@ -1390,7 +1390,7 @@ mod workflows {
|
|||
use super::ts;
|
||||
use fabro_api_types::*;
|
||||
|
||||
pub fn list_items() -> Vec<WorkflowListItem> {
|
||||
pub(super) fn list_items() -> Vec<WorkflowListItem> {
|
||||
vec![
|
||||
WorkflowListItem {
|
||||
name: "Fix Build".into(),
|
||||
|
|
@ -1450,7 +1450,7 @@ mod workflows {
|
|||
serde_json::from_value(val).unwrap()
|
||||
}
|
||||
|
||||
pub fn detail(name: &str) -> Option<WorkflowDetail> {
|
||||
pub(super) fn detail(name: &str) -> Option<WorkflowDetail> {
|
||||
let items = [
|
||||
WorkflowDetail {
|
||||
name: "Fix Build".into(), slug: "fix_build".into(), filename: "fix_build.fabro".into(),
|
||||
|
|
@ -2679,7 +2679,7 @@ mod verifications {
|
|||
|
||||
// ── Public API ──────────────────────────────────────────────────────
|
||||
|
||||
pub fn criteria() -> Vec<VerificationCriterion> {
|
||||
pub(super) fn criteria() -> Vec<VerificationCriterion> {
|
||||
ALL_CATEGORIES
|
||||
.iter()
|
||||
.map(|cat| VerificationCriterion {
|
||||
|
|
@ -2703,7 +2703,7 @@ mod verifications {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn criterion_detail(id: &str) -> Option<VerificationCriterionDetail> {
|
||||
pub(super) fn criterion_detail(id: &str) -> Option<VerificationCriterionDetail> {
|
||||
ALL_CATEGORIES
|
||||
.iter()
|
||||
.find(|cat| slugify(cat.name) == id)
|
||||
|
|
@ -2727,7 +2727,7 @@ mod verifications {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn controls() -> Vec<VerificationControlListItem> {
|
||||
pub(super) fn controls() -> Vec<VerificationControlListItem> {
|
||||
ALL_CATEGORIES
|
||||
.iter()
|
||||
.flat_map(|cat| {
|
||||
|
|
@ -2749,7 +2749,7 @@ mod verifications {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn control_detail(slug: &str) -> Option<VerificationDetailResponse> {
|
||||
pub(super) fn control_detail(slug: &str) -> Option<VerificationDetailResponse> {
|
||||
for cat in ALL_CATEGORIES {
|
||||
for (idx, ctrl) in cat.controls.iter().enumerate() {
|
||||
if ctrl.slug == slug {
|
||||
|
|
@ -2802,7 +2802,7 @@ mod verifications {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn run_verifications() -> Vec<RunVerification> {
|
||||
pub(super) fn run_verifications() -> Vec<RunVerification> {
|
||||
ALL_CATEGORIES
|
||||
.iter()
|
||||
.map(|cat| RunVerification {
|
||||
|
|
@ -2917,7 +2917,7 @@ mod signoffs {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn list_items(
|
||||
pub(super) fn list_items(
|
||||
control: Option<&str>,
|
||||
repository: Option<&str>,
|
||||
commit_sha: Option<&str>,
|
||||
|
|
@ -2931,11 +2931,11 @@ mod signoffs {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn detail(id: &str) -> Option<Signoff> {
|
||||
pub(super) fn detail(id: &str) -> Option<Signoff> {
|
||||
ALL_SIGNOFFS.iter().find(|s| s.id == id).map(to_signoff)
|
||||
}
|
||||
|
||||
pub fn stub_created() -> Signoff {
|
||||
pub(super) fn stub_created() -> Signoff {
|
||||
to_signoff(&ALL_SIGNOFFS[0])
|
||||
}
|
||||
}
|
||||
|
|
@ -2969,7 +2969,7 @@ mod retros {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn detail(run_id: &str) -> Option<RetroDetail> {
|
||||
pub(super) fn detail(run_id: &str) -> Option<RetroDetail> {
|
||||
match run_id {
|
||||
"run-1" => Some(RetroDetail {
|
||||
run_id: "run-1".into(),
|
||||
|
|
@ -3141,7 +3141,7 @@ mod retros {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn list_items() -> Vec<RetroListItem> {
|
||||
pub(super) fn list_items() -> Vec<RetroListItem> {
|
||||
vec![
|
||||
RetroListItem {
|
||||
run: RunReference {
|
||||
|
|
@ -3292,7 +3292,7 @@ mod sessions {
|
|||
const S7: u128 = 0x10000000_0000_4000_8000_000000000007;
|
||||
const S8: u128 = 0x10000000_0000_4000_8000_000000000008;
|
||||
|
||||
pub fn list_items() -> Vec<SessionListItem> {
|
||||
pub(super) fn list_items() -> Vec<SessionListItem> {
|
||||
vec![
|
||||
SessionListItem {
|
||||
id: uid(S1),
|
||||
|
|
@ -3361,7 +3361,7 @@ mod sessions {
|
|||
]
|
||||
}
|
||||
|
||||
pub fn detail(id: &str) -> Option<SessionDetail> {
|
||||
pub(super) fn detail(id: &str) -> Option<SessionDetail> {
|
||||
let parsed = id.parse::<Uuid>().ok()?;
|
||||
match parsed.as_u128() {
|
||||
S1 => Some(SessionDetail {
|
||||
|
|
@ -3420,7 +3420,7 @@ mod insights {
|
|||
use super::ts;
|
||||
use fabro_api_types::*;
|
||||
|
||||
pub fn saved_queries() -> Vec<SavedQuery> {
|
||||
pub(super) fn saved_queries() -> Vec<SavedQuery> {
|
||||
vec![
|
||||
SavedQuery { id: "1".into(), name: "Run duration by workflow".into(), sql: "SELECT workflow_name, AVG(duration_seconds) as avg_duration,\n COUNT(*) as run_count\nFROM runs\nGROUP BY workflow_name\nORDER BY avg_duration DESC\nLIMIT 20".into(), created_at: ts("2026-03-01T10:00:00Z"), updated_at: ts("2026-03-05T14:30:00Z") },
|
||||
SavedQuery { id: "2".into(), name: "Daily failure rate".into(), sql: "SELECT date_trunc('day', created_at) as day,\n COUNT(*) FILTER (WHERE status = 'failed') as failures,\n COUNT(*) as total\nFROM runs\nGROUP BY 1\nORDER BY 1 DESC\nLIMIT 30".into(), created_at: ts("2026-03-02T09:00:00Z"), updated_at: ts("2026-03-02T09:00:00Z") },
|
||||
|
|
@ -3428,7 +3428,7 @@ mod insights {
|
|||
]
|
||||
}
|
||||
|
||||
pub fn history() -> Vec<HistoryEntry> {
|
||||
pub(super) fn history() -> Vec<HistoryEntry> {
|
||||
vec![
|
||||
HistoryEntry {
|
||||
id: "h1".into(),
|
||||
|
|
@ -3460,7 +3460,7 @@ mod settings {
|
|||
use fabro_config::FabroSettings;
|
||||
use fabro_config::server::*;
|
||||
|
||||
pub fn server_settings() -> serde_json::Value {
|
||||
pub(super) fn server_settings() -> serde_json::Value {
|
||||
serde_json::to_value(FabroSettings {
|
||||
storage_dir: Some("/home/fabro/.fabro".into()),
|
||||
max_concurrent_runs: Some(10),
|
||||
|
|
|
|||
|
|
@ -17,19 +17,16 @@ type HmacSha256 = Hmac<Sha256>;
|
|||
/// `signature_header` is the value of the `X-Hub-Signature-256` header,
|
||||
/// expected in the form `sha256=<hex-digest>`.
|
||||
pub fn verify_signature(secret: &[u8], body: &[u8], signature_header: &str) -> bool {
|
||||
let hex_digest = match signature_header.strip_prefix("sha256=") {
|
||||
Some(h) => h,
|
||||
None => return false,
|
||||
let Some(hex_digest) = signature_header.strip_prefix("sha256=") else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let expected = match hex::decode(hex_digest) {
|
||||
Ok(b) => b,
|
||||
Err(_) => return false,
|
||||
let Ok(expected) = hex::decode(hex_digest) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let mut mac = match HmacSha256::new_from_slice(secret) {
|
||||
Ok(m) => m,
|
||||
Err(_) => return false,
|
||||
let Ok(mut mac) = HmacSha256::new_from_slice(secret) else {
|
||||
return false;
|
||||
};
|
||||
mac.update(body);
|
||||
mac.verify_slice(&expected).is_ok()
|
||||
|
|
@ -50,15 +47,12 @@ async fn webhook_handler(
|
|||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("unknown");
|
||||
|
||||
let signature = match headers
|
||||
let Some(signature) = headers
|
||||
.get("x-hub-signature-256")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
{
|
||||
Some(s) => s,
|
||||
None => {
|
||||
warn!(delivery = %delivery_id, "Webhook signature verification failed");
|
||||
return StatusCode::UNAUTHORIZED;
|
||||
}
|
||||
else {
|
||||
warn!(delivery = %delivery_id, "Webhook signature verification failed");
|
||||
return StatusCode::UNAUTHORIZED;
|
||||
};
|
||||
|
||||
if !verify_signature(&state.secret, &body, signature) {
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedService {
|
|||
.expect("AuthMode extension must be added to the router");
|
||||
|
||||
let strategies = match auth_mode {
|
||||
AuthMode::Disabled => return Ok(AuthenticatedService),
|
||||
AuthMode::Disabled => return Ok(Self),
|
||||
AuthMode::Strategies(strategies) => strategies,
|
||||
};
|
||||
|
||||
|
|
@ -246,7 +246,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedService {
|
|||
} => try_jwt(parts, key, validation, allowed_usernames),
|
||||
};
|
||||
match result {
|
||||
Ok(()) => return Ok(AuthenticatedService),
|
||||
Ok(()) => return Ok(Self),
|
||||
Err(e) => last_err = e,
|
||||
}
|
||||
}
|
||||
|
|
@ -275,7 +275,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedUser {
|
|||
|
||||
let strategies = match auth_mode {
|
||||
AuthMode::Disabled => {
|
||||
return Ok(AuthenticatedUser {
|
||||
return Ok(Self {
|
||||
login: "demo".to_string(),
|
||||
});
|
||||
}
|
||||
|
|
@ -297,7 +297,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedUser {
|
|||
} => {
|
||||
if try_jwt(parts, key, validation, allowed_usernames).is_ok() {
|
||||
if let Some(login) = extract_jwt_login(parts, key, validation) {
|
||||
return Ok(AuthenticatedUser { login });
|
||||
return Ok(Self { login });
|
||||
}
|
||||
}
|
||||
last_err = ApiError::unauthorized();
|
||||
|
|
@ -305,7 +305,7 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedUser {
|
|||
AuthStrategy::Mtls => {
|
||||
if try_mtls(parts).is_ok() {
|
||||
if let Some(login) = extract_mtls_cn(parts) {
|
||||
return Ok(AuthenticatedUser { login });
|
||||
return Ok(Self { login });
|
||||
}
|
||||
}
|
||||
last_err = ApiError::unauthorized();
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ pub struct ServeArgs {
|
|||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the server fails to bind or encounters a fatal error.
|
||||
#[allow(clippy::print_stderr)]
|
||||
pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::Result<()> {
|
||||
// Resolve dry-run mode (same pattern as run.rs)
|
||||
let dry_run_mode = if args.dry_run {
|
||||
|
|
@ -186,22 +187,19 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::
|
|||
Some(app_id) => {
|
||||
let secret = std::env::var("GITHUB_APP_WEBHOOK_SECRET").ok();
|
||||
let private_key_pem = read_github_private_key();
|
||||
match (secret, private_key_pem) {
|
||||
(Some(secret), Some(pem)) => {
|
||||
match WebhookManager::start(secret.into_bytes(), &app_id, &pem).await {
|
||||
Ok(manager) => Some(manager),
|
||||
Err(err) => {
|
||||
error!(error = %err, "Failed to start webhook listener");
|
||||
None
|
||||
}
|
||||
if let (Some(secret), Some(pem)) = (secret, private_key_pem) {
|
||||
match WebhookManager::start(secret.into_bytes(), &app_id, &pem).await {
|
||||
Ok(manager) => Some(manager),
|
||||
Err(err) => {
|
||||
error!(error = %err, "Failed to start webhook listener");
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
warn!(
|
||||
"Webhook config present but GITHUB_APP_WEBHOOK_SECRET or GITHUB_APP_PRIVATE_KEY not set; skipping webhook listener"
|
||||
);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Webhook config present but GITHUB_APP_WEBHOOK_SECRET or GITHUB_APP_PRIVATE_KEY not set; skipping webhook listener"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
|
|
@ -274,8 +272,8 @@ fn resolve_model_provider(
|
|||
|
||||
let provider_str = cli_provider.or(config_provider);
|
||||
let model = cli_model
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| config_model.map(|s| s.to_string()))
|
||||
.map(std::string::ToString::to_string)
|
||||
.or_else(|| config_model.map(std::string::ToString::to_string))
|
||||
.unwrap_or_else(|| {
|
||||
// Look up default model from catalog for the given provider,
|
||||
// falling back to the best provider with an API key configured.
|
||||
|
|
@ -292,10 +290,10 @@ fn resolve_model_provider(
|
|||
Some(info) => (
|
||||
info.id.clone(),
|
||||
provider_str
|
||||
.map(|s| s.to_string())
|
||||
.map(std::string::ToString::to_string)
|
||||
.or(Some(info.provider.to_string())),
|
||||
),
|
||||
None => (model, provider_str.map(|s| s.to_string())),
|
||||
None => (model, provider_str.map(std::string::ToString::to_string)),
|
||||
};
|
||||
|
||||
let provider_enum: Provider = provider_str
|
||||
|
|
|
|||
|
|
@ -606,9 +606,8 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
Some(r) if r.status == RunStatus::Queued => r,
|
||||
_ => return,
|
||||
};
|
||||
let run_dir = match managed_run.run_dir.clone() {
|
||||
Some(path) => path,
|
||||
None => return,
|
||||
let Some(run_dir) = managed_run.run_dir.clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (cancel_tx, cancel_rx) = oneshot::channel::<()>();
|
||||
|
|
@ -643,9 +642,8 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
|
|||
let runs = state.runs.lock().expect("runs lock poisoned");
|
||||
runs.get(&run_id).and_then(|r| r.cancel_token.clone())
|
||||
};
|
||||
let cancel_token = match cancel_token {
|
||||
Some(ct) => ct,
|
||||
None => return,
|
||||
let Some(cancel_token) = cancel_token else {
|
||||
return;
|
||||
};
|
||||
|
||||
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
|
||||
|
|
@ -835,8 +833,8 @@ pub fn spawn_scheduler(state: Arc<AppState>) {
|
|||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = state.scheduler_notify.notified() => {},
|
||||
_ = sleep(std::time::Duration::from_secs(1)) => {},
|
||||
() = state.scheduler_notify.notified() => {},
|
||||
() = sleep(std::time::Duration::from_secs(1)) => {},
|
||||
}
|
||||
// Promote as many queued runs as capacity allows
|
||||
loop {
|
||||
|
|
@ -862,7 +860,7 @@ pub fn spawn_scheduler(state: Arc<AppState>) {
|
|||
tokio::spawn(execute_run(state_clone, id));
|
||||
}
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -911,15 +909,12 @@ async fn get_questions(
|
|||
let runs = state.runs.lock().expect("runs lock poisoned");
|
||||
match runs.get(&id) {
|
||||
Some(managed_run) => {
|
||||
let interviewer = match &managed_run.interviewer {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
return (
|
||||
StatusCode::OK,
|
||||
Json(ListResponse::new(Vec::<ApiQuestion>::new())),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
let Some(interviewer) = &managed_run.interviewer else {
|
||||
return (
|
||||
StatusCode::OK,
|
||||
Json(ListResponse::new(Vec::<ApiQuestion>::new())),
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
let pending = interviewer.pending_questions();
|
||||
let questions: Vec<ApiQuestion> = pending
|
||||
|
|
@ -961,12 +956,9 @@ async fn submit_answer(
|
|||
let runs = state.runs.lock().expect("runs lock poisoned");
|
||||
match runs.get(&id) {
|
||||
Some(managed_run) => {
|
||||
let interviewer = match &managed_run.interviewer {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
return ApiError::new(StatusCode::CONFLICT, "Run is not yet running.")
|
||||
.into_response();
|
||||
}
|
||||
let Some(interviewer) = &managed_run.interviewer else {
|
||||
return ApiError::new(StatusCode::CONFLICT, "Run is not yet running.")
|
||||
.into_response();
|
||||
};
|
||||
let answer = if let Some(key) = &req.selected_option_key {
|
||||
let option = interviewer
|
||||
|
|
|
|||
|
|
@ -92,9 +92,8 @@ fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool,
|
|||
tokio::spawn(async move {
|
||||
let (event_tx, model_id, model_provider, system_prompt, messages, generation_seq) = {
|
||||
let store = store.read().expect("session store lock poisoned");
|
||||
let session = match store.get(&session_id) {
|
||||
Some(s) => s,
|
||||
None => return,
|
||||
let Some(session) = store.get(&session_id) else {
|
||||
return;
|
||||
};
|
||||
(
|
||||
session.event_tx.clone(),
|
||||
|
|
|
|||
|
|
@ -91,8 +91,11 @@ pub async fn serve_tls(
|
|||
|
||||
// Extract peer certificates once per connection (not per request)
|
||||
let (_, server_conn) = tls_stream.get_ref();
|
||||
let peer_certs =
|
||||
PeerCertificates(server_conn.peer_certificates().map(|certs| certs.to_vec()));
|
||||
let peer_certs = PeerCertificates(
|
||||
server_conn
|
||||
.peer_certificates()
|
||||
.map(<[rustls_pki_types::CertificateDer<'_>]>::to_vec),
|
||||
);
|
||||
|
||||
let io = TokioIo::new(tls_stream);
|
||||
|
||||
|
|
|
|||
|
|
@ -501,7 +501,7 @@ pub(crate) struct WaitArgs {
|
|||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct WorkflowListArgs {}
|
||||
pub(crate) struct WorkflowListArgs;
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct WorkflowCreateArgs {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#[allow(unused_imports)]
|
||||
pub use fabro_config::cli::*;
|
||||
pub(crate) use fabro_config::cli::*;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ use fabro_config::cli::load_cli_config;
|
|||
#[cfg(feature = "server")]
|
||||
use tracing::debug;
|
||||
|
||||
pub fn load_cli_settings(path: Option<&Path>) -> anyhow::Result<FabroSettings> {
|
||||
pub(crate) fn load_cli_settings(path: Option<&Path>) -> anyhow::Result<FabroSettings> {
|
||||
load_cli_config(path)?.try_into()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use crate::args::AssetCpArgs;
|
|||
use crate::cli_config::load_cli_settings;
|
||||
use crate::shared::split_run_path;
|
||||
|
||||
pub fn cp_command(args: &AssetCpArgs) -> Result<()> {
|
||||
pub(super) fn cp_command(args: &AssetCpArgs) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let (run_id, asset_path) = parse_source(&args.source);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::args::AssetListArgs;
|
|||
use crate::cli_config::load_cli_settings;
|
||||
use crate::shared::format_size;
|
||||
|
||||
pub fn list_command(args: &AssetListArgs) -> Result<()> {
|
||||
pub(super) fn list_command(args: &AssetListArgs) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let run = resolve_run(&base, &args.run_id)?;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use anyhow::Result;
|
|||
|
||||
use crate::args::{AssetCommand, AssetNamespace};
|
||||
|
||||
pub fn dispatch(ns: AssetNamespace) -> Result<()> {
|
||||
pub(crate) fn dispatch(ns: AssetNamespace) -> Result<()> {
|
||||
match ns.command {
|
||||
AssetCommand::List(args) => list::list_command(&args),
|
||||
AssetCommand::Cp(args) => cp::cp_command(&args),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use fabro_config::cli::load_cli_config;
|
|||
use fabro_config::project::{ResolveSettingsInput, discover_project_config, resolve_settings};
|
||||
use fabro_config::{FabroConfig, FabroSettings};
|
||||
|
||||
pub fn dispatch(ns: ConfigNamespace) -> anyhow::Result<()> {
|
||||
pub(crate) fn dispatch(ns: ConfigNamespace) -> anyhow::Result<()> {
|
||||
match ns.command {
|
||||
ConfigCommand::Show(args) => show_command(&args),
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ fn merged_config(workflow: Option<&Path>) -> anyhow::Result<FabroSettings> {
|
|||
FabroConfig::combine(project_config, cli_config).try_into()
|
||||
}
|
||||
|
||||
pub fn show_command(args: &ConfigShowArgs) -> anyhow::Result<()> {
|
||||
pub(crate) fn show_command(args: &ConfigShowArgs) -> anyhow::Result<()> {
|
||||
let config = merged_config(args.workflow.as_deref())?;
|
||||
let mut yaml = serde_yaml::to_string(&config)?;
|
||||
if !yaml.ends_with('\n') {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use fabro_config::server::{ApiAuthStrategy, AuthProvider};
|
|||
use fabro_llm::client::Client as LlmClient;
|
||||
use fabro_llm::types::{Message, Request};
|
||||
use fabro_model::{Catalog, Provider};
|
||||
pub use fabro_util::check_report::{
|
||||
pub(crate) use fabro_util::check_report::{
|
||||
CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus,
|
||||
};
|
||||
use fabro_util::terminal::Styles;
|
||||
|
|
@ -194,7 +194,7 @@ fn apply_live_result(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn check_config(path: Option<PathBuf>) -> CheckResult {
|
||||
pub(crate) fn check_config(path: Option<PathBuf>) -> CheckResult {
|
||||
match path {
|
||||
Some(p) => CheckResult {
|
||||
name: "Configuration".to_string(),
|
||||
|
|
@ -215,7 +215,7 @@ pub fn check_config(path: Option<PathBuf>) -> CheckResult {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn check_llm_providers(
|
||||
pub(crate) fn check_llm_providers(
|
||||
statuses: &[(Provider, bool)],
|
||||
live_results: Option<&[(Provider, Result<(), String>)]>,
|
||||
) -> CheckResult {
|
||||
|
|
@ -252,7 +252,10 @@ pub fn check_llm_providers(
|
|||
remediation: Some("Set at least one provider API key".to_string()),
|
||||
}
|
||||
} else if !failed_providers.is_empty() {
|
||||
let names: Vec<_> = failed_providers.iter().map(|p| p.to_string()).collect();
|
||||
let names: Vec<_> = failed_providers
|
||||
.iter()
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect();
|
||||
CheckResult {
|
||||
name: "LLM providers".to_string(),
|
||||
status: CheckStatus::Warning,
|
||||
|
|
@ -271,7 +274,7 @@ pub fn check_llm_providers(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn check_brave_search(
|
||||
pub(crate) fn check_brave_search(
|
||||
api_key_set: bool,
|
||||
live_result: Option<&Result<(), String>>,
|
||||
) -> CheckResult {
|
||||
|
|
@ -317,12 +320,12 @@ pub fn check_brave_search(
|
|||
}
|
||||
}
|
||||
|
||||
pub struct SandboxStatus {
|
||||
pub(crate) struct SandboxStatus {
|
||||
pub daytona_configured: bool,
|
||||
pub daytona_probe: Option<Result<(), String>>,
|
||||
}
|
||||
|
||||
pub fn check_sandbox(status: &SandboxStatus) -> CheckResult {
|
||||
pub(crate) fn check_sandbox(status: &SandboxStatus) -> CheckResult {
|
||||
let mut details = Vec::new();
|
||||
|
||||
match &status.daytona_probe {
|
||||
|
|
@ -378,7 +381,7 @@ pub fn check_sandbox(status: &SandboxStatus) -> CheckResult {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct GithubAppStatus {
|
||||
pub(crate) struct GithubAppStatus {
|
||||
pub app_id: Option<String>,
|
||||
pub slug: Option<String>,
|
||||
pub private_key_set: bool,
|
||||
|
|
@ -416,7 +419,7 @@ impl GithubAppStatus {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn check_github_app(status: &GithubAppStatus) -> CheckResult {
|
||||
pub(crate) fn check_github_app(status: &GithubAppStatus) -> CheckResult {
|
||||
let mut details: Vec<CheckDetail> = Vec::new();
|
||||
|
||||
match (&status.app_id, &status.slug) {
|
||||
|
|
@ -922,7 +925,7 @@ async fn probe_url(http: &reqwest::Client, url: &str) -> Result<(), String> {
|
|||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub async fn run_doctor(verbose: bool, live: bool) -> i32 {
|
||||
pub(crate) async fn run_doctor(verbose: bool, live: bool) -> i32 {
|
||||
let styles = Styles::detect_stdout();
|
||||
|
||||
let spinner = indicatif::ProgressBar::new_spinner();
|
||||
|
|
@ -999,7 +1002,7 @@ pub async fn run_doctor(verbose: bool, live: bool) -> i32 {
|
|||
Ok(pem) => Some(
|
||||
fabro_github::sign_app_jwt(app_id, &pem)
|
||||
.map(|_| ())
|
||||
.map_err(|e| e.to_string()),
|
||||
.map_err(|e| e.clone()),
|
||||
),
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
|
|
@ -1177,7 +1180,7 @@ pub async fn run_doctor(verbose: bool, live: bool) -> i32 {
|
|||
report.render(&styles, verbose, None, Some(term_width))
|
||||
);
|
||||
|
||||
if report.has_errors() { 1 } else { 0 }
|
||||
i32::from(report.has_errors())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use fabro_mcp::config::McpServerConfig;
|
|||
use crate::args::GlobalArgs;
|
||||
use crate::cli_config;
|
||||
|
||||
pub async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> {
|
||||
pub(crate) async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> {
|
||||
let cli_config = cli_config::load_cli_settings(None)?;
|
||||
#[cfg(feature = "sleep_inhibitor")]
|
||||
let _sleep_guard = crate::sleep_inhibitor::guard(cli_config.prevent_idle_sleep_enabled());
|
||||
|
|
@ -63,7 +63,7 @@ pub async fn execute(mut args: AgentArgs, globals: &GlobalArgs) -> Result<()> {
|
|||
{
|
||||
let _ = globals;
|
||||
tracing::info!(mode = "standalone", "Agent session starting");
|
||||
run_with_args(args, mcp_servers).await?
|
||||
run_with_args(args, mcp_servers).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use crate::shared::{print_diagnostics, read_workflow_file, relative_path};
|
|||
static RANKDIR_RE: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"rankdir\s*=\s*\w+").unwrap());
|
||||
|
||||
pub fn run(args: &GraphArgs, styles: &Styles) -> anyhow::Result<()> {
|
||||
pub(crate) fn run(args: &GraphArgs, styles: &Styles) -> anyhow::Result<()> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
let cli_defaults = load_cli_config(None)?;
|
||||
let settings = resolve_settings(ResolveSettingsInput {
|
||||
|
|
@ -61,7 +61,7 @@ pub fn run(args: &GraphArgs, styles: &Styles) -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_direction<'a>(source: &'a str, direction: Option<GraphDirection>) -> Cow<'a, str> {
|
||||
fn apply_direction(source: &str, direction: Option<GraphDirection>) -> Cow<'_, str> {
|
||||
match direction {
|
||||
Some(dir) => {
|
||||
let replacement = format!("rankdir={dir}");
|
||||
|
|
|
|||
|
|
@ -286,9 +286,11 @@ async fn setup_github_app(
|
|||
) -> Result<Vec<(String, String)>> {
|
||||
// Random suffix so app names don't collide
|
||||
let mut rng = rand::thread_rng();
|
||||
let suffix: String = (0..6)
|
||||
.map(|_| format!("{:x}", rng.gen::<u8>() % 16))
|
||||
.collect();
|
||||
let suffix: String = (0..6).fold(String::with_capacity(6), |mut s, _| {
|
||||
use std::fmt::Write;
|
||||
let _ = write!(s, "{:x}", rng.gen::<u8>() % 16);
|
||||
s
|
||||
});
|
||||
let app_name = format!("Arc-{suffix}");
|
||||
|
||||
// Bind to random port
|
||||
|
|
@ -441,14 +443,14 @@ async fn setup_github_app(
|
|||
let cli_toml_path = arc_dir.join("cli.toml");
|
||||
let existing = std::fs::read_to_string(&cli_toml_path).unwrap_or_default();
|
||||
let mut doc: toml::Value = if existing.is_empty() {
|
||||
toml::Value::Table(Default::default())
|
||||
toml::Value::Table(toml::Table::default())
|
||||
} else {
|
||||
toml::from_str(&existing).context("failed to parse existing cli.toml")?
|
||||
};
|
||||
let table = doc.as_table_mut().context("cli.toml root is not a table")?;
|
||||
let git = table
|
||||
.entry("git")
|
||||
.or_insert(toml::Value::Table(Default::default()));
|
||||
.or_insert(toml::Value::Table(toml::Table::default()));
|
||||
let git_table = git
|
||||
.as_table_mut()
|
||||
.context("cli.toml [git] is not a table")?;
|
||||
|
|
@ -480,7 +482,7 @@ async fn setup_github_app(
|
|||
Ok(env_pairs)
|
||||
}
|
||||
|
||||
pub async fn run_install(web_url: &str) -> Result<()> {
|
||||
pub(crate) async fn run_install(web_url: &str) -> Result<()> {
|
||||
let s = Styles::detect_stderr();
|
||||
let emoji = console::Emoji("⚒️ ", "");
|
||||
|
||||
|
|
@ -649,8 +651,8 @@ pub async fn run_install(web_url: &str) -> Result<()> {
|
|||
let slug = {
|
||||
let cli_toml_path = arc_dir.join("cli.toml");
|
||||
let toml_content = std::fs::read_to_string(&cli_toml_path).unwrap_or_default();
|
||||
let doc: toml::Value =
|
||||
toml::from_str(&toml_content).unwrap_or(toml::Value::Table(Default::default()));
|
||||
let doc: toml::Value = toml::from_str(&toml_content)
|
||||
.unwrap_or(toml::Value::Table(toml::Table::default()));
|
||||
doc.get("git")
|
||||
.and_then(|g| g.get("slug"))
|
||||
.and_then(|s| s.as_str())
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use fabro_llm::cli::{ServerConnection, run_chat_via_server};
|
|||
|
||||
use crate::args::GlobalArgs;
|
||||
|
||||
pub async fn execute(
|
||||
pub(super) async fn execute(
|
||||
mut args: ChatArgs,
|
||||
cli_config: &FabroSettings,
|
||||
globals: &GlobalArgs,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use anyhow::Result;
|
|||
use crate::args::{GlobalArgs, LlmCommand, LlmNamespace};
|
||||
use crate::cli_config::load_cli_settings;
|
||||
|
||||
pub async fn dispatch(ns: LlmNamespace, globals: &GlobalArgs) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: LlmNamespace, globals: &GlobalArgs) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
|
||||
match ns.command {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use fabro_llm::cli::{ServerConnection, run_prompt_via_server};
|
|||
|
||||
use crate::args::GlobalArgs;
|
||||
|
||||
pub async fn execute(
|
||||
pub(super) async fn execute(
|
||||
mut args: PromptArgs,
|
||||
cli_config: &FabroSettings,
|
||||
globals: &GlobalArgs,
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
pub mod asset;
|
||||
pub mod config;
|
||||
pub mod doctor;
|
||||
pub mod exec;
|
||||
pub mod graph;
|
||||
pub mod install;
|
||||
pub mod llm;
|
||||
pub mod model;
|
||||
pub mod parse;
|
||||
pub mod pr;
|
||||
pub mod preflight;
|
||||
pub mod provider;
|
||||
pub mod repo;
|
||||
pub mod run;
|
||||
pub mod runs;
|
||||
pub mod secret;
|
||||
pub mod skill;
|
||||
pub mod system;
|
||||
pub mod upgrade;
|
||||
pub mod validate;
|
||||
pub mod workflow;
|
||||
pub(crate) mod asset;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod doctor;
|
||||
pub(crate) mod exec;
|
||||
pub(crate) mod graph;
|
||||
pub(crate) mod install;
|
||||
pub(crate) mod llm;
|
||||
pub(crate) mod model;
|
||||
pub(crate) mod parse;
|
||||
pub(crate) mod pr;
|
||||
pub(crate) mod preflight;
|
||||
pub(crate) mod provider;
|
||||
pub(crate) mod repo;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod runs;
|
||||
pub(crate) mod secret;
|
||||
pub(crate) mod skill;
|
||||
pub(crate) mod system;
|
||||
pub(crate) mod upgrade;
|
||||
pub(crate) mod validate;
|
||||
pub(crate) mod workflow;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::args::GlobalArgs;
|
|||
#[cfg(feature = "server")]
|
||||
use crate::cli_config;
|
||||
|
||||
pub async fn execute(command: Option<ModelsCommand>, globals: &GlobalArgs) -> Result<()> {
|
||||
pub(crate) async fn execute(command: Option<ModelsCommand>, globals: &GlobalArgs) -> Result<()> {
|
||||
let server = {
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use fabro_graphviz::parser::parse_ast;
|
|||
use crate::args::ParseArgs;
|
||||
use crate::shared::read_workflow_file;
|
||||
|
||||
pub fn run(args: &ParseArgs) -> anyhow::Result<()> {
|
||||
pub(crate) fn run(args: &ParseArgs) -> anyhow::Result<()> {
|
||||
let stdout = std::io::stdout();
|
||||
run_to(args, stdout.lock())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use tracing::info;
|
|||
use crate::args::PrCloseArgs;
|
||||
use crate::cli_config::load_cli_settings;
|
||||
|
||||
pub async fn close_command(
|
||||
pub(super) async fn close_command(
|
||||
args: PrCloseArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use tracing::info;
|
|||
use crate::args::PrCreateArgs;
|
||||
use crate::cli_config::load_cli_settings;
|
||||
|
||||
pub async fn create_command(
|
||||
pub(super) async fn create_command(
|
||||
args: PrCreateArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use tracing::info;
|
|||
use crate::args::PrListArgs;
|
||||
use crate::cli_config::load_cli_settings;
|
||||
|
||||
pub async fn list_command(
|
||||
pub(super) async fn list_command(
|
||||
args: PrListArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use fabro_workflows::run_lookup::runs_base;
|
|||
use crate::args::PrMergeArgs;
|
||||
use crate::cli_config::load_cli_settings;
|
||||
|
||||
pub async fn merge_command(
|
||||
pub(super) async fn merge_command(
|
||||
args: PrMergeArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use crate::args::{PrCommand, PrNamespace};
|
|||
use crate::cli_config::load_cli_settings;
|
||||
use crate::shared::github::build_github_app_credentials;
|
||||
|
||||
pub async fn dispatch(ns: PrNamespace) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: PrNamespace) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let github_app = build_github_app_credentials(cli_config.app_id());
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use fabro_workflows::run_lookup::runs_base;
|
|||
use crate::args::PrViewArgs;
|
||||
use crate::cli_config::load_cli_settings;
|
||||
|
||||
pub async fn view_command(
|
||||
pub(super) async fn view_command(
|
||||
args: PrViewArgs,
|
||||
github_app: Option<fabro_github::GitHubAppCredentials>,
|
||||
) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate};
|
|||
use crate::args::PreflightArgs;
|
||||
use crate::shared::github::build_github_app_credentials;
|
||||
|
||||
pub async fn execute(mut args: PreflightArgs) -> anyhow::Result<()> {
|
||||
pub(crate) async fn execute(mut args: PreflightArgs) -> anyhow::Result<()> {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let cli_defaults = load_cli_config(None)?;
|
||||
let cli_config: FabroSettings = cli_defaults.clone().try_into()?;
|
||||
|
|
@ -121,7 +121,7 @@ fn parse_sandbox_provider(settings: &FabroSettings) -> anyhow::Result<Option<San
|
|||
settings
|
||||
.sandbox_settings()
|
||||
.and_then(|s| s.provider.as_deref())
|
||||
.map(|s| s.parse::<SandboxProvider>())
|
||||
.map(str::parse::<SandboxProvider>)
|
||||
.transpose()
|
||||
.map_err(|e| anyhow::anyhow!("Invalid sandbox provider: {e}"))
|
||||
}
|
||||
|
|
@ -367,8 +367,11 @@ async fn run_preflight(
|
|||
let default_provider = provider.as_deref().unwrap_or("anthropic");
|
||||
let llm_ok = match LlmClient::from_env().await {
|
||||
Ok(c) => {
|
||||
let configured: Vec<String> =
|
||||
c.provider_names().iter().map(|s| s.to_string()).collect();
|
||||
let configured: Vec<String> = c
|
||||
.provider_names()
|
||||
.iter()
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect();
|
||||
|
||||
let mut model_providers = std::collections::BTreeSet::new();
|
||||
for node in graph.nodes.values() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use tokio::task::spawn_blocking;
|
|||
use crate::args::ProviderLoginArgs;
|
||||
use crate::shared::provider_auth;
|
||||
|
||||
pub async fn login_command(args: ProviderLoginArgs) -> Result<()> {
|
||||
pub(super) async fn login_command(args: ProviderLoginArgs) -> Result<()> {
|
||||
let s = Styles::detect_stderr();
|
||||
let arc_dir = dirs::home_dir()
|
||||
.context("could not determine home directory")?
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
|
||||
use crate::args::{ProviderCommand, ProviderNamespace};
|
||||
|
||||
pub async fn dispatch(ns: ProviderNamespace) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: ProviderNamespace) -> Result<()> {
|
||||
match ns.command {
|
||||
ProviderCommand::Login(args) => login::login_command(args).await,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::{Context, Result, bail};
|
||||
|
||||
pub fn run_deinit() -> Result<()> {
|
||||
pub(crate) fn run_deinit() -> Result<()> {
|
||||
let repo_root = super::init::git_repo_root()?;
|
||||
|
||||
let fabro_toml = repo_root.join("fabro.toml");
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ pub(super) fn git_repo_root() -> Result<PathBuf> {
|
|||
))
|
||||
}
|
||||
|
||||
pub async fn run_init() -> Result<()> {
|
||||
pub(crate) async fn run_init() -> Result<()> {
|
||||
let repo_root = git_repo_root()?;
|
||||
|
||||
let fabro_toml = repo_root.join("fabro.toml");
|
||||
|
|
@ -148,46 +148,40 @@ async fn check_github_app_installation() {
|
|||
|
||||
// Convert SSH URL to HTTPS and parse owner/repo
|
||||
let https_url = fabro_github::ssh_url_to_https(&remote_url);
|
||||
let (owner, repo) = match fabro_github::parse_github_owner_repo(&https_url) {
|
||||
Ok(pair) => pair,
|
||||
Err(_) => return, // Not a GitHub repo — skip silently
|
||||
let Ok((owner, repo)) = fabro_github::parse_github_owner_repo(&https_url) else {
|
||||
return; // Not a GitHub repo — skip silently
|
||||
};
|
||||
|
||||
// Load CLI config to get app_id and slug
|
||||
let cli_config = match load_cli_settings(None) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return,
|
||||
let Ok(cli_config) = load_cli_settings(None) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let app_id = match cli_config.app_id() {
|
||||
Some(id) => id.to_string(),
|
||||
None => {
|
||||
eprintln!(
|
||||
"\n Run {} to set up the GitHub App",
|
||||
console::Style::new()
|
||||
.cyan()
|
||||
.bold()
|
||||
.apply_to("fabro install")
|
||||
);
|
||||
return;
|
||||
}
|
||||
let app_id = if let Some(id) = cli_config.app_id() {
|
||||
id.to_string()
|
||||
} else {
|
||||
eprintln!(
|
||||
"\n Run {} to set up the GitHub App",
|
||||
console::Style::new()
|
||||
.cyan()
|
||||
.bold()
|
||||
.apply_to("fabro install")
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
let slug = cli_config.slug().map(String::from);
|
||||
|
||||
// Build GitHub App credentials
|
||||
let creds = match build_github_app_credentials(Some(&app_id)) {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
eprintln!(
|
||||
"\n Set {} to enable GitHub App integration",
|
||||
console::Style::new()
|
||||
.cyan()
|
||||
.bold()
|
||||
.apply_to("GITHUB_APP_PRIVATE_KEY")
|
||||
);
|
||||
return;
|
||||
}
|
||||
let Some(creds) = build_github_app_credentials(Some(&app_id)) else {
|
||||
eprintln!(
|
||||
"\n Set {} to enable GitHub App integration",
|
||||
console::Style::new()
|
||||
.cyan()
|
||||
.bold()
|
||||
.apply_to("GITHUB_APP_PRIVATE_KEY")
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
let jwt = match fabro_github::sign_app_jwt(&creds.app_id, &creds.private_key_pem) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
pub mod deinit;
|
||||
pub mod init;
|
||||
pub(crate) mod deinit;
|
||||
pub(crate) mod init;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::args::{RepoCommand, RepoNamespace};
|
||||
|
||||
pub async fn dispatch(ns: RepoNamespace) -> Result<()> {
|
||||
pub(crate) async fn dispatch(ns: RepoNamespace) -> Result<()> {
|
||||
match ns.command {
|
||||
RepoCommand::Init { skill } => {
|
||||
init::run_init().await?;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ const INTERVIEW_UNANSWERED_MESSAGE: &str =
|
|||
/// Attach to a running (or finished) workflow run, rendering progress live.
|
||||
///
|
||||
/// Returns exit code 0 for success/partial_success, 1 otherwise.
|
||||
pub async fn attach_run(
|
||||
pub(crate) async fn attach_run(
|
||||
run_dir: &Path,
|
||||
kill_on_detach: bool,
|
||||
styles: &'static Styles,
|
||||
|
|
@ -460,6 +460,7 @@ fn determine_exit_code(conclusion_path: &Path, status_record: Option<RunStatusRe
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn kill_engine(run_dir: &Path) {
|
||||
if let Some(pid) = read_launcher_pid(run_dir).map(|pid| pid as i32) {
|
||||
#[cfg(unix)]
|
||||
|
|
@ -470,6 +471,7 @@ fn kill_engine(run_dir: &Path) {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn process_alive(pid: u32) -> bool {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use fabro_util::terminal::Styles;
|
|||
|
||||
use crate::args::{GlobalArgs, RunArgs};
|
||||
|
||||
pub async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> Result<()> {
|
||||
pub(crate) async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> Result<()> {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let cli_defaults = load_cli_config(None)?;
|
||||
let cli_config: fabro_config::FabroSettings = cli_defaults.clone().try_into()?;
|
||||
|
|
@ -12,7 +12,7 @@ pub async fn execute(mut args: RunArgs, _globals: &GlobalArgs) -> Result<()> {
|
|||
|
||||
let quiet = args.detach;
|
||||
let prevent_idle_sleep = cli_config.prevent_idle_sleep_enabled();
|
||||
let (run_id, run_dir) = super::create::create_run(&args, cli_defaults, styles, quiet).await?;
|
||||
let (run_id, run_dir) = super::create::create_run(&args, cli_defaults, styles, quiet)?;
|
||||
|
||||
#[cfg(feature = "sleep_inhibitor")]
|
||||
let _sleep_guard = crate::sleep_inhibitor::guard(prevent_idle_sleep);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ enum CopyDirection {
|
|||
},
|
||||
}
|
||||
|
||||
pub async fn cp_command(args: CpArgs) -> Result<()> {
|
||||
pub(crate) async fn cp_command(args: CpArgs) -> Result<()> {
|
||||
let direction = parse_direction(&args.src, &args.dst)?;
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use super::output::{print_diagnostics_from_error, print_workflow_report_from_per
|
|||
/// Create a workflow run: allocate run directory, persist RunRecord, return (run_id, run_dir).
|
||||
///
|
||||
/// This does NOT execute the workflow — it only prepares the run directory.
|
||||
pub async fn create_run(
|
||||
pub(crate) fn create_run(
|
||||
args: &RunArgs,
|
||||
cli_defaults: FabroConfig,
|
||||
styles: &Styles,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use fabro_workflows::operations::{StartServices, resume as resume_run, start as
|
|||
use crate::cli_config;
|
||||
use crate::shared;
|
||||
|
||||
pub async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bool) -> Result<()> {
|
||||
pub(crate) async fn execute(run_dir: PathBuf, launcher_path: PathBuf, resume: bool) -> Result<()> {
|
||||
let cli_config = cli_config::load_cli_settings(None)?;
|
||||
let github_app = shared::github::build_github_app_credentials(cli_config.app_id());
|
||||
let git_author = GitAuthor::from_options(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use tracing::{debug, info};
|
|||
use crate::args::DiffArgs;
|
||||
use crate::cli_config::load_cli_settings;
|
||||
|
||||
pub async fn run(args: DiffArgs) -> Result<()> {
|
||||
pub(crate) async fn run(args: DiffArgs) -> Result<()> {
|
||||
info!(run_id = %args.run, "Showing diff");
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use git2::Repository;
|
|||
|
||||
use crate::args::ForkArgs;
|
||||
|
||||
pub fn run(args: &ForkArgs, styles: &Styles) -> Result<()> {
|
||||
pub(crate) fn run(args: &ForkArgs, styles: &Styles) -> Result<()> {
|
||||
let repo = Repository::discover(".").context("not in a git repository")?;
|
||||
let run_id = find_run_id_by_prefix(&repo, &args.run_id)?;
|
||||
let store = Store::new(repo);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ pub(crate) fn launcher_record_is_running(record: &LauncherRecord) -> bool {
|
|||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[allow(unsafe_code)]
|
||||
fn process_alive(pid: u32) -> bool {
|
||||
unsafe { libc::kill(pid as i32, 0) == 0 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use tracing::{debug, info};
|
|||
use crate::args::LogsArgs;
|
||||
use crate::cli_config::load_cli_settings;
|
||||
|
||||
pub fn run(args: LogsArgs, styles: &Styles) -> Result<()> {
|
||||
pub(crate) fn run(args: LogsArgs, styles: &Styles) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let run = resolve_run(&base, &args.run)?;
|
||||
|
|
@ -102,7 +102,7 @@ fn extract_timestamp(line: &str) -> Option<DateTime<Utc>> {
|
|||
ts_str.parse::<DateTime<Utc>>().ok()
|
||||
}
|
||||
|
||||
pub fn parse_since(s: &str) -> Result<DateTime<Utc>> {
|
||||
pub(crate) fn parse_since(s: &str) -> Result<DateTime<Utc>> {
|
||||
let s = s.trim();
|
||||
if s.is_empty() {
|
||||
bail!("empty --since value");
|
||||
|
|
@ -186,7 +186,7 @@ fn render_indented_markdown(styles: &Styles, text: &str, indent: &str) -> String
|
|||
.join("\n")
|
||||
}
|
||||
|
||||
pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
||||
pub(crate) fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
||||
let envelope: serde_json::Value = serde_json::from_str(line).ok()?;
|
||||
let event = envelope.get("event")?.as_str()?;
|
||||
let ts = format_timestamp(envelope.get("ts")?.as_str()?);
|
||||
|
|
@ -234,7 +234,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
|||
if let Some(usage) = envelope.get("usage") {
|
||||
let total = usage
|
||||
.get("total_tokens")
|
||||
.and_then(|value| value.as_i64())
|
||||
.and_then(serde_json::Value::as_i64)
|
||||
.unwrap_or(0);
|
||||
let pad = " ".repeat(ts.len() + 1);
|
||||
if total > 0 {
|
||||
|
|
@ -246,10 +246,13 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
|||
.apply_to(format!("Tokens: {}", format_tokens(total as u64)))
|
||||
));
|
||||
}
|
||||
if let Some(cache_read) = usage.get("cache_read_tokens").and_then(|v| v.as_i64()) {
|
||||
if let Some(cache_read) = usage
|
||||
.get("cache_read_tokens")
|
||||
.and_then(serde_json::Value::as_i64)
|
||||
{
|
||||
let cache_write = usage
|
||||
.get("cache_write_tokens")
|
||||
.and_then(|v| v.as_i64())
|
||||
.and_then(serde_json::Value::as_i64)
|
||||
.unwrap_or(0);
|
||||
lines.push(format!(
|
||||
"{}{}",
|
||||
|
|
@ -261,7 +264,10 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
|||
))
|
||||
));
|
||||
}
|
||||
if let Some(reasoning) = usage.get("reasoning_tokens").and_then(|v| v.as_i64()) {
|
||||
if let Some(reasoning) = usage
|
||||
.get("reasoning_tokens")
|
||||
.and_then(serde_json::Value::as_i64)
|
||||
{
|
||||
if reasoning > 0 {
|
||||
lines.push(format!(
|
||||
"{}{}",
|
||||
|
|
@ -321,14 +327,17 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
|||
let label = str_field(&envelope, "node_label").unwrap_or("?");
|
||||
let duration = format_duration_ms(envelope.get("duration_ms"));
|
||||
let cost = format_cost(envelope.get("cost"));
|
||||
let turns = envelope.get("turns").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
let turns = envelope
|
||||
.get("turns")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
let tools = envelope
|
||||
.get("tool_calls")
|
||||
.and_then(|v| v.as_u64())
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
let tokens = envelope
|
||||
.get("total_tokens")
|
||||
.and_then(|v| v.as_u64())
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
let stats = format!("({turns} turns, {tools} tools, {})", format_tokens(tokens));
|
||||
Some(format!(
|
||||
|
|
@ -386,7 +395,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
|||
let tool = str_field(&envelope, "tool_name").unwrap_or("?");
|
||||
let is_error = envelope
|
||||
.get("is_error")
|
||||
.and_then(|v| v.as_bool())
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let detail = tool_detail(&envelope);
|
||||
let display = match detail {
|
||||
|
|
@ -432,7 +441,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
|||
"SetupCompleted" => {
|
||||
let count = envelope
|
||||
.get("command_count")
|
||||
.and_then(|v| v.as_u64())
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
let duration = format_duration_ms(envelope.get("duration_ms"));
|
||||
Some(format!(
|
||||
|
|
@ -445,11 +454,11 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
|||
"Agent.CompactionCompleted" => {
|
||||
let original = envelope
|
||||
.get("original_turn_count")
|
||||
.and_then(|v| v.as_u64())
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
let preserved = envelope
|
||||
.get("preserved_turn_count")
|
||||
.and_then(|v| v.as_u64())
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
Some(format!(
|
||||
"{} {}",
|
||||
|
|
@ -462,7 +471,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
|||
"ParallelStarted" => {
|
||||
let count = envelope
|
||||
.get("branch_count")
|
||||
.and_then(|v| v.as_u64())
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
Some(format!(
|
||||
"{} {} Parallel {} branches",
|
||||
|
|
@ -502,7 +511,7 @@ pub fn format_event_pretty(line: &str, styles: &Styles) -> Option<String> {
|
|||
let url = str_field(&envelope, "pr_url").unwrap_or("?");
|
||||
let draft = envelope
|
||||
.get("draft")
|
||||
.and_then(|v| v.as_bool())
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let label = if draft { "Draft PR:" } else { "PR:" };
|
||||
Some(format!(
|
||||
|
|
@ -584,7 +593,7 @@ fn format_timestamp(ts: &str) -> String {
|
|||
}
|
||||
|
||||
fn format_duration_ms(value: Option<&serde_json::Value>) -> String {
|
||||
let ms = value.and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
let ms = value.and_then(serde_json::Value::as_u64).unwrap_or(0);
|
||||
if ms < 1000 {
|
||||
format!("{ms}ms")
|
||||
} else {
|
||||
|
|
@ -599,7 +608,7 @@ fn format_duration_ms(value: Option<&serde_json::Value>) -> String {
|
|||
}
|
||||
|
||||
fn format_cost(value: Option<&serde_json::Value>) -> String {
|
||||
let cost = value.and_then(|v| v.as_f64()).unwrap_or(0.0);
|
||||
let cost = value.and_then(serde_json::Value::as_f64).unwrap_or(0.0);
|
||||
if cost > 0.0 {
|
||||
format!("${cost:.2}")
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ pub(crate) mod ssh;
|
|||
pub(crate) mod start;
|
||||
pub(crate) mod wait;
|
||||
|
||||
pub async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<()> {
|
||||
pub(crate) async fn dispatch(cmd: RunCommands, globals: &GlobalArgs) -> Result<()> {
|
||||
match cmd {
|
||||
RunCommands::Run(args) => command::execute(args, globals).await,
|
||||
RunCommands::Create(args) => {
|
||||
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
|
||||
let cli_defaults = load_cli_config(None)?;
|
||||
let (run_id, _run_dir) = create::create_run(&args, cli_defaults, styles, true).await?;
|
||||
let (run_id, _run_dir) = create::create_run(&args, cli_defaults, styles, true)?;
|
||||
println!("{run_id}");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::args::PreviewArgs;
|
|||
use crate::cli_config::load_cli_settings;
|
||||
use crate::shared::validate_daytona_provider;
|
||||
|
||||
pub async fn run(args: PreviewArgs) -> Result<()> {
|
||||
pub(crate) async fn run(args: PreviewArgs) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let run_dir = resolve_run(&base, &args.run)?.path;
|
||||
|
|
@ -56,10 +56,12 @@ pub async fn run(args: PreviewArgs) -> Result<()> {
|
|||
}
|
||||
|
||||
fn format_standard_output(url: &str, token: &str) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut out = format!("URL: {url}\nToken: {token}\n");
|
||||
out.push_str(&format!(
|
||||
let _ = write!(
|
||||
out,
|
||||
"\ncurl -H \"x-daytona-preview-token: {token}\" \\\n -H \"X-Daytona-Skip-Preview-Warning: true\" \\\n {url}\n"
|
||||
));
|
||||
);
|
||||
out
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ use crate::cli_config::load_cli_settings;
|
|||
/// Looks up the run by ID prefix, validates a checkpoint exists, cleans stale
|
||||
/// artifacts from the previous execution, then spawns an engine subprocess
|
||||
/// (identical to `fabro run`'s create→start→attach flow).
|
||||
pub async fn resume_command(args: ResumeArgs, styles: &'static Styles) -> anyhow::Result<()> {
|
||||
pub(crate) async fn resume_command(
|
||||
args: ResumeArgs,
|
||||
styles: &'static Styles,
|
||||
) -> anyhow::Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let run_dir = find_run_by_prefix(&base, &args.run)?;
|
||||
|
|
@ -64,6 +67,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn process_alive(pid: u32) -> bool {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use git2::Repository;
|
|||
use crate::args::RewindArgs;
|
||||
use crate::shared::color_if;
|
||||
|
||||
pub fn run(args: &RewindArgs, styles: &Styles) -> Result<()> {
|
||||
pub(crate) fn run(args: &RewindArgs, styles: &Styles) -> Result<()> {
|
||||
let repo = Repository::discover(".").context("not in a git repository")?;
|
||||
let run_id = find_run_id_by_prefix(&repo, &args.run_id)?;
|
||||
let store = Store::new(repo);
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ enum ProgressRenderer {
|
|||
|
||||
// ── ProgressUI ──────────────────────────────────────────────────────────
|
||||
|
||||
pub struct ProgressUI {
|
||||
pub(crate) struct ProgressUI {
|
||||
renderer: ProgressRenderer,
|
||||
verbose: bool,
|
||||
active_stages: HashMap<String, ActiveStage>,
|
||||
|
|
@ -199,7 +199,7 @@ pub struct ProgressUI {
|
|||
|
||||
#[allow(dead_code)]
|
||||
impl ProgressUI {
|
||||
pub fn new(is_tty: bool, verbose: bool) -> Self {
|
||||
pub(crate) fn new(is_tty: bool, verbose: bool) -> Self {
|
||||
let renderer = if is_tty {
|
||||
ProgressRenderer::Tty(TtyRenderer {
|
||||
multi: MultiProgress::new(),
|
||||
|
|
@ -224,7 +224,7 @@ impl ProgressUI {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_working_directory(&mut self, dir: String) {
|
||||
pub(crate) fn set_working_directory(&mut self, dir: String) {
|
||||
self.working_directory = Some(dir);
|
||||
}
|
||||
|
||||
|
|
@ -266,7 +266,7 @@ impl ProgressUI {
|
|||
}
|
||||
|
||||
/// Register event handlers on the emitter.
|
||||
pub fn register(progress: &Arc<Mutex<Self>>, emitter: &EventEmitter) {
|
||||
pub(crate) fn register(progress: &Arc<Mutex<Self>>, emitter: &EventEmitter) {
|
||||
let p = Arc::clone(progress);
|
||||
emitter.on_event(move |event| {
|
||||
let mut ui = p.lock().expect("progress lock poisoned");
|
||||
|
|
@ -275,21 +275,21 @@ impl ProgressUI {
|
|||
}
|
||||
|
||||
/// Hide indicatif progress bars (for interview prompts in attach mode).
|
||||
pub fn hide_bars(&self) {
|
||||
pub(crate) fn hide_bars(&self) {
|
||||
if let ProgressRenderer::Tty(tty) = &self.renderer {
|
||||
tty.multi.set_draw_target(ProgressDrawTarget::hidden());
|
||||
}
|
||||
}
|
||||
|
||||
/// Show indicatif progress bars after an interview prompt.
|
||||
pub fn show_bars(&self) {
|
||||
pub(crate) fn show_bars(&self) {
|
||||
if let ProgressRenderer::Tty(tty) = &self.renderer {
|
||||
tty.multi.set_draw_target(ProgressDrawTarget::stderr());
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all active bars and release the terminal for normal stderr output.
|
||||
pub fn finish(&mut self) {
|
||||
pub(crate) fn finish(&mut self) {
|
||||
for (_id, stage) in self.active_stages.drain() {
|
||||
for entry in &stage.tool_calls {
|
||||
if entry.is_branch || self.verbose {
|
||||
|
|
@ -662,19 +662,22 @@ impl ProgressUI {
|
|||
|
||||
/// Parse a JSONL envelope line and dispatch to internal rendering methods.
|
||||
/// Used by the attach loop to render events from progress.jsonl.
|
||||
pub fn handle_json_line(&mut self, line: &str) {
|
||||
pub(crate) fn handle_json_line(&mut self, line: &str) {
|
||||
let envelope: serde_json::Value = match serde_json::from_str(line) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return,
|
||||
};
|
||||
let event_name = match envelope.get("event").and_then(|v| v.as_str()) {
|
||||
Some(name) => name,
|
||||
None => return,
|
||||
let Some(event_name) = envelope.get("event").and_then(|v| v.as_str()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let str_field = |key: &str| -> Option<&str> { envelope.get(key).and_then(|v| v.as_str()) };
|
||||
let u64_field =
|
||||
|key: &str| -> u64 { envelope.get(key).and_then(|v| v.as_u64()).unwrap_or(0) };
|
||||
let u64_field = |key: &str| -> u64 {
|
||||
envelope
|
||||
.get(key)
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(0)
|
||||
};
|
||||
|
||||
match event_name {
|
||||
"WorkflowRunStarted" => {
|
||||
|
|
@ -697,8 +700,8 @@ impl ProgressUI {
|
|||
.to_string();
|
||||
let duration_ms = u64_field("duration_ms");
|
||||
let name = str_field("name").map(String::from);
|
||||
let cpu = envelope.get("cpu").and_then(|v| v.as_f64());
|
||||
let memory = envelope.get("memory").and_then(|v| v.as_f64());
|
||||
let cpu = envelope.get("cpu").and_then(serde_json::Value::as_f64);
|
||||
let memory = envelope.get("memory").and_then(serde_json::Value::as_f64);
|
||||
let url = str_field("url").map(String::from);
|
||||
self.on_sandbox_event(&fabro_agent::SandboxEvent::Ready {
|
||||
provider,
|
||||
|
|
@ -741,7 +744,7 @@ impl ProgressUI {
|
|||
let cost_str = envelope
|
||||
.get("usage")
|
||||
.and_then(|u| u.get("cost"))
|
||||
.and_then(|c| c.as_f64())
|
||||
.and_then(serde_json::Value::as_f64)
|
||||
.map(|c| format!("{} ", format_cost(c)))
|
||||
.unwrap_or_default();
|
||||
|
||||
|
|
@ -752,8 +755,12 @@ impl ProgressUI {
|
|||
let total_tokens = envelope
|
||||
.get("usage")
|
||||
.map(|u| {
|
||||
u.get("input_tokens").and_then(|v| v.as_i64()).unwrap_or(0)
|
||||
+ u.get("output_tokens").and_then(|v| v.as_i64()).unwrap_or(0)
|
||||
u.get("input_tokens")
|
||||
.and_then(serde_json::Value::as_i64)
|
||||
.unwrap_or(0)
|
||||
+ u.get("output_tokens")
|
||||
.and_then(serde_json::Value::as_i64)
|
||||
.unwrap_or(0)
|
||||
})
|
||||
.unwrap_or(0);
|
||||
if turn_count > 0 || tool_call_count > 0 || total_tokens > 0 {
|
||||
|
|
@ -833,7 +840,7 @@ impl ProgressUI {
|
|||
let tool_call_id = str_field("tool_call_id").unwrap_or("?");
|
||||
let is_error = envelope
|
||||
.get("is_error")
|
||||
.and_then(|v| v.as_bool())
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
self.on_tool_call_completed(stage, tool_call_id, is_error);
|
||||
}
|
||||
|
|
@ -931,7 +938,7 @@ impl ProgressUI {
|
|||
let pr_url = str_field("pr_url").unwrap_or("?");
|
||||
let draft = envelope
|
||||
.get("draft")
|
||||
.and_then(|value| value.as_bool())
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
self.on_pull_request_created(pr_url, draft);
|
||||
}
|
||||
|
|
@ -1027,7 +1034,7 @@ impl ProgressUI {
|
|||
if let Some(cli_name) = str_field("cli_name") {
|
||||
let already_installed = envelope
|
||||
.get("already_installed")
|
||||
.and_then(|v| v.as_bool())
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let duration_ms = u64_field("duration_ms");
|
||||
self.on_cli_ensure_completed(cli_name, already_installed, duration_ms);
|
||||
|
|
@ -1213,7 +1220,7 @@ impl ProgressUI {
|
|||
|
||||
// ── Logs dir (called externally) ────────────────────────────────────
|
||||
|
||||
pub fn show_run_dir(&mut self, run_dir: &Path) {
|
||||
pub(crate) fn show_run_dir(&mut self, run_dir: &Path) {
|
||||
let path_str = tilde_path(run_dir);
|
||||
match &self.renderer {
|
||||
ProgressRenderer::Tty(tty) => {
|
||||
|
|
@ -1227,7 +1234,7 @@ impl ProgressUI {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn show_version(&mut self) {
|
||||
pub(crate) fn show_version(&mut self) {
|
||||
let version = FABRO_VERSION;
|
||||
match &self.renderer {
|
||||
ProgressRenderer::Tty(tty) => {
|
||||
|
|
@ -1241,7 +1248,7 @@ impl ProgressUI {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn show_run_id(&mut self, run_id: &str) {
|
||||
pub(crate) fn show_run_id(&mut self, run_id: &str) {
|
||||
match &self.renderer {
|
||||
ProgressRenderer::Tty(tty) => {
|
||||
let bar = tty.multi.add(ProgressBar::new_spinner());
|
||||
|
|
@ -1254,7 +1261,7 @@ impl ProgressUI {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn show_time(&mut self, time: &str) {
|
||||
pub(crate) fn show_time(&mut self, time: &str) {
|
||||
match &self.renderer {
|
||||
ProgressRenderer::Tty(tty) => {
|
||||
let bar = tty.multi.add(ProgressBar::new_spinner());
|
||||
|
|
@ -1267,7 +1274,7 @@ impl ProgressUI {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn show_worktree(&mut self, path: &Path) {
|
||||
pub(crate) fn show_worktree(&mut self, path: &Path) {
|
||||
let path_str = tilde_path(path);
|
||||
match &self.renderer {
|
||||
ProgressRenderer::Tty(tty) => {
|
||||
|
|
@ -1281,7 +1288,7 @@ impl ProgressUI {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn show_base_info(&mut self, branch: Option<&str>, sha: &str) {
|
||||
pub(crate) fn show_base_info(&mut self, branch: Option<&str>, sha: &str) {
|
||||
let short_sha = &sha[..sha.len().min(12)];
|
||||
let text = match branch {
|
||||
Some(b) => format!("Base: {b} ({short_sha})"),
|
||||
|
|
@ -1408,7 +1415,7 @@ impl ProgressUI {
|
|||
{
|
||||
let usage_percent = details
|
||||
.get("usage_percent")
|
||||
.and_then(|v| v.as_u64())
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
let yellow = Style::new().yellow();
|
||||
self.insert_info_line_for_stage(
|
||||
|
|
@ -1725,14 +1732,14 @@ impl ProgressUI {
|
|||
/// Wraps a `ConsoleInterviewer` so that progress bars are hidden during
|
||||
/// interactive prompts (avoids garbled output from concurrent writes).
|
||||
#[allow(dead_code)]
|
||||
pub struct ProgressAwareInterviewer {
|
||||
pub(crate) struct ProgressAwareInterviewer {
|
||||
inner: ConsoleInterviewer,
|
||||
progress: Arc<Mutex<ProgressUI>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl ProgressAwareInterviewer {
|
||||
pub fn new(inner: ConsoleInterviewer, progress: Arc<Mutex<ProgressUI>>) -> Self {
|
||||
pub(crate) fn new(inner: ConsoleInterviewer, progress: Arc<Mutex<ProgressUI>>) -> Self {
|
||||
Self { inner, progress }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::args::SshArgs;
|
|||
use crate::cli_config::load_cli_settings;
|
||||
use crate::shared::validate_daytona_provider;
|
||||
|
||||
pub async fn run(args: SshArgs) -> Result<()> {
|
||||
pub(crate) async fn run(args: SshArgs) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let run_dir = resolve_run(&base, &args.run)?.path;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ use super::launcher::{
|
|||
///
|
||||
/// The engine process reads `run.json` from the run directory and executes the
|
||||
/// workflow. Returns the child process handle (use `.id()` for the PID).
|
||||
pub fn start_run(run_dir: &Path, resume: bool) -> Result<std::process::Child> {
|
||||
#[allow(unsafe_code)]
|
||||
pub(crate) fn start_run(run_dir: &Path, resume: bool) -> Result<std::process::Child> {
|
||||
let record = RunRecord::load(run_dir)
|
||||
.map_err(|e| anyhow!("Cannot start run: failed to load run.json: {e}"))?;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use crate::args::WaitArgs;
|
|||
use crate::cli_config::load_cli_settings;
|
||||
use crate::shared::format_duration_ms;
|
||||
|
||||
pub fn run(args: WaitArgs, styles: &Styles) -> Result<()> {
|
||||
pub(crate) fn run(args: WaitArgs, styles: &Styles) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let run_info = resolve_run(&base, &args.run)?;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use crate::args::InspectArgs;
|
|||
use crate::cli_config::load_cli_settings;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InspectOutput {
|
||||
pub(crate) struct InspectOutput {
|
||||
pub run_id: String,
|
||||
pub run_dir: PathBuf,
|
||||
pub status: RunStatus,
|
||||
|
|
@ -25,17 +25,17 @@ pub struct InspectOutput {
|
|||
pub sandbox: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
pub fn run(args: &InspectArgs) -> Result<()> {
|
||||
pub(crate) fn run(args: &InspectArgs) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let run = resolve_run(&base, &args.run)?;
|
||||
let output = inspect_run_dir(&run.run_id, &run.path, run.status)?;
|
||||
let output = inspect_run_dir(&run.run_id, &run.path, run.status);
|
||||
let json = serde_json::to_string_pretty(&[output])?;
|
||||
println!("{json}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> Result<InspectOutput> {
|
||||
fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> InspectOutput {
|
||||
let run_record = RunRecord::load(run_dir)
|
||||
.ok()
|
||||
.and_then(|v| serde_json::to_value(v).ok());
|
||||
|
|
@ -52,7 +52,7 @@ fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> Result<In
|
|||
.ok()
|
||||
.and_then(|v| serde_json::to_value(v).ok());
|
||||
|
||||
Ok(InspectOutput {
|
||||
InspectOutput {
|
||||
run_id: run_id.to_string(),
|
||||
run_dir: run_dir.to_path_buf(),
|
||||
status,
|
||||
|
|
@ -61,5 +61,5 @@ fn inspect_run_dir(run_id: &str, run_dir: &Path, status: RunStatus) -> Result<In
|
|||
conclusion,
|
||||
checkpoint,
|
||||
sandbox,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use crate::shared::{color_if, format_duration_ms, tilde_path};
|
|||
|
||||
use super::short_run_id;
|
||||
|
||||
pub fn list_command(args: &RunsListArgs, styles: &Styles) -> Result<()> {
|
||||
pub(crate) fn list_command(args: &RunsListArgs, styles: &Styles) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
let runs = scan_runs(&base)?;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pub(crate) mod inspect;
|
|||
pub(crate) mod list;
|
||||
pub(crate) mod rm;
|
||||
|
||||
pub async fn dispatch(cmd: RunsCommands) -> Result<()> {
|
||||
pub(crate) async fn dispatch(cmd: RunsCommands) -> Result<()> {
|
||||
match cmd {
|
||||
RunsCommands::Ps(args) => {
|
||||
let styles = Styles::detect_stdout();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use crate::cli_config::load_cli_settings;
|
|||
|
||||
use super::short_run_id;
|
||||
|
||||
pub async fn remove_command(args: &RunsRemoveArgs) -> Result<()> {
|
||||
pub(crate) async fn remove_command(args: &RunsRemoveArgs) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
remove_from(args, &base).await
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use anyhow::{Result, bail};
|
|||
use crate::args::SecretGetArgs;
|
||||
use fabro_config::dotenv;
|
||||
|
||||
pub fn get_command(args: &SecretGetArgs) -> Result<()> {
|
||||
pub(super) fn get_command(args: &SecretGetArgs) -> Result<()> {
|
||||
let path = dotenv::env_file_path()?;
|
||||
match dotenv::get_env_value(&path, &args.key)? {
|
||||
Some(value) => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use anyhow::{Result, bail};
|
|||
use crate::args::SecretListArgs;
|
||||
use fabro_config::dotenv;
|
||||
|
||||
pub fn list_command(args: &SecretListArgs) -> Result<()> {
|
||||
pub(super) fn list_command(args: &SecretListArgs) -> Result<()> {
|
||||
let path = dotenv::env_file_path()?;
|
||||
let contents = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use anyhow::Result;
|
|||
|
||||
use crate::args::{SecretCommand, SecretNamespace};
|
||||
|
||||
pub fn dispatch(ns: SecretNamespace) -> Result<()> {
|
||||
pub(crate) fn dispatch(ns: SecretNamespace) -> Result<()> {
|
||||
match ns.command {
|
||||
SecretCommand::Get(args) => get::get_command(&args),
|
||||
SecretCommand::List(args) => list::list_command(&args),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use anyhow::{Result, bail};
|
|||
use crate::args::SecretRmArgs;
|
||||
use fabro_config::dotenv;
|
||||
|
||||
pub fn rm_command(args: &SecretRmArgs) -> Result<()> {
|
||||
pub(super) fn rm_command(args: &SecretRmArgs) -> Result<()> {
|
||||
let path = dotenv::env_file_path()?;
|
||||
let contents = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use anyhow::Result;
|
|||
use crate::args::SecretSetArgs;
|
||||
use fabro_config::dotenv;
|
||||
|
||||
pub fn set_command(args: &SecretSetArgs) -> Result<()> {
|
||||
pub(super) fn set_command(args: &SecretSetArgs) -> Result<()> {
|
||||
let path = dotenv::env_file_path()?;
|
||||
let existing = std::fs::read_to_string(&path).unwrap_or_default();
|
||||
let merged = dotenv::merge_env(&existing, &[(&args.key, &args.value)]);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const SKILL_FILES: &[(&str, &str)] = &[
|
|||
];
|
||||
|
||||
/// Install all skill files under `base_dir/fabro-create-workflow/`.
|
||||
pub fn install_skill_to(base_dir: &Path) -> Result<()> {
|
||||
pub(crate) fn install_skill_to(base_dir: &Path) -> Result<()> {
|
||||
let skill_dir = base_dir.join("fabro-create-workflow");
|
||||
|
||||
for (rel_path, content) in SKILL_FILES {
|
||||
|
|
@ -37,7 +37,7 @@ pub fn install_skill_to(base_dir: &Path) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_skill_install(args: &SkillInstallArgs) -> Result<()> {
|
||||
pub(super) fn run_skill_install(args: &SkillInstallArgs) -> Result<()> {
|
||||
let base_dir = resolve_base_dir(&args.scope, &args.dir)?;
|
||||
let skill_dir = base_dir.join("fabro-create-workflow");
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ use anyhow::Result;
|
|||
|
||||
use crate::args::{SkillCommand, SkillNamespace};
|
||||
|
||||
pub use install::install_skill_to;
|
||||
pub(crate) use install::install_skill_to;
|
||||
|
||||
pub fn dispatch(ns: SkillNamespace) -> Result<()> {
|
||||
pub(crate) fn dispatch(ns: SkillNamespace) -> Result<()> {
|
||||
match ns.command {
|
||||
SkillCommand::Install(args) => install::run_skill_install(&args),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use crate::args::DfArgs;
|
|||
use crate::cli_config::load_cli_settings;
|
||||
use crate::shared::format_size;
|
||||
|
||||
pub fn df_command(args: &DfArgs) -> Result<()> {
|
||||
pub(super) fn df_command(args: &DfArgs) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let data_dir = cli_config.storage_dir();
|
||||
let runs_base_dir = runs_base(&data_dir);
|
||||
|
|
@ -81,7 +81,12 @@ fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) -
|
|||
continue;
|
||||
}
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
if name.ends_with(".db") || name.ends_with(".db-wal") || name.ends_with(".db-shm") {
|
||||
if std::path::Path::new(&name)
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("db"))
|
||||
|| name.ends_with(".db-wal")
|
||||
|| name.ends_with(".db-shm")
|
||||
{
|
||||
if let Ok(meta) = path.metadata() {
|
||||
db_count += 1;
|
||||
total_db_size += meta.len();
|
||||
|
|
@ -214,9 +219,9 @@ fn truncate_str(s: &str, max_len: usize) -> String {
|
|||
fn dir_size(path: &Path) -> u64 {
|
||||
walkdir::WalkDir::new(path)
|
||||
.into_iter()
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter_map(|entry| entry.metadata().ok())
|
||||
.filter(|metadata| metadata.is_file())
|
||||
.filter(std::fs::Metadata::is_file)
|
||||
.map(|metadata| metadata.len())
|
||||
.sum()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::args::{SystemCommand, SystemNamespace};
|
|||
|
||||
pub(crate) use prune::parse_duration;
|
||||
|
||||
pub fn dispatch(ns: SystemNamespace) -> Result<()> {
|
||||
pub(crate) fn dispatch(ns: SystemNamespace) -> Result<()> {
|
||||
match ns.command {
|
||||
SystemCommand::Prune(args) => prune::prune_command(&args),
|
||||
SystemCommand::Df(args) => df::df_command(&args),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use crate::args::RunsPruneArgs;
|
|||
use crate::cli_config::load_cli_settings;
|
||||
use crate::shared::format_size;
|
||||
|
||||
pub fn prune_command(args: &RunsPruneArgs) -> Result<()> {
|
||||
pub(super) fn prune_command(args: &RunsPruneArgs) -> Result<()> {
|
||||
let cli_config = load_cli_settings(None)?;
|
||||
let base = runs_base(&cli_config.storage_dir());
|
||||
prune_from(args, &base)
|
||||
|
|
@ -111,9 +111,9 @@ fn parse_label_filters(label_args: &[String]) -> Vec<(String, String)> {
|
|||
fn dir_size(path: &Path) -> u64 {
|
||||
walkdir::WalkDir::new(path)
|
||||
.into_iter()
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter_map(|entry| entry.metadata().ok())
|
||||
.filter(|metadata| metadata.is_file())
|
||||
.filter(std::fs::Metadata::is_file)
|
||||
.map(|metadata| metadata.len())
|
||||
.sum()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ fn http_client() -> Result<reqwest::Client> {
|
|||
impl Backend {
|
||||
async fn fetch_latest_release_tag(&self) -> Result<String> {
|
||||
match self {
|
||||
Backend::Gh => {
|
||||
Self::Gh => {
|
||||
let output = TokioCommand::new("gh")
|
||||
.args([
|
||||
"release",
|
||||
|
|
@ -52,7 +52,7 @@ impl Backend {
|
|||
}
|
||||
Ok(String::from_utf8(output.stdout)?.trim().to_string())
|
||||
}
|
||||
Backend::Http(client) => {
|
||||
Self::Http(client) => {
|
||||
let url = format!("https://api.github.com/repos/{GITHUB_REPO}/releases/latest");
|
||||
let resp = client
|
||||
.get(&url)
|
||||
|
|
@ -77,7 +77,7 @@ impl Backend {
|
|||
async fn download_release(&self, tag: &str, asset: &str, dest_dir: &Path) -> Result<PathBuf> {
|
||||
let dest = dest_dir.join(asset);
|
||||
match self {
|
||||
Backend::Gh => {
|
||||
Self::Gh => {
|
||||
let status = TokioCommand::new("gh")
|
||||
.args([
|
||||
"release",
|
||||
|
|
@ -98,7 +98,7 @@ impl Backend {
|
|||
bail!("gh release download failed with exit code {status}");
|
||||
}
|
||||
}
|
||||
Backend::Http(client) => {
|
||||
Self::Http(client) => {
|
||||
let url =
|
||||
format!("https://github.com/{GITHUB_REPO}/releases/download/{tag}/{asset}");
|
||||
let resp = client
|
||||
|
|
@ -223,7 +223,7 @@ impl UpgradeCheckState {
|
|||
|
||||
// ── Main upgrade command ───────────────────────────────────────────────────
|
||||
|
||||
pub async fn run_upgrade(args: UpgradeArgs) -> Result<()> {
|
||||
pub(crate) async fn run_upgrade(args: UpgradeArgs) -> Result<()> {
|
||||
let backend = select_backend().await;
|
||||
|
||||
let current =
|
||||
|
|
@ -346,7 +346,7 @@ pub async fn run_upgrade(args: UpgradeArgs) -> Result<()> {
|
|||
/// Spawn a background task that checks for a newer version and prints a notice
|
||||
/// to stderr after the main command completes. Returns a handle that should be
|
||||
/// awaited at the end of `main_inner`.
|
||||
pub fn spawn_upgrade_check(
|
||||
pub(crate) fn spawn_upgrade_check(
|
||||
no_upgrade_check: bool,
|
||||
upgrade_check_enabled: bool,
|
||||
) -> Option<JoinHandle<()>> {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use fabro_workflows::operations::{ValidateInput, WorkflowInput, validate};
|
|||
use crate::args::ValidateArgs;
|
||||
use crate::shared::{print_diagnostics, relative_path};
|
||||
|
||||
pub fn run(args: &ValidateArgs, styles: &Styles) -> anyhow::Result<()> {
|
||||
pub(crate) fn run(args: &ValidateArgs, styles: &Styles) -> anyhow::Result<()> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
let cli_defaults = load_cli_config(None)?;
|
||||
let settings = resolve_settings(ResolveSettingsInput {
|
||||
|
|
|
|||
|
|
@ -7,15 +7,14 @@ use fabro_config::project::{discover_project_config, resolve_fabro_root};
|
|||
use crate::args::WorkflowCreateArgs;
|
||||
use crate::shared::relative_path;
|
||||
|
||||
pub fn create_command(args: &WorkflowCreateArgs) -> Result<()> {
|
||||
pub(super) fn create_command(args: &WorkflowCreateArgs) -> Result<()> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
|
||||
let (config_path, config) = match discover_project_config(&cwd)? {
|
||||
Some(found) => found,
|
||||
None => bail!(
|
||||
let Some((config_path, config)) = discover_project_config(&cwd)? else {
|
||||
bail!(
|
||||
"No fabro.toml found in {cwd} or any parent directory",
|
||||
cwd = cwd.display()
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
let fabro_root = resolve_fabro_root(&config_path, &config);
|
||||
|
|
|
|||
|
|
@ -11,16 +11,15 @@ use crate::shared::relative_path;
|
|||
|
||||
const GOAL_MAX_LEN: usize = 60;
|
||||
|
||||
pub fn list_command(_args: &WorkflowListArgs) -> Result<()> {
|
||||
pub(super) fn list_command(_args: &WorkflowListArgs) -> Result<()> {
|
||||
let styles = Styles::detect_stderr();
|
||||
let cwd = std::env::current_dir()?;
|
||||
|
||||
let (config_path, config) = match discover_project_config(&cwd)? {
|
||||
Some(found) => found,
|
||||
None => bail!(
|
||||
let Some((config_path, config)) = discover_project_config(&cwd)? else {
|
||||
bail!(
|
||||
"No fabro.toml found in {cwd} or any parent directory",
|
||||
cwd = cwd.display()
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
let fabro_root = resolve_fabro_root(&config_path, &config);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use anyhow::Result;
|
|||
|
||||
use crate::args::{WorkflowCommand, WorkflowNamespace};
|
||||
|
||||
pub fn dispatch(ns: WorkflowNamespace) -> Result<()> {
|
||||
pub(crate) fn dispatch(ns: WorkflowNamespace) -> Result<()> {
|
||||
match ns.command {
|
||||
WorkflowCommand::List(args) => list::list_command(&args),
|
||||
WorkflowCommand::Create(args) => create::create_command(&args),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ use fabro_util::run_log;
|
|||
use tracing_appender::rolling;
|
||||
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
pub fn init_tracing(debug: bool, config_log_level: Option<&str>, log_prefix: &str) -> Result<()> {
|
||||
pub(crate) fn init_tracing(
|
||||
debug: bool,
|
||||
config_log_level: Option<&str>,
|
||||
log_prefix: &str,
|
||||
) -> Result<()> {
|
||||
let default_level = if debug {
|
||||
"debug"
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::print_stdout, clippy::print_stderr, clippy::exit)]
|
||||
|
||||
mod args;
|
||||
mod cli_config;
|
||||
mod commands;
|
||||
|
|
@ -220,7 +222,7 @@ async fn main_inner() -> (String, Result<()>) {
|
|||
result?;
|
||||
}
|
||||
Commands::SendPanic { path } => {
|
||||
let result = tel_panic::capture(&path).await;
|
||||
let result = tel_panic::capture(&path);
|
||||
let _ = std::fs::remove_file(&path);
|
||||
result?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ use cli_table::Color;
|
|||
use fabro_util::terminal::Styles;
|
||||
use fabro_validate::{Diagnostic, Severity};
|
||||
|
||||
pub fn read_workflow_file(path: &Path) -> anyhow::Result<String> {
|
||||
pub(crate) fn read_workflow_file(path: &Path) -> anyhow::Result<String> {
|
||||
std::fs::read_to_string(path)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to read {}: {e}", path.display()))
|
||||
}
|
||||
|
||||
pub fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) {
|
||||
pub(crate) fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) {
|
||||
for d in diagnostics {
|
||||
let location = match (&d.node_id, &d.edge) {
|
||||
(Some(node), _) => format!(" [node: {node}]"),
|
||||
|
|
@ -41,7 +41,7 @@ pub fn print_diagnostics(diagnostics: &[Diagnostic], styles: &Styles) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn relative_path(path: &Path) -> String {
|
||||
pub(crate) fn relative_path(path: &Path) -> String {
|
||||
if let Ok(cwd) = std::env::current_dir() {
|
||||
if let Ok(rel) = path.strip_prefix(&cwd) {
|
||||
return rel.display().to_string();
|
||||
|
|
@ -50,7 +50,7 @@ pub fn relative_path(path: &Path) -> String {
|
|||
tilde_path(path)
|
||||
}
|
||||
|
||||
pub fn format_tokens_human(tokens: i64) -> String {
|
||||
pub(crate) fn format_tokens_human(tokens: i64) -> String {
|
||||
if tokens >= 1_000_000 {
|
||||
format!("{:.1}m", tokens as f64 / 1_000_000.0)
|
||||
} else if tokens >= 1000 {
|
||||
|
|
@ -60,7 +60,7 @@ pub fn format_tokens_human(tokens: i64) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tilde_path(path: &Path) -> String {
|
||||
pub(crate) fn tilde_path(path: &Path) -> String {
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
if let Ok(suffix) = path.strip_prefix(&home) {
|
||||
return format!("~/{}", suffix.display());
|
||||
|
|
@ -69,18 +69,18 @@ pub fn tilde_path(path: &Path) -> String {
|
|||
path.display().to_string()
|
||||
}
|
||||
|
||||
pub fn color_if(use_color: bool, color: Color) -> Option<Color> {
|
||||
pub(crate) fn color_if(use_color: bool, color: Color) -> Option<Color> {
|
||||
if use_color { Some(color) } else { None }
|
||||
}
|
||||
|
||||
pub fn split_run_path(s: &str) -> Option<(&str, &str)> {
|
||||
pub(crate) fn split_run_path(s: &str) -> Option<(&str, &str)> {
|
||||
if s.starts_with('/') || s.starts_with("./") || s.starts_with("../") {
|
||||
return None;
|
||||
}
|
||||
s.split_once(':')
|
||||
}
|
||||
|
||||
pub fn validate_daytona_provider(
|
||||
pub(crate) fn validate_daytona_provider(
|
||||
record: &fabro_sandbox::SandboxRecord,
|
||||
feature: &str,
|
||||
) -> Result<()> {
|
||||
|
|
@ -93,7 +93,7 @@ pub fn validate_daytona_provider(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn format_duration_ms(ms: u64) -> String {
|
||||
pub(crate) fn format_duration_ms(ms: u64) -> String {
|
||||
let duration = Duration::from_millis(ms);
|
||||
let secs = duration.as_secs();
|
||||
if secs >= 60 {
|
||||
|
|
@ -105,7 +105,7 @@ pub fn format_duration_ms(ms: u64) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn format_size(bytes: u64) -> String {
|
||||
pub(crate) fn format_size(bytes: u64) -> String {
|
||||
const KB: u64 = 1024;
|
||||
const MB: u64 = 1024 * KB;
|
||||
const GB: u64 = 1024 * MB;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use std::fmt::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, bail};
|
||||
|
|
@ -265,7 +266,7 @@ fn resolve_workflow_arg_impl(
|
|||
available.join(", ")
|
||||
);
|
||||
if let Some(suggestion) = find_closest_match(&name, &available) {
|
||||
msg.push_str(&format!("\n\nDid you mean '{suggestion}'?"));
|
||||
let _ = write!(msg, "\n\nDid you mean '{suggestion}'?");
|
||||
}
|
||||
bail!("{msg}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ impl<G: Graph + 'static> Executor<G> {
|
|||
.await?;
|
||||
|
||||
// Determine next step
|
||||
let last_outcome = state.node_outcomes.get(node.id()).unwrap();
|
||||
let last_outcome = &state.node_outcomes[node.id()];
|
||||
let next = self
|
||||
.resolve_next_step(&node, last_outcome, &state, graph)
|
||||
.await?;
|
||||
|
|
@ -369,40 +369,37 @@ impl<G: Graph + 'static> Executor<G> {
|
|||
}
|
||||
|
||||
// Normal edge selection
|
||||
match graph.select_edge(node, outcome, &state.context) {
|
||||
Some(selection) => {
|
||||
let target = selection.edge.target().to_string();
|
||||
let is_restart = selection.edge.is_loop_restart();
|
||||
if let Some(selection) = graph.select_edge(node, outcome, &state.context) {
|
||||
let target = selection.edge.target().to_string();
|
||||
let is_restart = selection.edge.is_loop_restart();
|
||||
|
||||
let ctx = EdgeContext {
|
||||
from: node.id(),
|
||||
to: &target,
|
||||
edge: Some(selection.edge.clone()),
|
||||
is_jump: false,
|
||||
outcome,
|
||||
reason: selection.reason,
|
||||
};
|
||||
match self.lifecycle.on_edge_selected(&ctx, state).await? {
|
||||
EdgeDecision::Continue => {
|
||||
if is_restart {
|
||||
Ok(NextStep::LoopRestart(target))
|
||||
} else {
|
||||
Ok(NextStep::Edge(target))
|
||||
}
|
||||
}
|
||||
EdgeDecision::Override(new_target) => Ok(NextStep::Edge(new_target)),
|
||||
EdgeDecision::Block(msg) => Err(CoreError::blocked(msg)),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// No edge found
|
||||
if outcome.status == StageStatus::Fail {
|
||||
if let Some(retry_target) = graph.get_retry_target(node.id()) {
|
||||
return Ok(NextStep::Edge(retry_target));
|
||||
let ctx = EdgeContext {
|
||||
from: node.id(),
|
||||
to: &target,
|
||||
edge: Some(selection.edge.clone()),
|
||||
is_jump: false,
|
||||
outcome,
|
||||
reason: selection.reason,
|
||||
};
|
||||
match self.lifecycle.on_edge_selected(&ctx, state).await? {
|
||||
EdgeDecision::Continue => {
|
||||
if is_restart {
|
||||
Ok(NextStep::LoopRestart(target))
|
||||
} else {
|
||||
Ok(NextStep::Edge(target))
|
||||
}
|
||||
}
|
||||
Ok(NextStep::End)
|
||||
EdgeDecision::Override(new_target) => Ok(NextStep::Edge(new_target)),
|
||||
EdgeDecision::Block(msg) => Err(CoreError::blocked(msg)),
|
||||
}
|
||||
} else {
|
||||
// No edge found
|
||||
if outcome.status == StageStatus::Fail {
|
||||
if let Some(retry_target) = graph.get_retry_target(node.id()) {
|
||||
return Ok(NextStep::Edge(retry_target));
|
||||
}
|
||||
}
|
||||
Ok(NextStep::End)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ impl StallWatchdog {
|
|||
let handle = tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = sleep(timeout) => {
|
||||
() = sleep(timeout) => {
|
||||
if shutdown.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -69,12 +69,11 @@ impl StallWatchdog {
|
|||
cancel_token.store(true, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
_ = activity.notified() => {
|
||||
() = activity.notified() => {
|
||||
if shutdown.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
// Activity reported, restart the timer
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
/// Extracted configuration from a Docker Compose service.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ComposeServiceConfig {
|
||||
pub(crate) struct ComposeServiceConfig {
|
||||
pub image: Option<String>,
|
||||
pub build: Option<ComposeBuild>,
|
||||
pub ports: Vec<u16>,
|
||||
|
|
@ -13,13 +13,13 @@ pub struct ComposeServiceConfig {
|
|||
|
||||
/// Build configuration from a Docker Compose service.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ComposeBuild {
|
||||
pub(crate) struct ComposeBuild {
|
||||
pub context: String,
|
||||
pub dockerfile: Option<String>,
|
||||
}
|
||||
|
||||
/// Parse a Docker Compose file and extract config for the named service.
|
||||
pub fn parse_compose(
|
||||
pub(crate) fn parse_compose(
|
||||
compose_path: &Path,
|
||||
service_name: &str,
|
||||
) -> Result<ComposeServiceConfig, String> {
|
||||
|
|
@ -155,7 +155,7 @@ fn parse_environment(service: &serde_yaml::Value) -> HashMap<String, String> {
|
|||
/// Parse multiple Docker Compose files and merge config for the named service.
|
||||
/// Later files override earlier files for image/build/user; ports accumulate (deduped);
|
||||
/// environment keys from later files override earlier ones.
|
||||
pub fn parse_compose_multi(
|
||||
pub(crate) fn parse_compose_multi(
|
||||
compose_paths: &[PathBuf],
|
||||
service_name: &str,
|
||||
) -> Result<ComposeServiceConfig, String> {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
|||
use crate::features::FeatureLayer;
|
||||
|
||||
/// Generate a combined Dockerfile from base + features + env + user.
|
||||
pub fn generate(
|
||||
pub(crate) fn generate(
|
||||
base_dockerfile: &str,
|
||||
feature_layers: &[FeatureLayer],
|
||||
container_env: &HashMap<String, String>,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::fmt::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use tokio::fs;
|
||||
|
|
@ -10,7 +11,7 @@ use crate::types::{FeatureMetadata, LifecycleCommand};
|
|||
|
||||
/// A resolved feature layer ready to be inserted into a Dockerfile.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FeatureLayer {
|
||||
pub(crate) struct FeatureLayer {
|
||||
/// Feature identifier (e.g. "ghcr.io/devcontainers/features/node:1")
|
||||
pub id: String,
|
||||
/// Directory name for COPY
|
||||
|
|
@ -21,7 +22,7 @@ pub struct FeatureLayer {
|
|||
|
||||
/// All resolved feature data: layers, environment, and lifecycle hooks.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ResolvedFeatures {
|
||||
pub(crate) struct ResolvedFeatures {
|
||||
pub layers: Vec<FeatureLayer>,
|
||||
pub container_env: HashMap<String, String>,
|
||||
pub on_create_commands: Vec<LifecycleCommand>,
|
||||
|
|
@ -133,7 +134,10 @@ async fn find_tgz(dir: &Path) -> Option<String> {
|
|||
let mut entries = fs::read_dir(dir).await.ok()?;
|
||||
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
if name.ends_with(".tgz") {
|
||||
if Path::new(name)
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("tgz"))
|
||||
{
|
||||
return Some(name.to_string());
|
||||
}
|
||||
}
|
||||
|
|
@ -332,7 +336,10 @@ fn topo_sort(
|
|||
return Vec::new();
|
||||
}
|
||||
|
||||
let id_set: HashSet<&str> = feature_ids.iter().map(|s| s.as_str()).collect();
|
||||
let id_set: HashSet<&str> = feature_ids
|
||||
.iter()
|
||||
.map(std::string::String::as_str)
|
||||
.collect();
|
||||
|
||||
// Build adjacency list and in-degree count.
|
||||
// An edge from A -> B means "A must be installed before B".
|
||||
|
|
@ -350,7 +357,11 @@ fn topo_sort(
|
|||
for id in feature_ids {
|
||||
if let Some(meta) = metadata_map.get(id) {
|
||||
// Collect dependency refs from both installsAfter and dependsOn
|
||||
let mut dep_refs: Vec<&str> = meta.installs_after.iter().map(|s| s.as_str()).collect();
|
||||
let mut dep_refs: Vec<&str> = meta
|
||||
.installs_after
|
||||
.iter()
|
||||
.map(std::string::String::as_str)
|
||||
.collect();
|
||||
for dep_id in meta.depends_on.keys() {
|
||||
dep_refs.push(dep_id.as_str());
|
||||
}
|
||||
|
|
@ -511,12 +522,14 @@ fn generate_layer(
|
|||
}
|
||||
|
||||
let mut snippet = format!("# Feature: {feature_id}\n");
|
||||
snippet.push_str(&format!(
|
||||
"COPY {dir_name}/ /tmp/devcontainer-features/{dir_name}/\n"
|
||||
));
|
||||
snippet.push_str(&format!(
|
||||
"RUN cd /tmp/devcontainer-features/{dir_name} && \\\n"
|
||||
));
|
||||
let _ = writeln!(
|
||||
snippet,
|
||||
"COPY {dir_name}/ /tmp/devcontainer-features/{dir_name}/"
|
||||
);
|
||||
let _ = writeln!(
|
||||
snippet,
|
||||
"RUN cd /tmp/devcontainer-features/{dir_name} && \\"
|
||||
);
|
||||
for line in &env_lines {
|
||||
snippet.push_str(line);
|
||||
snippet.push('\n');
|
||||
|
|
@ -528,7 +541,7 @@ fn generate_layer(
|
|||
}
|
||||
|
||||
/// Fetch, order, and resolve features into Dockerfile layers.
|
||||
pub async fn resolve_features(
|
||||
pub(crate) async fn resolve_features(
|
||||
features: &HashMap<String, serde_json::Value>,
|
||||
devcontainer_dir: &Path,
|
||||
remote_user: Option<&str>,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/// Strip JSONC comments and trailing commas, producing valid JSON.
|
||||
pub fn strip_jsonc(input: &str) -> String {
|
||||
pub(crate) fn strip_jsonc(input: &str) -> String {
|
||||
let mut out = String::with_capacity(input.len());
|
||||
let bytes = input.as_bytes();
|
||||
let len = bytes.len();
|
||||
|
|
|
|||
|
|
@ -252,20 +252,26 @@ impl DevcontainerResolver {
|
|||
build_args: HashMap::new(),
|
||||
build_target: None,
|
||||
initialize_commands: Self::collect_commands(
|
||||
&devcontainer.initialize_command,
|
||||
devcontainer.initialize_command.as_ref(),
|
||||
&vars,
|
||||
),
|
||||
on_create_commands: Self::collect_commands(
|
||||
devcontainer.on_create_command.as_ref(),
|
||||
&vars,
|
||||
),
|
||||
on_create_commands: Self::collect_commands(&devcontainer.on_create_command, &vars),
|
||||
post_create_commands: Self::collect_commands(
|
||||
&devcontainer.post_create_command,
|
||||
devcontainer.post_create_command.as_ref(),
|
||||
&vars,
|
||||
),
|
||||
post_start_commands: Self::collect_commands(
|
||||
&devcontainer.post_start_command,
|
||||
devcontainer.post_start_command.as_ref(),
|
||||
&vars,
|
||||
),
|
||||
environment,
|
||||
container_env: Self::collect_container_env(&devcontainer.container_env, &vars),
|
||||
container_env: Self::collect_container_env(
|
||||
devcontainer.container_env.as_ref(),
|
||||
&vars,
|
||||
),
|
||||
remote_user: devcontainer.remote_user.clone().or(compose_config.user),
|
||||
workspace_folder,
|
||||
forwarded_ports: {
|
||||
|
|
@ -362,11 +368,12 @@ impl DevcontainerResolver {
|
|||
let forwarded_ports = Self::parse_forward_ports(&devcontainer.forward_ports);
|
||||
|
||||
// Collect devcontainer.json lifecycle commands, then append feature lifecycle commands
|
||||
let mut on_create_commands = Self::collect_commands(&devcontainer.on_create_command, &vars);
|
||||
let mut on_create_commands =
|
||||
Self::collect_commands(devcontainer.on_create_command.as_ref(), &vars);
|
||||
let mut post_create_commands =
|
||||
Self::collect_commands(&devcontainer.post_create_command, &vars);
|
||||
Self::collect_commands(devcontainer.post_create_command.as_ref(), &vars);
|
||||
let mut post_start_commands =
|
||||
Self::collect_commands(&devcontainer.post_start_command, &vars);
|
||||
Self::collect_commands(devcontainer.post_start_command.as_ref(), &vars);
|
||||
|
||||
for cmd in &resolved_features.on_create_commands {
|
||||
on_create_commands.push(Self::convert_lifecycle_command(cmd));
|
||||
|
|
@ -383,7 +390,10 @@ impl DevcontainerResolver {
|
|||
build_context,
|
||||
build_args,
|
||||
build_target,
|
||||
initialize_commands: Self::collect_commands(&devcontainer.initialize_command, &vars),
|
||||
initialize_commands: Self::collect_commands(
|
||||
devcontainer.initialize_command.as_ref(),
|
||||
&vars,
|
||||
),
|
||||
on_create_commands,
|
||||
post_create_commands,
|
||||
post_start_commands,
|
||||
|
|
@ -438,7 +448,7 @@ impl DevcontainerResolver {
|
|||
path: devcontainer_dir.clone(),
|
||||
source,
|
||||
})?
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter(|entry| entry.path().is_dir())
|
||||
.map(|entry| entry.path())
|
||||
.filter(|dir| dir.join("devcontainer.json").exists())
|
||||
|
|
@ -487,7 +497,7 @@ impl DevcontainerResolver {
|
|||
}
|
||||
|
||||
fn collect_container_env(
|
||||
env: &Option<HashMap<String, String>>,
|
||||
env: Option<&HashMap<String, String>>,
|
||||
vars: &variables::VariableContext,
|
||||
) -> HashMap<String, String> {
|
||||
match env {
|
||||
|
|
@ -508,7 +518,7 @@ impl DevcontainerResolver {
|
|||
}
|
||||
|
||||
fn collect_commands(
|
||||
cmd: &Option<types::LifecycleCommand>,
|
||||
cmd: Option<&types::LifecycleCommand>,
|
||||
vars: &variables::VariableContext,
|
||||
) -> Vec<Command> {
|
||||
match cmd {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ pub enum LifecycleCommand {
|
|||
/// Metadata from a devcontainer-feature.json file.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FeatureMetadata {
|
||||
pub(crate) struct FeatureMetadata {
|
||||
pub id: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub version: Option<String>,
|
||||
|
|
@ -135,7 +135,7 @@ pub struct FeatureMetadata {
|
|||
|
||||
/// A single option for a devcontainer feature.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct FeatureOption {
|
||||
pub(crate) struct FeatureOption {
|
||||
#[serde(rename = "type")]
|
||||
pub option_type: Option<String>,
|
||||
pub default: Option<serde_json::Value>,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use fabro_util::env::Env;
|
||||
|
||||
/// Context for variable substitution.
|
||||
pub struct VariableContext<'a> {
|
||||
pub(crate) struct VariableContext<'a> {
|
||||
pub local_workspace_folder: String,
|
||||
pub local_workspace_folder_basename: String,
|
||||
pub container_workspace_folder: String,
|
||||
|
|
@ -9,7 +9,7 @@ pub struct VariableContext<'a> {
|
|||
}
|
||||
|
||||
/// Replace devcontainer variables in a string value.
|
||||
pub fn substitute(input: &str, ctx: &VariableContext) -> String {
|
||||
pub(crate) fn substitute(input: &str, ctx: &VariableContext) -> String {
|
||||
let mut result = String::with_capacity(input.len());
|
||||
let mut rest = input;
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ pub fn substitute(input: &str, ctx: &VariableContext) -> String {
|
|||
Some(val) => result.push_str(&val),
|
||||
None => {
|
||||
// Unknown variable — leave as-is
|
||||
result.push_str(&rest[start..start + 2 + close + 1]);
|
||||
result.push_str(&rest[start..=(start + 2 + close)]);
|
||||
}
|
||||
}
|
||||
rest = &after_open[close + 1..];
|
||||
|
|
|
|||
|
|
@ -115,9 +115,8 @@ impl<'a> BranchStore<'a> {
|
|||
|
||||
/// Read a single file from the latest tree. Returns `None` if branch or path doesn't exist.
|
||||
pub fn read_entry(&self, path: &str) -> Result<Option<Vec<u8>>> {
|
||||
let commit_oid = match self.objects.resolve_ref(&self.branch)? {
|
||||
Some(oid) => oid,
|
||||
None => return Ok(None),
|
||||
let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let commit = self.objects.repo().find_commit(commit_oid)?;
|
||||
let tree = commit.tree()?;
|
||||
|
|
@ -134,9 +133,8 @@ impl<'a> BranchStore<'a> {
|
|||
|
||||
/// Read multiple paths. Missing paths are omitted from the result.
|
||||
pub fn read_entries<'b>(&self, paths: &[&'b str]) -> Result<Vec<(&'b str, Vec<u8>)>> {
|
||||
let commit_oid = match self.objects.resolve_ref(&self.branch)? {
|
||||
Some(oid) => oid,
|
||||
None => return Ok(vec![]),
|
||||
let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else {
|
||||
return Ok(vec![]);
|
||||
};
|
||||
let commit = self.objects.repo().find_commit(commit_oid)?;
|
||||
let tree = commit.tree()?;
|
||||
|
|
@ -157,9 +155,8 @@ impl<'a> BranchStore<'a> {
|
|||
|
||||
/// List all paths under a prefix in the latest tree.
|
||||
pub fn list_entries(&self, prefix: &str) -> Result<Vec<String>> {
|
||||
let commit_oid = match self.objects.resolve_ref(&self.branch)? {
|
||||
Some(oid) => oid,
|
||||
None => return Ok(vec![]),
|
||||
let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else {
|
||||
return Ok(vec![]);
|
||||
};
|
||||
let commit = self.objects.repo().find_commit(commit_oid)?;
|
||||
let tree_oid = commit.tree_id();
|
||||
|
|
@ -184,9 +181,8 @@ impl<'a> BranchStore<'a> {
|
|||
|
||||
/// Walk commits on the branch, newest first.
|
||||
pub fn log(&self, limit: usize) -> Result<Vec<CommitInfo>> {
|
||||
let commit_oid = match self.objects.resolve_ref(&self.branch)? {
|
||||
Some(oid) => oid,
|
||||
None => return Ok(vec![]),
|
||||
let Some(commit_oid) = self.objects.resolve_ref(&self.branch)? else {
|
||||
return Ok(vec![]);
|
||||
};
|
||||
let mut revwalk = self.objects.repo().revwalk()?;
|
||||
revwalk.set_sorting(git2::Sort::TIME | git2::Sort::TOPOLOGICAL)?;
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@ pub enum FileMode {
|
|||
impl FileMode {
|
||||
fn as_i32(self) -> i32 {
|
||||
match self {
|
||||
FileMode::Blob => 0o100644,
|
||||
FileMode::BlobExecutable => 0o100755,
|
||||
FileMode::Tree => 0o040000,
|
||||
Self::Blob => 0o100644,
|
||||
Self::BlobExecutable => 0o100755,
|
||||
Self::Tree => 0o040000,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_i32(mode: i32) -> Self {
|
||||
match mode {
|
||||
0o100755 => FileMode::BlobExecutable,
|
||||
0o040000 => FileMode::Tree,
|
||||
_ => FileMode::Blob,
|
||||
0o100755 => Self::BlobExecutable,
|
||||
0o040000 => Self::Tree,
|
||||
_ => Self::Blob,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ impl TreeEntries {
|
|||
self.0.get(path)
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, other: &TreeEntries) {
|
||||
pub fn merge(&mut self, other: &Self) {
|
||||
for (path, entry) in &other.0 {
|
||||
self.0.insert(path.clone(), entry.clone());
|
||||
}
|
||||
|
|
@ -224,7 +224,7 @@ fn read_tree_recursive(
|
|||
prefix: &str,
|
||||
entries: &mut TreeEntries,
|
||||
) -> Result<()> {
|
||||
for entry in tree.iter() {
|
||||
for entry in tree {
|
||||
let name = entry.name().unwrap_or("");
|
||||
let path = if prefix.is_empty() {
|
||||
name.to_string()
|
||||
|
|
@ -246,7 +246,7 @@ fn read_tree_recursive(
|
|||
/// Intermediate structure for building nested git trees from flat paths.
|
||||
struct DirNode {
|
||||
files: BTreeMap<String, TreeEntry>,
|
||||
dirs: BTreeMap<String, DirNode>,
|
||||
dirs: BTreeMap<String, Self>,
|
||||
}
|
||||
|
||||
impl DirNode {
|
||||
|
|
|
|||
|
|
@ -141,9 +141,8 @@ impl<'a> SnapshotStore<'a> {
|
|||
|
||||
/// Tip commit of a snapshot branch. `None` if branch doesn't exist.
|
||||
pub fn latest(&self, branch: &str) -> Result<Option<SnapshotInfo>> {
|
||||
let commit_oid = match self.objects.resolve_ref(branch)? {
|
||||
Some(oid) => oid,
|
||||
None => return Ok(None),
|
||||
let Some(commit_oid) = self.objects.resolve_ref(branch)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let commit = self.objects.repo().find_commit(commit_oid)?;
|
||||
let tree_oid = commit.tree_id();
|
||||
|
|
@ -173,9 +172,8 @@ impl<'a> SnapshotStore<'a> {
|
|||
|
||||
/// Walk commits on a snapshot branch, newest first.
|
||||
pub fn list_commits(&self, branch: &str, limit: usize) -> Result<Vec<SnapshotInfo>> {
|
||||
let commit_oid = match self.objects.resolve_ref(branch)? {
|
||||
Some(oid) => oid,
|
||||
None => return Ok(vec![]),
|
||||
let Some(commit_oid) = self.objects.resolve_ref(branch)? else {
|
||||
return Ok(vec![]);
|
||||
};
|
||||
let mut revwalk = self.objects.repo().revwalk()?;
|
||||
revwalk.set_sorting(git2::Sort::TIME | git2::Sort::TOPOLOGICAL)?;
|
||||
|
|
@ -245,7 +243,7 @@ impl<'a> SnapshotStore<'a> {
|
|||
let walker = walkdir::WalkDir::new(&disk_dir.disk_path)
|
||||
.follow_links(false)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok());
|
||||
.filter_map(std::result::Result::ok);
|
||||
|
||||
for entry in walker {
|
||||
// Skip symlinks
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue