use crate::error::Result;
use crate::output::{print_raw, Ctx};
use crate::tax;
pub fn run(_ctx: Ctx) -> Result<()> {
let config_path = crate::config::config_path()?.display().to_string();
let state_dir = crate::config::state_path()?.display().to_string();
let database = crate::config::db_path()?.display().to_string();
let profiles: Vec<_> = tax::all_profiles()
.into_iter()
.map(|p| {
serde_json::json!({
"code": p.code,
"country": p.country,
"tax_label": p.tax_label,
"default_rate": p.default_rate,
"currency": p.currency,
"symbol": p.symbol,
"tax_invoice_title": p.tax_invoice_title,
"supports_reverse_charge": p.supports_reverse_charge,
})
})
.collect();
let commands_list: &[(&str, &str)] = &[
("issuer add <slug> --name X --jurisdiction sg|uk|us|eu --address ... [--logo PATH --number-format F]", "Register an issuer (billing entity). New issuers default to {issuer}-{year}-{seq:04} so invoice numbers are globally addressable"),
("issuer edit <slug> [--name ... --template ... --jurisdiction ... --number-format ... --logo PATH etc]", "Update any subset of an issuer's fields (incl. logo path and numbering format)"),
("issuer set-template <slug> <template>", "Shorthand: change an issuer's default template"),
("issuer list | ls", "List issuers"),
("issuer show <slug> | get", "Show issuer details"),
("issuer delete <slug> | rm", "Delete an issuer"),
("clients add <slug> --name X --address ... [--default-issuer S --default-template T]", "Register a client, optionally pinning a default issuer/template"),
("clients edit <slug> [--name ... --default-issuer ... --default-template ...]", "Update any subset of a client's fields"),
("clients set-issuer <slug> <issuer-slug>", "Shorthand: pin the default issuer for this client"),
("clients set-template <slug> <template>", "Shorthand: pin the preferred template for this client"),
("clients list | ls", "List clients"),
("clients show <slug> | get", "Show client details"),
("clients delete <slug> | rm", "Delete a client"),
("products add <slug> --description X --unit Y --price N --currency SGD", "Register a reusable product/service line"),
("products edit <slug> [--description ... --price ... etc]", "Update any subset of a product's fields"),
("products list | ls", "List products"),
("products show <slug> | get", "Show product details"),
("products delete <slug> | rm", "Delete a product"),
("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 or config.default_issuer). Optional invoice-level discount (percent OR fixed major-units)"),
("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"),
("invoices items add <number> <spec> [--subtitle ... --discount-rate ... --discount-fixed ...]", "Add a line item to a DRAFT invoice (spec: 'product-slug[:qty]' OR 'Description:qty:price[:rate]')"),
("invoices items remove <number> <position> | rm", "Remove the line at zero-indexed position from a DRAFT invoice"),
("invoices items edit <number> <position> [--description ... --subtitle ... --qty ... --unit ... --price ... --tax-rate ... --discount-rate ... --discount-fixed ...]", "Edit any subset of a DRAFT invoice line's fields"),
("invoices credit-note <number> [--full | --item <spec>...] [--notes ... --pay-link ...]", "Issue a credit note against an existing invoice. --full reverses source items; --item accepts positive refund specs and stores them as credits"),
("invoices aging [--as <issuer>]", "Ageing report for unpaid invoices, bucketed 0-30 / 31-60 / 61-90 / 90+ days past due"),
("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"),
("invoices duplicate <number> [--client C --as I --due 30d]", "Clone an invoice's line items into a new draft (for recurring billing)"),
("invoices list | ls [--status X] [--as Y] [--overdue]", "List invoices (includes total per invoice). --overdue filters to past-due unpaid invoices"),
("invoices show <number> | get", "Show invoice details"),
("invoices render <number> [--template T] [--out PATH] [--open]", "Render to PDF. Template chain: --template > client.default_template > issuer.default_template > 'vienna'"),
("invoices mark <number> draft|issued|paid|void", "Update invoice status (auto-stamps issued_at/paid_at)"),
("invoices delete <number> [--force] | rm", "Delete an invoice. --force allows deleting non-draft (breaks number-sequence integrity — prefer 'mark void' or credit-note)"),
("template list", "List available PDF templates"),
("template preview <name>", "Render a template with synthetic data"),
("config show | path | set <key> <value>", "View / edit config"),
("agent-info | info", "This manifest"),
("doctor", "Diagnose dependencies and config"),
("skill install", "Install embedded Claude/Codex/Gemini skill"),
("update [--check]", "Self-update — queries crates.io, upgrades via brew/cargo"),
];
let mut commands = serde_json::Map::new();
for (k, v) in commands_list {
commands.insert(
(*k).to_string(),
serde_json::Value::String((*v).to_string()),
);
}
let manifest = serde_json::json!({
"name": "invoice",
"version": env!("CARGO_PKG_VERSION"),
"description": env!("CARGO_PKG_DESCRIPTION"),
"commands": commands,
"flags": {
"--json": "Force JSON envelope output (auto-enabled when piped)",
"--quiet": "Suppress human output"
},
"exit_codes": {
"0": "Success",
"1": "Transient error (IO, render) — retry may help",
"2": "Config error — fix setup",
"3": "Bad input / not found / ambiguous — fix arguments",
"4": "Rate limited — wait and retry"
},
"envelope_schema": {
"version": "1",
"status": "success | error",
"data": "… (success)",
"error": "{ code, message, suggestion } (error)"
},
"config_path": config_path,
"state_dir": state_dir,
"database": database,
"templates": ["helvetica-nera", "tiefletter-gold", "monoline", "vienna", "boutique"],
"tax_profiles": profiles,
"item_spec": "product-slug[:qty] OR description:qty:price[:rate]",
"config_keys": {
"default_issuer": "Issuer slug used by invoices new when --as is omitted and the client has no default issuer. `invoice config set default_issuer unset` clears it.",
"self_update": "Reserved shared setting for updater behavior across the suite."
},
"first_run": [
"invoice doctor --json",
"invoice issuer add <slug> --name <display-name> --jurisdiction sg|uk|us|eu|custom --address \"line1\\nline2\"",
"invoice config set default_issuer <issuer-slug>",
"invoice clients add <slug> --name <client-name> --address \"line1\\nline2\" --default-issuer <issuer-slug>",
"invoice invoices new --client <client-slug> --item \"Description:1:100:20\""
],
"examples": [
{
"goal": "Create an invoice using client/default issuer routing",
"command": "invoice invoices new --client meridian --item \"Consulting:1:1200:20\" --json"
},
{
"goal": "Create an invoice for a specific company",
"command": "invoice invoices new --as paperfoot --client meridian --item design:1 --json"
},
{
"goal": "Render an invoice PDF",
"command": "invoice invoices render paperfoot-2026-0001 --json"
},
{
"goal": "Issue a full credit note",
"command": "invoice invoices credit-note paperfoot-2026-0001 --full --json"
}
],
"multi_company_model": {
"issuer": "The company/person billing as. Each issuer owns tax profile, logo, bank details, default notes/output dir, and its own sequence counter.",
"client": "The customer. A client may pin default_issuer and default_template.",
"invoice_numbering": "Invoice numbers are globally addressable by the CLI. New issuers default to {issuer}-{year}-{seq:04}; legacy colliding formats are auto-prefixed with the issuer slug on collision.",
"recommended_default": "Set config.default_issuer for single-company workflows; use client.default_issuer or explicit --as for multi-company workflows."
},
"guardrails": [
"Run doctor before first use and after changing config.",
"Prefer --json for agents; stdout is data and stderr is diagnostics.",
"Use globally unique issuer number formats, ideally {issuer}-{year}-{seq:04}.",
"Do not delete issued invoices; mark void or create a credit note.",
"Use invoice numbers returned by JSON responses rather than guessing the next sequence.",
"Pin client default issuers when the same customer is always billed by the same company."
]
});
print_raw(&manifest);
Ok(())
}