Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "texforge"
version = "0.8.0"
version = "0.9.0"
edition = "2021"
# Raised from 1.75: `hayro` (the pure-Rust PDF rasterizer) requires 1.92.
rust-version = "1.92"
Expand Down
13 changes: 13 additions & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ texforge <command> [options]
| `texforge template add <name>` | Download a template from the registry |
| `texforge template remove <name>` | Remove an installed template |
| `texforge template validate <name>` | Verify template compatibility |
| `texforge template refresh` | Refresh all cached templates (bypass TTL) |
| `texforge template refresh <name>` | Refresh one cached template (bypass TTL) |

## Spell-Check

Expand Down Expand Up @@ -91,6 +93,17 @@ Default scope is global (`~/.texforge/spell-words`). Both scopes are unioned at
|---|---|
| `texforge doctor` | Diagnose Tectonic, cache, fonts, dictionaries, and project |

## Uninstall

| Command | Description |
|---|---|
| `texforge uninstall` | Remove everything texforge manages under `~/.texforge` |
| `texforge uninstall --yes` | Skip the confirmation prompt |
| `texforge uninstall --dry-run` | Print the plan without removing anything |
| `texforge uninstall --include-spell-words` | Also remove the personal spell dictionary (preserved by default) |

The texforge binary itself is never removed by this command. The personal spell dictionary (`~/.texforge/spell-words`) contains your own writing and is preserved unless `--include-spell-words` is passed.

## Configuration

| Command | Description |
Expand Down
16 changes: 14 additions & 2 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,19 @@ x86_64 are published on the

## Uninstall

To remove everything texforge manages (Tectonic engine, template cache, dictionary cache, configuration):

```bash
rm -f ~/.local/bin/texforge # texforge binary
rm -rf ~/.texforge/ # tectonic engine + cached templates
texforge uninstall
```

This shows what it would remove and asks for confirmation. The personal spell dictionary (`~/.texforge/spell-words`) is preserved by default — it contains your own writing. To remove it as well:

```bash
texforge uninstall --include-spell-words
```

The texforge binary itself is not removed by this command. To remove it:

- **If installed via the quick installer or a direct download:** `rm -f ~/.local/bin/texforge` (or the path shown by `texforge uninstall`).
- **If installed via cargo:** `cargo uninstall texforge`.
6 changes: 3 additions & 3 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ SKILLS_REPO="https://github.com/UniverLab/skills"

if [ -n "${SKIP_SKILL:-}" ]; then
info "skill" "skipped (SKIP_SKILL set)"
elif command -v npx >/dev/null 2>&1; then
elif command -v npx >/dev/null 2>&1 && (exec </dev/tty) 2>/dev/null; then
printf '\n \033[1;36m?\033[0m Install the \033[1m%s\033[0m agent skill? (teaches AI agents how to use %s) [Y/n] ' "$SKILL" "$SKILL"
read -r ANSWER </dev/tty
case "$ANSWER" in
Expand All @@ -139,15 +139,15 @@ elif command -v npx >/dev/null 2>&1; then
;;
*)
info "skill" "adding '$SKILL' (npx skills add)"
if npx -y skills add "$SKILLS_REPO" --skill "$SKILL"; then
if npx -y skills add "$SKILLS_REPO" --skill "$SKILL" </dev/tty; then
info "skill" "installed"
else
info "skill" "skipped — add later with: npx skills add $SKILLS_REPO --skill $SKILL"
fi
;;
esac
else
info "skill" "npx not found — add later with: npx skills add $SKILLS_REPO --skill $SKILL"
info "skill" "skipped — add later with: npx skills add $SKILLS_REPO --skill $SKILL"
fi

# ============================================================
Expand Down
23 changes: 23 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ enum Commands {
},
/// Diagnose the managed environment (Tectonic, cache, fonts, dictionaries, project)
Doctor,
/// Remove everything texforge manages under ~/.texforge
Uninstall {
/// Skip the confirmation prompt
#[arg(long)]
yes: bool,
/// Print the plan without removing anything
#[arg(long)]
dry_run: bool,
/// Also remove the personal spell dictionary (your own writing)
#[arg(long)]
include_spell_words: bool,
},
/// Manage global configuration
Config {
/// Key to get/set (name, email, institution, language)
Expand Down Expand Up @@ -189,6 +201,11 @@ enum TemplateAction {
Remove { name: String },
/// Validate template compatibility
Validate { name: String },
/// Refresh cached templates (bypass TTL; all templates or one by name)
Refresh {
/// Template name to refresh (omit to refresh all cached templates)
name: Option<String>,
},
}

impl Cli {
Expand Down Expand Up @@ -235,6 +252,7 @@ impl Cli {
TemplateAction::Add { source } => commands::template::add(&source),
TemplateAction::Remove { name } => commands::template::remove(&name),
TemplateAction::Validate { name } => commands::template::validate(&name),
TemplateAction::Refresh { name } => commands::template::refresh(name.as_deref()),
},
Commands::Spell { action } => {
let action = match action {
Expand All @@ -261,6 +279,11 @@ impl Cli {
commands::spell::execute(action)
}
Commands::Doctor => commands::doctor::execute(),
Commands::Uninstall {
yes,
dry_run,
include_spell_words,
} => commands::uninstall::execute(yes, dry_run, include_spell_words),
Commands::Config { key, value } => match (key, value) {
(None, None) => commands::config::wizard(),
(Some(k), None) if k == "list" => commands::config::list(),
Expand Down
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ pub mod preview;
pub mod spell;
pub mod stats;
pub mod template;
pub mod uninstall;
25 changes: 23 additions & 2 deletions src/commands/pdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ fn print_meta(key: &str, value: Option<&str>) {
}

fn cmd_pages(project: &Project, pdf_path: &Path) -> Result<()> {
let (breaks, source) = build_page_breaks(project, pdf_path)?;
eprintln!("source: {}", source_label(source));
println!("{}", pdftext::format_page_breaks(&breaks));
Ok(())
}

fn build_page_breaks(
project: &Project,
pdf_path: &Path,
) -> Result<(Vec<pdftext::PdfPageBreak>, pdftext::PageBreakSource)> {
if let Some((outline_entries, page_count)) = pdftext::read_pdf_outline(pdf_path)? {
let breaks = pdftext::page_breaks_from_outline(&outline_entries, page_count);
return Ok((breaks, pdftext::PageBreakSource::Outline));
}

let page_texts = pdftext::extract_text_by_pages(pdf_path)?;
let outline = outline::build_outline(
&project.config.document.title,
Expand All @@ -114,8 +129,14 @@ fn cmd_pages(project: &Project, pdf_path: &Path) -> Result<()> {
.map(|s| (s.number.clone(), s.title.clone()))
.collect();
let breaks = pdftext::page_breaks(&page_texts, &sections);
println!("{}", pdftext::format_page_breaks(&breaks));
Ok(())
Ok((breaks, pdftext::PageBreakSource::TextMatch))
}

fn source_label(source: pdftext::PageBreakSource) -> &'static str {
match source {
pdftext::PageBreakSource::Outline => "pdf-outline",
pdftext::PageBreakSource::TextMatch => "text-match",
}
}

fn cmd_check(project: &Project, pdf_path: &Path) -> Result<()> {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ mod tests {
let path = dir.path().join("preview").join(name);
let (w, h, rgba) = decode(&path);
assert_eq!((w, h), (612, 842), "{name}");
assert!(rgba.chunks_exact(4).all(|px| px[3] == 255));
assert!(rgba.as_chunks::<4>().0.iter().all(|px| px[3] == 255));
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/commands/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ pub fn validate(name: &str) -> Result<()> {
Ok(())
}

/// Refresh cached templates, bypassing the TTL.
pub fn refresh(name: Option<&str>) -> Result<()> {
match name {
Some(n) => {
println!("Refreshing template '{}'...", n);
templates::refresh(n)?;
println!(" ◇ Template '{}' refreshed", n);
}
None => {
templates::refresh_all()?;
}
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading