1use crate::error::Result;
2use crate::output::{print_raw, Ctx};
3use crate::tax;
4
5pub fn run(_ctx: Ctx) -> Result<()> {
6 let profiles: Vec<_> = tax::all_profiles()
7 .into_iter()
8 .map(|p| {
9 serde_json::json!({
10 "code": p.code,
11 "country": p.country,
12 "tax_label": p.tax_label,
13 "default_rate": p.default_rate,
14 "currency": p.currency,
15 "symbol": p.symbol,
16 "tax_invoice_title": p.tax_invoice_title,
17 "supports_reverse_charge": p.supports_reverse_charge,
18 })
19 })
20 .collect();
21
22 let commands_list: &[(&str, &str)] = &[
25 ("issuer add <slug> --name X --jurisdiction sg|uk|us|eu --address ... [--logo PATH]", "Register an issuer (billing entity). --logo points to a PNG/SVG/JPG rendered in template header"),
26 ("issuer edit <slug> [--name ... --template ... --jurisdiction ... --logo PATH etc]", "Update any subset of an issuer's fields (incl. logo path)"),
27 ("issuer set-template <slug> <template>", "Shorthand: change an issuer's default template"),
28 ("issuer list | ls", "List issuers"),
29 ("issuer show <slug> | get", "Show issuer details"),
30 ("issuer delete <slug> | rm", "Delete an issuer"),
31 ("clients add <slug> --name X --address ... [--default-issuer S --default-template T]", "Register a client, optionally pinning a default issuer/template"),
32 ("clients edit <slug> [--name ... --default-issuer ... --default-template ...]", "Update any subset of a client's fields"),
33 ("clients set-issuer <slug> <issuer-slug>", "Shorthand: pin the default issuer for this client"),
34 ("clients set-template <slug> <template>", "Shorthand: pin the preferred template for this client"),
35 ("clients list | ls", "List clients"),
36 ("clients show <slug> | get", "Show client details"),
37 ("clients delete <slug> | rm", "Delete a client"),
38 ("products add <slug> --description X --unit Y --price N --currency SGD", "Register a reusable product/service line"),
39 ("products edit <slug> [--description ... --price ... etc]", "Update any subset of a product's fields"),
40 ("products list | ls", "List products"),
41 ("products show <slug> | get", "Show product details"),
42 ("products delete <slug> | rm", "Delete a product"),
43 ("invoices new [--as <issuer>] --client <client> --item <spec>... [--discount-rate R | --discount-fixed X]", "Create a new invoice (omit --as when client has a default_issuer). Optional invoice-level discount (percent OR fixed major-units)"),
44 ("invoices edit <number> [--client ... --due ... --terms ... --notes ... --currency ... --pay-link ... --reverse-charge ... --discount-rate ... --discount-fixed ...]", "Edit DRAFT invoice metadata only — issued/paid/void invoices are immutable; use credit-note instead"),
45 ("invoices items <number> add <spec> [--subtitle ... --discount-rate ... --discount-fixed ...]", "Add a line item to a DRAFT invoice (spec: 'product-slug[:qty]' OR 'Description:qty:price[:rate]')"),
46 ("invoices items <number> remove <position> | rm", "Remove the line at zero-indexed position from a DRAFT invoice"),
47 ("invoices items <number> edit <position> [--description ... --subtitle ... --qty ... --unit ... --price ... --tax-rate ... --discount-rate ... --discount-fixed ...]", "Edit any subset of a DRAFT invoice line's fields"),
48 ("invoices credit-note <number> [--full | --item <spec>...] [--notes ... --pay-link ...]", "Issue a credit note against an existing invoice. --full clones source items as positive reversal; --item lets you specify exact refund lines"),
49 ("invoices aging [--as <issuer>]", "Ageing report for unpaid invoices, bucketed 0-30 / 31-60 / 61-90 / 90+ days past due"),
50 ("invoices export [--from YYYY-MM-DD --to YYYY-MM-DD --format csv|json --out PATH --as <issuer>]", "Export invoices for accountant handoff. Defaults to CSV on stdout when --out omitted"),
51 ("invoices duplicate <number> [--client C --as I --due 30d]", "Clone an invoice's line items into a new draft (for recurring billing)"),
52 ("invoices list | ls [--status X] [--as Y] [--overdue]", "List invoices (includes total per invoice). --overdue filters to past-due unpaid invoices"),
53 ("invoices show <number> | get", "Show invoice details"),
54 ("invoices render <number> [--template T] [--out PATH] [--open]", "Render to PDF. Template chain: --template > client.default_template > issuer.default_template > 'vienna'"),
55 ("invoices mark <number> draft|issued|paid|void", "Update invoice status (auto-stamps issued_at/paid_at)"),
56 ("invoices delete <number> [--force] | rm", "Delete an invoice. --force allows deleting non-draft (breaks number-sequence integrity — prefer 'mark void' or credit-note)"),
57 ("template list", "List available PDF templates"),
58 ("template preview <name>", "Render a template with synthetic data"),
59 ("config show | path | set <key> <value>", "View / edit config"),
60 ("agent-info | info", "This manifest"),
61 ("doctor", "Diagnose dependencies and config"),
62 ("skill install", "Install embedded Claude/Codex/Gemini skill"),
63 ("update [--check]", "Self-update — queries crates.io, upgrades via brew/cargo"),
64 ];
65 let mut commands = serde_json::Map::new();
66 for (k, v) in commands_list {
67 commands.insert((*k).to_string(), serde_json::Value::String((*v).to_string()));
68 }
69
70 let manifest = serde_json::json!({
71 "name": "invoice",
72 "version": env!("CARGO_PKG_VERSION"),
73 "description": env!("CARGO_PKG_DESCRIPTION"),
74 "commands": commands,
75 "flags": {
76 "--json": "Force JSON envelope output (auto-enabled when piped)",
77 "--quiet": "Suppress human output"
78 },
79 "exit_codes": {
80 "0": "Success",
81 "1": "Transient error (IO, render) — retry may help",
82 "2": "Config error — fix setup",
83 "3": "Bad input / not found / ambiguous — fix arguments",
84 "4": "Rate limited — wait and retry"
85 },
86 "envelope_schema": {
87 "version": "1",
88 "status": "success | error",
89 "data": "… (success)",
90 "error": "{ code, message, suggestion } (error)"
91 },
92 "config_path": "~/.config/invoice/config.toml",
93 "state_dir": "~/.local/share/invoice/",
94 "database": "~/.local/share/invoice/invoice.db",
95 "templates": ["helvetica-nera", "tiefletter-gold", "monoline", "vienna", "boutique"],
96 "tax_profiles": profiles,
97 "item_spec": "product-slug[:qty] OR description:qty:price[:rate]"
98 });
99
100 print_raw(&manifest);
101 Ok(())
102}