Skip to main content

Command

Enum Command 

Source
pub enum Command {
Show 15 variants Init { key_backup: PathBuf, repair: bool, }, Import { files: Vec<PathBuf>, }, Verify, Report { year: Option<i32>, tax_year: Option<i32>, prior_taxable_gifts: Option<String>, write_carryover: bool, force: bool, }, Events(Events), Limitations, Reconcile(Reconcile), Config { set_fee_treatment: Option<FeeArg>, set_pre2025_method: Option<MethodLotArg>, attest_pre2025_method: bool, set_forward_method: Option<MethodLotArg>, exchange: Option<String>, effective_from: Option<String>, }, ExportSnapshot { out: PathBuf, tax_year: Option<i32>, attest: Option<String>, }, ExportIrsPdf { out: PathBuf, tax_year: i32, forms: Vec<FormArg>, attest: Option<String>, }, BackupKey { out: PathBuf, }, Optimize(Optimize), WhatIf(WhatIf), Income(IncomeCmd), TaxProfile {
Show 13 fields year: i32, filing_status: Option<FilingStatusArg>, ordinary_taxable_income: Option<String>, magi_excluding_crypto: Option<String>, qualified_dividends: Option<String>, other_net_capital_gain: Option<String>, carryforward_short: Option<String>, carryforward_long: Option<String>, w2_ss_wages: Option<String>, w2_medicare_wages: Option<String>, schedule_c_expenses: Option<String>, show: bool, force: bool,
},
}

Variants§

§

Init

Create the encrypted vault + force a key backup.

Fields

§key_backup: PathBuf

File to write the forced key backup to: an ASCII-armored, passphrase(S2K)-encrypted private key, owner-only (mode 0600). Identical format to backup-key --out. Store it offline — it is the only way to recover the vault if you lose vault.key.

FORMAT (structure — NOT a real key): —–BEGIN PGP PRIVATE KEY BLOCK—– … base64 armor of the S2K-encrypted secret key … —–END PGP PRIVATE KEY BLOCK—–

§repair: bool

Clear an interrupted/half-created init (orphan vault.key, no encrypted store) and start fresh.

§

Import

Import one or more export files (auto-groups Swan).

Fields

§files: Vec<PathBuf>
§

Verify

FR9 integrity check (non-zero exit on hard blockers).

§

Report

Show holdings + realized disposals/removals/income. With –tax-year: standalone TaxResult.

Exit code with –tax-year (UX-P4-10): 0 = a filing-ready number was rendered; 1 = ran but the year is NOT COMPUTABLE (no filing-ready number — a missing tax profile/table or a hard blocker; mirrors verify); 2 = the command failed (any error). Scripts should key on NON-ZERO. A pseudo-active report still exits 0 — the on-screen banner is the signal, not the exit code.

Fields

§year: Option<i32>

Filter realized disposals/removals/income to a specific calendar year (display path).

§tax_year: Option<i32>

Compute the crypto-attributable federal tax for the given tax year (B.5 / Task 9). Requires a stored tax profile (tax-profile --year Y ...) and the bundled TY table. Independent of –year; the two flags are not aliased.

§prior_taxable_gifts: Option<String>

Cumulative prior-year TAXABLE gifts (post-annual-exclusion Form 709 amounts), not gross gifts. Used for the §2505 lifetime-exclusion consumption advisory. Defaults to $0 when omitted (the advisory discloses this assumption). Must not be negative.

§write_carryover: bool

§4 R3-M6: persist this year’s computed charitable + QBI carryover-OUT as next year’s carryover-IN (a full-return --tax-year ReturnInputs year only). report is otherwise read-only; this flag opts into the vault write.

§force: bool

With --write-carryover: overwrite a next-year carryover-in that was user-entered (income import). Without it, a user-entered value is left untouched and the write refuses.

§

Events(Events)

Discover the reconciliation event references (refs) you pass to the reconcile verbs.

§

Limitations

Print the LIMITATIONS & supported-forms document: what a v1 full return covers, the credits it omits conservatively (your tax is overstated, never understated), what it refuses outright, and what it cannot represent. Read this before you file.

§

Reconcile(Reconcile)

Emit a reconciliation decision event.

§

Config

Show or set projection config (TP8 fee treatment / pre-2025 lot method / forward method).

Fields

§set_fee_treatment: Option<FeeArg>
§set_pre2025_method: Option<MethodLotArg>
§attest_pre2025_method: bool
§set_forward_method: Option<MethodLotArg>

§A.5(a): append a MethodElection decision (the forward standing order). Not a flag mutation — this is an event in the ledger. Use –effective-from to set the date (default: today / the decision’s made-date).

§exchange: Option<String>

§A.5(a) per-ACCOUNT scope for –set-forward-method (IRS 2025+ per-account rule): exchange:PROVIDER:ACCOUNT (the canonical wallet grammar). Omit for a GLOBAL election (the existing behavior). Only exchange accounts are electable (a method election is a brokerage-account concept; self:LABEL is rejected). The account MUST already exist in the vault — an unknown/typo’d account is rejected LOUDLY so it can’t create a dead election.

§effective_from: Option<String>

Effective-from date for –set-forward-method (YYYY-MM-DD). Defaults to made-date.

§

ExportSnapshot

FR10: export decrypted SQLite + CSV (the NFR2 plaintext exception).

WARNS (does not refuse) on unresolved Hard blockers: any Hard blocker makes every affected tax year NOT COMPUTABLE, so the exported Form 8949 / Schedule D / figures are INFORMATIONAL, not final. A warning is printed to stderr and the export still succeeds (exit 0). Automation that must GATE on unresolved blockers should check btctax verify (which exits non-zero), since export-snapshot itself stays exit 0.

Fields

§out: PathBuf

Output DIRECTORY receiving the decrypted SQLite DB (snapshot.sqlite) + projection CSVs (the NFR2 plaintext exception; created owner-only). ALWAYS writes: lots.csv, disposals.csv, removals.csv, income.csv. With –tax-year it ALSO writes form8949.csv, schedule_d.csv, form8283.csv, and schedule_se.csv (schedule_se only when there is business self-employment income). The event column in disposals.csv / removals.csv / income.csv is the event-ref that reconcile commands consume (select-lots, set-donation-details, reclassify-income, …).

FORMAT (removals.csv header + one sample donation row): event,kind,removed_at,lot,sat,basis,fmv_at_transfer,term,acquired_at,claimed_deduction,donee import|coinbase|X,donation,2025-03-01,import|coinbase|X#0,25000,120.00,150.00,long,2023-01-05,150.00,Charity Y

§tax_year: Option<i32>

Also emit the per-tax-year Form 8949 + Schedule D CSVs (form8949.csv / schedule_d.csv), scoped to this calendar year. Omit to write only the all-years projection CSVs.

§attest: Option<String>

Attestation phrase required to export while the ledger is PSEUDO-RECONCILED (a synthetic default contributes to the projection). Pass the exact phrase I attest this is true (trimmed, case-sensitive) to export the fictional draft ON PURPOSE. Omit on a fully-real ledger (never gated). Omit on an interactive terminal to be prompted; omit when piped (non-TTY) while pseudo-active and the export is refused.

§

ExportIrsPdf

Fill the OFFICIAL IRS fillable PDFs for a tax year (a whole packet).

Writes (owner-only) into –out, populated from btctax’s already-computed projection — no capital-gains figure is recomputed:

  • f8949.pdf + schedule_d.pdf — ALWAYS. On the 2025 (1099-DA) revision Bitcoin is filed under Box I (short-term) / Box L (long-term) — the digital-asset boxes; on the pre-1099-DA 2024 and 2017 revisions it is Box C / Box F (“not reported on a 1099-B”). Never the wrong pair for the year. More rows than a part’s grid holds (11 in 2025, 14 in 2024/2017) paginate onto multiple copies, each with its own totals.
  • schedule_se.pdf — when there is business self-employment income and net earnings are ≥ the $400 floor. Line 12 (SE tax) = Social Security + regular Medicare ONLY; the 0.9% Additional Medicare Tax is a Form 8959 item (flagged on stderr, not put on Schedule SE). Requires a stored tax-profile for the year (filing status); missing profile ⇒ a NOTE, not a form.
  • form_8283.pdf — when there are BTC donations. Fills the donee/appraiser IDENTITY + per- donation property rows (Section A ≤ $5,000 or Section B > $5,000). The property-type box is “k Digital assets” on the Rev. 12-2023/2025 forms (2024/2025); the 2017 Rev. 12-2014 form has no such box, so BTC uses “j Other” + a printed note. Leaves every OTHER party’s declaration/signature BLANK — a Section B 8283 is NOT filing-ready without those signed. Overflows onto additional copies.
  • form_1040_capgains.pdf — when there is reportable capital/digital-asset activity. Fills the capital-gain line (line 7a in 2025 / line 7 in 2024 / line 13 in 2017, when Schedule D is active and line 16 ≥ 0; active-and-zero → “-0-”; a net loss leaves it blank — the §1211 line-21 cap is yours) and, on 2024/2025, the Digital-Asset question (YES iff any disposal, income, gift, or donation; never a “No”). The 2017 form has no Digital-Asset question, so an income-only 2017 year produces no 1040. 7b checkboxes are untouched.

Every written value is read back GEOMETRICALLY against the blank PDF’s own field coordinates and the fill FAILS CLOSED on any mis-placement — a wrong tax form is never written. The engine drops the forms’ XFA layer (else Acrobat opens them blank) and sets NeedAppearances so a viewer regenerates the visible values. Schedule D lines 17-22 (28%-rate / unrecaptured-§1250 / QDI worksheet, incl. the line-21 loss limit) are OUT OF SCOPE. Rows on an exchange that MAY carry broker reporting (a 1099-DA from TY2025; a 1099-B before) are flagged on stderr — btctax files every row under the year’s not-reported box (I/L from TY2025, C/F before) and says so.

A tax year that has FULL-RETURN inputs (income import) DISPATCHES to the complete return packet — the 1040 and every schedule/attachment it cites, in Attachment-Sequence order, plus a manifest (--forms is ignored on that path). A crypto-only year (no income import) instead fills the crypto SLICE: Schedule D carries only the ledger’s crypto totals — it has no line 13 (1099-DIV box-2a capital-gain distributions) and no lines 6/14 (capital-loss carryovers), and the 1040 fill covers only the capital-gain cluster. For a crypto-only year those slice forms are complete and correct; a full-return year needs the full packet, which is why the two paths are dispatched separately (and write non-overlapping filenames). See btctax limitations.

PSEUDO-RECONCILED ledgers: the same attestation gate as export-snapshot applies, AND every page is stamped with a diagonal DRAFT — ESTIMATE, NOT FOR FILING watermark.

Fields

§out: PathBuf

Output DIRECTORY receiving the filled official PDFs (created owner-only): f8949.pdf, schedule_d.pdf, and — when applicable — schedule_se.pdf, form_8283.pdf, form_1040_capgains.pdf. These contain your unencrypted tax data — write –out OUTSIDE any git repo.

§tax_year: i32

The tax year to fill (this build bundles TY2017, TY2024 and TY2025; other years are refused). TY2024/TY2017 are pre-1099-DA revisions: Bitcoin is filed under Box C/F (not Box I/L). TY2017 additionally uses the OLD forms — the §B long Schedule SE, Form 8283 Rev. 12-2014 (“j Other”, no digital-asset box), the 1040 capital gain on line 13, and NO Digital-Asset question.

§forms: Vec<FormArg>

Restrict the crypto-slice packet to specific forms (repeat or comma-separate). Values: f8949, schedule-d, schedule-se, form8283, form1040, form8275. Default = every applicable form (f8949 + schedule-d always; schedule-se when SE income ≥ the $400 floor; form8283 when there are donations; form1040 when there is reportable digital-asset activity; form8275 when a promoted-basis disposal leg files in the year — Approach-B’s Cohan-estimate disclosure). A named form is still skipped when it does not apply. Ignored on a full-return year (that path fills the whole packet).

§attest: Option<String>

Attestation phrase required to export while the ledger is PSEUDO-RECONCILED (a synthetic default contributes to the projection). Pass the exact phrase I attest this is true (trimmed, case-sensitive) to fill the DRAFT-watermarked forms ON PURPOSE. Omit on a fully-real ledger (never gated). Omit on an interactive terminal to be prompted; omit when piped (non-TTY) while pseudo-active and the export is refused.

§

BackupKey

Export the passphrase-protected key.

Fields

§out: PathBuf

File to write the exported key to: an ASCII-armored, passphrase(S2K)-encrypted private key, owner-only (mode 0600). Identical format to init --key-backup.

FORMAT (structure — NOT a real key): —–BEGIN PGP PRIVATE KEY BLOCK—– … base64 armor of the S2K-encrypted secret key … —–END PGP PRIVATE KEY BLOCK—–

§

Optimize(Optimize)

Lot-specific-identification optimizer (§C — read-only proposal or gated persistence).

§

WhatIf(WhatIf)

Read-only what-if tax planning (task #43): posit a HYPOTHETICAL, NON-persisted transaction and see its MARGINAL federal-tax effect on the current-year position. Routes through the same audited tax engine as report --tax-year; invents no tax authority. Writes NOTHING — no event, no side-table row, no vault mutation. Tax decision-support (consequences), not buy/sell/hold advice.

§

Income(IncomeCmd)

Full-return (v1) input surface: import, show, or clear the per-year full-return inputs (W-2s, 1099s, deductions, household). Offline; stored in the encrypted vault.

§

TaxProfile

Set or show the per-tax-year tax profile (filing status, income, MAGI, etc.).

Fields

§year: i32

The tax year (e.g. 2025).

§filing_status: Option<FilingStatusArg>

IRS filing status.

§ordinary_taxable_income: Option<String>

Ordinary taxable income EXCLUDING all app-computed crypto items (net ST gains, mining/staking ordinary income). The engine adds the crypto items on top (B.1 / I5).

§magi_excluding_crypto: Option<String>

Modified AGI excluding crypto items, for the §1411 NIIT threshold comparison.

IMPORTANT (§1411 contract): this value MUST already include the taxpayer’s qualified dividends and non-crypto net capital gains (and any other MAGI add-backs from §1411(d)). The engine adds ONLY the crypto AGI delta on top (ambiguity #5 in the design). Omitting QD or non-crypto cap gains from this figure understates NIIT.

§qualified_dividends: Option<String>

Qualified dividends + other preferential-rate income sharing the §1(h) 0/15/20 LTCG rate stack. Required when setting a profile.

§other_net_capital_gain: Option<String>

Non-crypto net LT-character capital gain already in the profile (optional; defaults to 0 when omitted).

§carryforward_short: Option<String>

§1212(b) short-term capital loss carryforward into this year (optional; defaults to 0).

§carryforward_long: Option<String>

§1212(b) long-term capital loss carryforward into this year (optional; defaults to 0).

§w2_ss_wages: Option<String>

Form W-2 Social Security wages (Box 3 + Box 7 tips; Schedule SE line 8a). Reduces the §1401(a) SS cap: ss_cap = max(0, wage_base − w2_ss_wages). Optional; defaults to $0. Must not be negative.

§w2_medicare_wages: Option<String>

Medicare wages (Box 5; Form 8959 line 1). Reduces the Additional-Medicare threshold: addl_threshold = max(0, threshold − w2_medicare_wages) (§1401(b)(2)(B)/Form 8959 Part II). Optional; defaults to $0. Must not be negative.

§schedule_c_expenses: Option<String>

Schedule C deductible business expenses for the year — reduces net SE earnings; the income-tax stack above is NOT adjusted (see the advisory). Optional; defaults to $0. Must not be negative.

§show: bool

Show the stored profile for --year instead of setting it.

§force: bool

Store the raw profile even when full-return inputs (income import) already exist for the year (they take precedence, so the raw profile would otherwise be ignored — D-4 guard).

Trait Implementations§

Source§

impl FromArgMatches for Command

Source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from ArgMatches, parsing the arguments as needed. Read more
Source§

fn from_arg_matches_mut( __clap_arg_matches: &mut ArgMatches, ) -> Result<Self, Error>

Instantiate Self from ArgMatches, parsing the arguments as needed. Read more
Source§

fn update_from_arg_matches( &mut self, __clap_arg_matches: &ArgMatches, ) -> Result<(), Error>

Assign values from ArgMatches to self.
Source§

fn update_from_arg_matches_mut<'b>( &mut self, __clap_arg_matches: &mut ArgMatches, ) -> Result<(), Error>

Assign values from ArgMatches to self.
Source§

impl Subcommand for Command

Source§

fn augment_subcommands<'b>(__clap_app: Command) -> Command

Append to Command so it can instantiate Self via FromArgMatches::from_arg_matches_mut Read more
Source§

fn augment_subcommands_for_update<'b>(__clap_app: Command) -> Command

Append to Command so it can instantiate self via FromArgMatches::update_from_arg_matches_mut Read more
Source§

fn has_subcommand(__clap_name: &str) -> bool

Test whether Self can parse a specific subcommand

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V