#!/bin/bash # desktop-assistant tool: personal finance from Firefly III (read-only). # Copyright (C) 2026 Danilo M. # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License version 2 as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # finance.sh WHAT [SINCE] [ACCOUNT]: WHAT is balances (active asset # accounts), budgets (this month, spent against the limit) or spending # (withdrawals from SINCE, YYYY-MM-DD, default the first of this month, # optionally only from ACCOUNT: total and the 10 largest). Only firefly's list commands run here: nothing is recorded # or edited. set -uo pipefail ff() { firefly "$@" || { echo "firefly $1 $2 failed" >&2; exit 1; }; } case "${1:-}" in balances) ff account list --type asset | jq -r '.[] | select(.active) | "\(.name): \(.current_balance | tonumber * 100 | round / 100) \(.currency_code)"' ;; budgets) ff budget list | jq -r '.[] | select(.active) | "\(.name): spent \([.spent[].sum | tonumber] | add // 0 | fabs * 100 | round / 100)" + (if .auto_budget_amount then " of \(.auto_budget_amount | tonumber)" else "" end) + " \(.currency_code // "")"' ;; spending) since="${2:-$(date +%Y-%m-01)}" date -d "$since" >/dev/null 2>&1 || { echo "since must be YYYY-MM-DD" >&2; exit 1; } account=() if [ -n "${3:-}" ]; then # firefly's own "no such account" lists every expense account too; # the model only needs the user's own accounts to retry. names=$(ff account list --type asset | jq -r '.[] | select(.active) | .name') grep -qixF -- "$3" <<<"$names" \ || { echo "no account named $3; accounts: $(paste -sd, <<<"$names")" >&2; exit 1; } # The per-account listing needs an end date, or the API rejects it. account=(--account "$3" --until "$(date +%F)") fi json=$(firefly tx list --since "$since" "${account[@]}" --all --flat) || exit 1 jq -r --arg since "$since" --arg acct "${3:-all accounts}" ' [.[] | select(.type == "withdrawal") | .amount |= tonumber] | "spent from \($acct) since \($since): \(map(.amount) | add // 0 | . * 100 | round / 100) in \(length) payments", (sort_by(-.amount)[:10][] | "\(.date[:10]) \(.amount * 100 | round / 100) \(.destination_name): \(.description)")' <<<"$json" ;; *) echo "what must be balances, budgets or spending" >&2 exit 1 ;; esac