1use crate::error::Result;
2use crate::output::{print_raw, Ctx};
3use crate::tax;
4
5pub fn run(_ctx: Ctx) -> Result<()> {
6 let config_path = crate::config::config_path()?.display().to_string();
7 let state_dir = crate::config::state_path()?.display().to_string();
8 let database = crate::config::db_path()?.display().to_string();
9
10 let profiles: Vec<_> = tax::all_profiles()
11 .into_iter()
12 .map(|p| {
13 serde_json::json!({
14 "code": p.code,
15 "country": p.country,
16 "tax_label": p.tax_label,
17 "default_rate": p.default_rate,
18 "currency": p.currency,
19 "symbol": p.symbol,
20 "tax_invoice_title": p.tax_invoice_title,
21 "supports_reverse_charge": p.supports_reverse_charge,
22 })
23 })
24 .collect();
25
26 let commands_list: &[(&str, &str)] = &[
29 ("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"),
30 ("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)"),
31 ("issuer set-template <slug> <template>", "Shorthand: change an issuer's default template"),
32 ("issuer list | ls", "List issuers"),
33 ("issuer show <slug> | get", "Show issuer details"),
34 ("issuer delete <slug> | rm", "Delete an issuer"),
35 ("clients add <slug> --name X --address ... [--default-issuer S --default-template T]", "Register a client, optionally pinning a default issuer/template"),
36 ("clients edit <slug> [--name ... --default-issuer ... --default-template ...]", "Update any subset of a client's fields"),
37 ("clients set-issuer <slug> <issuer-slug>", "Shorthand: pin the default issuer for this client"),
38 ("clients set-template <slug> <template>", "Shorthand: pin the preferred template for this client"),
39 ("clients list | ls", "List clients"),
40 ("clients show <slug> | get", "Show client details"),
41 ("clients delete <slug> | rm", "Delete a client"),
42 ("products add <slug> --description X --unit Y --price N --currency SGD", "Register a reusable product/service line"),
43 ("products edit <slug> [--description ... --price ... etc]", "Update any subset of a product's fields"),
44 ("products list | ls", "List products"),
45 ("products show <slug> | get", "Show product details"),
46 ("products delete <slug> | rm", "Delete a product"),
47 ("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)"),
48 ("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"),
49 ("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]')"),
50 ("invoices items remove <number> <position> | rm", "Remove the line at zero-indexed position from a DRAFT invoice"),
51 ("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"),
52 ("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"),
53 ("invoices aging [--as <issuer>]", "Ageing report for unpaid invoices, bucketed 0-30 / 31-60 / 61-90 / 90+ days past due"),
54 ("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"),
55 ("invoices duplicate <number> [--client C --as I --due 30d]", "Clone an invoice's line items into a new draft (for recurring billing)"),
56 ("invoices list | ls [--status X] [--as Y] [--overdue]", "List invoices (includes total per invoice). --overdue filters to past-due unpaid invoices"),
57 ("invoices show <number> | get", "Show invoice details"),
58 ("invoices render <number> [--template T] [--out PATH] [--open]", "Render to PDF. Template chain: --template > client.default_template > issuer.default_template > 'vienna'"),
59 ("invoices mark <number> draft|issued|paid|void", "Update invoice status (auto-stamps issued_at/paid_at)"),
60 ("invoices delete <number> [--force] | rm", "Delete an invoice. --force allows deleting non-draft (breaks number-sequence integrity — prefer 'mark void' or credit-note)"),
61 ("template list", "List available PDF templates"),
62 ("template preview <name>", "Render a template with synthetic data"),
63 ("config show | path | set <key> <value>", "View / edit config"),
64 ("agent-info | info", "This manifest"),
65 ("doctor", "Diagnose dependencies and config"),
66 ("skill install", "Install embedded Claude/Codex/Gemini skill"),
67 ("update [--check]", "Self-update — queries crates.io, upgrades via brew/cargo"),
68 ];
69 let mut commands = serde_json::Map::new();
70 for (k, v) in commands_list {
71 commands.insert(
72 (*k).to_string(),
73 serde_json::Value::String((*v).to_string()),
74 );
75 }
76
77 let manifest = serde_json::json!({
78 "name": "invoice",
79 "version": env!("CARGO_PKG_VERSION"),
80 "description": env!("CARGO_PKG_DESCRIPTION"),
81 "commands": commands,
82 "flags": {
83 "--json": "Force JSON envelope output (auto-enabled when piped)",
84 "--quiet": "Suppress human output"
85 },
86 "exit_codes": {
87 "0": "Success",
88 "1": "Transient error (IO, render) — retry may help",
89 "2": "Config error — fix setup",
90 "3": "Bad input / not found / ambiguous — fix arguments",
91 "4": "Rate limited — wait and retry"
92 },
93 "envelope_schema": {
94 "version": "1",
95 "status": "success | error",
96 "data": "… (success)",
97 "error": "{ code, message, suggestion } (error)"
98 },
99 "config_path": config_path,
100 "state_dir": state_dir,
101 "database": database,
102 "templates": ["helvetica-nera", "tiefletter-gold", "monoline", "vienna", "boutique"],
103 "tax_profiles": profiles,
104 "item_spec": "product-slug[:qty] OR description:qty:price[:rate]",
105 "config_keys": {
106 "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.",
107 "self_update": "Reserved shared setting for updater behavior across the suite."
108 },
109 "first_run": [
110 "invoice doctor --json",
111 "invoice issuer add <slug> --name <display-name> --jurisdiction sg|uk|us|eu|custom --address \"line1\\nline2\"",
112 "invoice config set default_issuer <issuer-slug>",
113 "invoice clients add <slug> --name <client-name> --address \"line1\\nline2\" --default-issuer <issuer-slug>",
114 "invoice invoices new --client <client-slug> --item \"Description:1:100:20\""
115 ],
116 "examples": [
117 {
118 "goal": "Create an invoice using client/default issuer routing",
119 "command": "invoice invoices new --client meridian --item \"Consulting:1:1200:20\" --json"
120 },
121 {
122 "goal": "Create an invoice for a specific company",
123 "command": "invoice invoices new --as paperfoot --client meridian --item design:1 --json"
124 },
125 {
126 "goal": "Render an invoice PDF",
127 "command": "invoice invoices render paperfoot-2026-0001 --json"
128 },
129 {
130 "goal": "Issue a full credit note",
131 "command": "invoice invoices credit-note paperfoot-2026-0001 --full --json"
132 }
133 ],
134 "multi_company_model": {
135 "issuer": "The company/person billing as. Each issuer owns tax profile, logo, bank details, default notes/output dir, and its own sequence counter.",
136 "client": "The customer. A client may pin default_issuer and default_template.",
137 "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.",
138 "recommended_default": "Set config.default_issuer for single-company workflows; use client.default_issuer or explicit --as for multi-company workflows."
139 },
140 "guardrails": [
141 "Run doctor before first use and after changing config.",
142 "Prefer --json for agents; stdout is data and stderr is diagnostics.",
143 "Use globally unique issuer number formats, ideally {issuer}-{year}-{seq:04}.",
144 "Do not delete issued invoices; mark void or create a credit note.",
145 "Use invoice numbers returned by JSON responses rather than guessing the next sequence.",
146 "Pin client default issuers when the same customer is always billed by the same company."
147 ]
148 });
149
150 print_raw(&manifest);
151 Ok(())
152}