use crate::cli::SkillCmd;
use crate::error::Result;
use crate::output::{print_success, Ctx};
const SKILL_MD: &str = r#"---
name: invoice-cli
description: >
Generate beautiful, internationally-compliant invoices (PDF) from the CLI.
Stateful (SQLite) — supports multiple issuer companies, clients, products,
tax profiles (SG GST / UK VAT / US / EU / custom), multiple Typst templates.
Use when the user asks to create, list, render, mark paid, or manage
invoices, or to manage clients / products / invoicing entities.
---
## invoice-cli
`invoice` is a stateful CLI for generating, tracking, and rendering invoices.
### Quick start
```
invoice issuer add acme --name "Acme Studio" --jurisdiction sg --tax-registered --tax-id "GST M2-..." --address "1 Marina Bay\nSingapore" --bank-line "Bank: DBS" --bank-line "Account: 1234567890"
invoice clients add meridian --name "Meridian & Co." --country US --address "..." \
--default-issuer acme --default-template boutique
invoice products add design --description "Design engagement" --unit project --price 8400 --currency SGD --tax-rate 9
invoice invoices new --client meridian --item design --due 30d # no --as needed: uses client default
invoice invoices render acme-2026-0001 --open # uses client.default_template
invoice invoices mark acme-2026-0001 paid # auto-stamps paid_at
invoice invoices duplicate acme-2026-0001 # clone for next month's billing
```
### Editing existing records
```
invoice issuer edit acme --phone "+65 ..." --bank-line "Bank: DBS" --bank-line "Account: 1234567890"
invoice clients edit meridian --default-template tiefletter-gold
invoice products edit design --price 9200
invoice issuer set-template acme boutique # shorthand for --template
invoice clients set-issuer meridian acme # shorthand
```
### Logo
Attach a logo image (PNG/SVG/JPG) to an issuer; the template renders it in the header.
```
invoice issuer edit acme --logo ~/Pictures/acme.png
```
### Editing drafts & credit notes
DRAFT invoices are mutable; once `issued`/`paid`/`void` they're immutable — use a credit note.
```
invoice invoices edit acme-2026-0001 --notes "Net 14 — early-payment 2% discount"
invoice invoices items add acme-2026-0001 "Extra fee:1:500"
invoice invoices credit-note acme-2026-0001 --item "Refund:1:500" --notes "Goodwill credit"
```
### Aging & export
```
invoice invoices aging # 0-30/31-60/61-90/90+ buckets
invoice invoices export --from 2026-01-01 --to 2026-03-31 --format csv --out q1.csv
```
### Discounts
Apply a discount at invoice level (percent OR fixed amount, mutually exclusive).
```
invoice invoices new --client meridian --item design --discount-rate 10
```
### Tips
- Run `invoice agent-info` for the full JSON capability manifest.
- Run `invoice doctor --json` to verify typst, DB, default issuer, and multi-company numbering.
- Item spec supports `product-slug[:qty]` OR `description:qty:price[:rate]`.
- Template resolution at render: `--template` flag > client.default_template > issuer.default_template > `vienna`.
- `--as` picks the issuer; omit it when the client has `default_issuer` pinned or `config.default_issuer` is set.
- New issuers default to `{issuer}-{year}-{seq:04}` so invoice numbers are globally addressable; use `issuer edit --number-format` for per-company custom prefixes.
- Use invoice numbers returned by JSON responses instead of predicting the next sequence.
- `mark issued` / `mark paid` auto-stamp `issued_at` / `paid_at` (first transition only).
- `invoices list` shows totals per invoice (computed with `rust_decimal`).
- Every tax value is computed with `rust_decimal` — no float rounding.
"#;
pub fn run(_cmd: SkillCmd, ctx: Ctx) -> Result<()> {
let targets = [
dirs_path(".claude/skills/invoice-cli/SKILL.md"),
dirs_path(".codex/skills/invoice-cli/SKILL.md"),
dirs_path(".gemini/skills/invoice-cli/SKILL.md"),
];
let mut written = Vec::new();
for t in targets {
if let Some(parent) = t.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&t, SKILL_MD)?;
written.push(t.display().to_string());
}
print_success(ctx, &written, |paths| {
for p in paths {
println!("installed → {}", p);
}
});
Ok(())
}
fn dirs_path(rel: &str) -> std::path::PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
std::path::PathBuf::from(home).join(rel)
}