1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
mod ai;
mod assemble;
mod backup;
mod cli;
mod config;
mod error;
mod grammar;
mod project;
mod scripting;
mod storage;
mod store;
mod tui;
mod typst_compile;
use clap::Parser;
fn main() {
let cli = cli::Cli::parse();
// Tracing routing depends on the subcommand. TUI sessions must NOT
// write to stderr — any log line printed mid-frame corrupts ratatui's
// back-buffer (we'd see ghost panes or stray text inside the rendered
// grid). Route TUI logs to a per-project file and keep CLI logs on
// stderr where they're useful.
let is_tui = matches!(&cli.command, None | Some(cli::Command::Tui));
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("inkhaven=info,warn"));
if is_tui {
let log_path = tui_log_path(cli.project.as_deref());
// Best-effort file open; fall back to stderr-less /dev/null if the
// path can't be created (read-only project dir, full disk, etc.) —
// logs are diagnostic, not load-bearing.
let file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&log_path)
.ok();
if let Some(file) = file {
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_ansi(false)
.with_writer(std::sync::Mutex::new(file))
.init();
} else {
// Last resort: drop logs entirely. We don't want stderr writes
// bleeding into the TUI.
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(std::io::sink)
.init();
}
} else {
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.init();
}
let rt = match tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
{
Ok(rt) => rt,
Err(e) => {
eprintln!("inkhaven: could not start tokio runtime: {e}");
std::process::exit(1);
}
};
let _guard = rt.enter();
match cli.run() {
Ok(()) => {}
Err(e) => {
// anyhow's `{:#}` chains causes without showing the backtrace.
eprintln!("inkhaven: {e:#}");
std::process::exit(1);
}
}
}
/// Where to write TUI session logs. Lives inside the project directory so
/// it's tied to the work being edited and easy to gitignore. Falls back to
/// the system temp dir if no `--project` was passed (the TUI will still try
/// to open `.` and may succeed).
fn tui_log_path(project: Option<&std::path::Path>) -> std::path::PathBuf {
match project {
Some(p) => p.join(".inkhaven.log"),
None => std::env::current_dir()
.unwrap_or_else(|_| std::env::temp_dir())
.join(".inkhaven.log"),
}
}