refactor: Replace color-eyre with miette
miette provides richer diagnostic output (source spans, help text)
which will be useful as config parsing gets more complex. All io::Error
sites now use .into_diagnostic() for the conversion.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/Cargo.lock b/Cargo.lock
index 45c7667..854f960 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -103,6 +103,15 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "backtrace-ext"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
+dependencies = [
+ "backtrace",
+]
+
[[package]]
name = "bitflags"
version = "2.11.0"
@@ -155,33 +164,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
-[[package]]
-name = "color-eyre"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d"
-dependencies = [
- "backtrace",
- "color-spantrace",
- "eyre",
- "indenter",
- "once_cell",
- "owo-colors",
- "tracing-error",
-]
-
-[[package]]
-name = "color-spantrace"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427"
-dependencies = [
- "once_cell",
- "owo-colors",
- "tracing-core",
- "tracing-error",
-]
-
[[package]]
name = "colorchoice"
version = "1.0.5"
@@ -225,16 +207,6 @@ dependencies = [
"windows-sys",
]
-[[package]]
-name = "eyre"
-version = "0.6.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
-dependencies = [
- "indenter",
- "once_cell",
-]
-
[[package]]
name = "fastrand"
version = "2.3.0"
@@ -313,12 +285,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
-[[package]]
-name = "indenter"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
-
[[package]]
name = "indexmap"
version = "2.13.0"
@@ -331,6 +297,12 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "is_ci"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@@ -414,8 +386,28 @@ version = "7.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
dependencies = [
+ "backtrace",
+ "backtrace-ext",
"cfg-if",
- "unicode-width",
+ "miette-derive",
+ "owo-colors",
+ "supports-color",
+ "supports-hyperlinks",
+ "supports-unicode",
+ "terminal_size",
+ "textwrap",
+ "unicode-width 0.1.14",
+]
+
+[[package]]
+name = "miette-derive"
+version = "7.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -587,9 +579,9 @@ name = "ramekin"
version = "0.1.0"
dependencies = [
"clap",
- "color-eyre",
"fastrand",
"fs-err",
+ "miette",
"serde",
"serde-kdl2",
"serde_yaml",
@@ -756,6 +748,27 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+[[package]]
+name = "supports-color"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6"
+dependencies = [
+ "is_ci",
+]
+
+[[package]]
+name = "supports-hyperlinks"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91"
+
+[[package]]
+name = "supports-unicode"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
+
[[package]]
name = "syn"
version = "2.0.117"
@@ -780,6 +793,26 @@ dependencies = [
"windows-sys",
]
+[[package]]
+name = "terminal_size"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
+dependencies = [
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
+dependencies = [
+ "unicode-linebreak",
+ "unicode-width 0.2.2",
+]
+
[[package]]
name = "thiserror"
version = "2.0.18"
@@ -841,16 +874,6 @@ dependencies = [
"valuable",
]
-[[package]]
-name = "tracing-error"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
-dependencies = [
- "tracing",
- "tracing-subscriber",
-]
-
[[package]]
name = "tracing-log"
version = "0.2.0"
@@ -886,12 +909,24 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+[[package]]
+name = "unicode-linebreak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
+
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
[[package]]
name = "unicode-xid"
version = "0.2.6"
diff --git a/Cargo.toml b/Cargo.toml
index 0670a49..f74c864 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,7 @@ edition = "2024"
[dependencies]
clap = { version = "*", features = ["derive"] }
-color-eyre = "*"
+miette = { version = "*", features = ["fancy"] }
fastrand = "*"
fs-err = "*"
serde = { version = "*", features = ["derive"] }
diff --git a/src/config.rs b/src/config.rs
index 95ce23d..33cca80 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,7 +1,7 @@
use std::fmt;
use std::path::{Path, PathBuf};
-use color_eyre::eyre::{Context, Result};
+use miette::{Context, IntoDiagnostic, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
@@ -134,6 +134,7 @@ impl Config {
let xdg = xdg::BaseDirectories::with_prefix("ramekin");
let user_path = xdg
.place_config_file("config.kdl")
+ .into_diagnostic()
.wrap_err("failed to determine user config path")?;
if user_path.exists() {
@@ -174,8 +175,12 @@ impl Config {
/// Parse a config file.
fn load_file(path: &Path) -> Result<Self> {
- let content = fs_err::read_to_string(path).wrap_err("failed to read config file")?;
- serde_kdl2::from_str(&content).wrap_err("failed to parse config file")
+ let content = fs_err::read_to_string(path)
+ .into_diagnostic()
+ .wrap_err("failed to read config file")?;
+ serde_kdl2::from_str(&content)
+ .into_diagnostic()
+ .wrap_err("failed to parse config file")
}
/// Resolve all mounts, skipping any whose source directory does not exist.
@@ -237,15 +242,15 @@ pub fn clear_agent_dir(agent_dir: &Path) -> Result<()> {
if !agent_dir.exists() {
return Ok(());
}
- for entry in fs_err::read_dir(agent_dir)? {
- let entry = entry?;
+ for entry in fs_err::read_dir(agent_dir).into_diagnostic()? {
+ let entry = entry.into_diagnostic()?;
if entry.file_name() == "auth.json" {
continue;
}
- if entry.file_type()?.is_dir() {
- fs_err::remove_dir_all(entry.path())?;
+ if entry.file_type().into_diagnostic()?.is_dir() {
+ fs_err::remove_dir_all(entry.path()).into_diagnostic()?;
} else {
- fs_err::remove_file(entry.path())?;
+ fs_err::remove_file(entry.path()).into_diagnostic()?;
}
}
Ok(())
@@ -271,7 +276,7 @@ pub fn assemble_pi(agent_dir: &Path, entries: &[ResolvedPiEntry]) -> Result<()>
if entry.source.is_dir() {
copy_dir(&entry.source, &target)?;
} else {
- fs_err::copy(&entry.source, &target)?;
+ fs_err::copy(&entry.source, &target).into_diagnostic()?;
}
}
Ok(())
@@ -279,15 +284,15 @@ pub fn assemble_pi(agent_dir: &Path, entries: &[ResolvedPiEntry]) -> Result<()>
/// Recursively copy a directory tree.
fn copy_dir(src: &Path, dst: &Path) -> Result<()> {
- fs_err::create_dir_all(dst)?;
- for entry in fs_err::read_dir(src)? {
- let entry = entry?;
- let file_type = entry.file_type()?;
+ fs_err::create_dir_all(dst).into_diagnostic()?;
+ for entry in fs_err::read_dir(src).into_diagnostic()? {
+ let entry = entry.into_diagnostic()?;
+ let file_type = entry.file_type().into_diagnostic()?;
let target = dst.join(entry.file_name());
if file_type.is_dir() {
copy_dir(&entry.path(), &target)?;
} else {
- fs_err::copy(entry.path(), &target)?;
+ fs_err::copy(entry.path(), &target).into_diagnostic()?;
}
}
Ok(())
diff --git a/src/main.rs b/src/main.rs
index 8a1e6c4..195ed4c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use clap::{Parser, Subcommand};
-use color_eyre::eyre::{Context, Result, bail};
+use miette::{Context, IntoDiagnostic, Result, bail};
use serde::Serialize;
use tracing::{error, info};
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
@@ -42,7 +42,9 @@ enum Cmd {
}
fn main() -> Result<()> {
- color_eyre::install()?;
+ miette::set_hook(Box::new(|_| {
+ Box::new(miette::MietteHandlerOpts::new().build())
+ }))?;
tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::from_default_env())
@@ -77,28 +79,35 @@ impl Ramekin {
/// Resolve all paths, create XDG directories, seed default files, and
/// resolve mounts.
fn resolve(workspace_arg: PathBuf) -> Result<Self> {
- let workspace = workspace_arg.canonicalize().wrap_err_with(|| {
- format!("workspace path does not exist: {}", workspace_arg.display())
- })?;
+ let workspace = workspace_arg
+ .canonicalize()
+ .into_diagnostic()
+ .wrap_err_with(|| {
+ format!("workspace path does not exist: {}", workspace_arg.display())
+ })?;
let xdg = xdg::BaseDirectories::with_prefix("ramekin");
// Agent directory mirrors /root/.pi/agent in the container.
let agent_dir = xdg
.create_config_directory("agent")
+ .into_diagnostic()
.wrap_err("failed to create agent config directory")?;
let pi_data_dir = xdg
.create_data_directory("")
+ .into_diagnostic()
.wrap_err("failed to create pi data directory")?;
let repo_slug = repo_slug(&workspace);
let repo_sessions_dir = xdg
.create_data_directory(format!("repos/{repo_slug}/sessions"))
+ .into_diagnostic()
.wrap_err("failed to create repo sessions directory")?;
let cache_dir = xdg
.create_cache_directory("")
+ .into_diagnostic()
.wrap_err("failed to create cache directory")?;
let custom_dockerfile_path = workspace.join(".ramekin/Dockerfile");
@@ -134,8 +143,8 @@ impl Ramekin {
// Always write ramekin.ts after assembly.
let extensions_dir = agent_dir.join("extensions");
- fs_err::create_dir_all(&extensions_dir)?;
- fs_err::write(extensions_dir.join("ramekin.ts"), RAMEKIN_EXTENSION)?;
+ fs_err::create_dir_all(&extensions_dir).into_diagnostic()?;
+ fs_err::write(extensions_dir.join("ramekin.ts"), RAMEKIN_EXTENSION).into_diagnostic()?;
Ok(Self {
workspace,
@@ -232,7 +241,7 @@ impl Ramekin {
// Write the embedded Dockerfile to the cache directory
let base_dockerfile = self.cache_dir.join("Dockerfile");
- fs_err::write(&base_dockerfile, DOCKERFILE)?;
+ fs_err::write(&base_dockerfile, DOCKERFILE).into_diagnostic()?;
// Build the base image
if rebuild {
@@ -248,7 +257,10 @@ impl Ramekin {
build_cmd.args(["--no-cache", "--pull"]);
}
build_cmd.arg(&self.cache_dir);
- let status = build_cmd.status().wrap_err("failed to build base image")?;
+ let status = build_cmd
+ .status()
+ .into_diagnostic()
+ .wrap_err("failed to build base image")?;
if !status.success() {
bail!("base image build failed ({})", status);
}
@@ -267,6 +279,7 @@ impl Ramekin {
let session_dir = self
.xdg
.create_cache_directory(format!("sessions/{session_id}"))
+ .into_diagnostic()
.wrap_err("failed to create session directory")?;
let all_mounts: Vec<_> = self
@@ -277,7 +290,7 @@ impl Ramekin {
.collect();
let compose = generate_compose(&dockerfile, &build_context, &all_mounts, pi_args);
let compose_file = session_dir.join("compose.yml");
- fs_err::write(&compose_file, &compose)?;
+ fs_err::write(&compose_file, &compose).into_diagnostic()?;
let project_name = format!("ramekin-{session_id}");
let docker_compose = |args: &[&str]| -> Result<Command> {
@@ -291,6 +304,7 @@ impl Ramekin {
let status = docker_compose(&["up", "-d", "--build"])?
.status()
+ .into_diagnostic()
.wrap_err("failed to run docker compose up")?;
if !status.success() {
bail!("docker compose up failed ({})", status);
@@ -301,11 +315,13 @@ impl Ramekin {
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
+ .into_diagnostic()
.wrap_err("failed to attach to agent")?;
// Always tear down, regardless of attach exit status
let down_status = docker_compose(&["down"])?
.status()
+ .into_diagnostic()
.wrap_err("failed to run docker compose down")?;
if !down_status.success() {
error!("docker compose down failed ({})", down_status);