Skill: benbjurstrom/fiscal (version 0.1.3)

--- name: fiscal description: >- Act as a personal accountant using the fscl (fiscal) CLI for Actual Budget. Use when the user wants help with personal finances, budgeting, spending, bills, subscriptions, bank imports, or managing accounts and categories. --- # Fiscal Personal Accountant This skill helps you perform the duties of a personal accountant using the `fscl` binary — a headless command line interface for [Actual Budget](https://actualbudget.org/). It will teach you how to handle budgeting, bank imports, transaction categorization, rules automation, and spending analysis. The user should never need to learn Actual Budget or CLI commands. ## How It Works Talk to the user about their finances in plain language. Translate their intent into `fscl` commands and present results as human-readable summaries. Look up entity IDs automatically, convert raw amounts from cents to dollars, and confirm financial decisions before executing. **Key conventions:** - Always pass `--json` to fscl commands. Present output as tables, bullets, or summaries — never raw JSON. - Amounts: CLI outputs cents (integers), display as currency (`-4599` → **-$45.99**). CLI input uses decimals (`--amount 45.99`). - Dates: `YYYY-MM-DD` for dates, `YYYY-MM` for months. - IDs: Fetch with `find` or `list`, reuse all session. Never show UUIDs to the user — use names. - Accounts: Confirm account type (`checking`, `savings`, `credit card`, etc.) before creating or importing transactions into an account. - Account names: Include institution + account type (+ last4/nickname when available), for example `Chase Checking 5736` or `AmEx Credit 1008`. - Categories model: category groups and categories are separate entities. Categories belong to groups; categories do not nest under categories. - Draft pattern: Always run `<command> draft` first to generate the draft file, then edit that generated file, then run `<command> apply`. Never hand-create draft JSON files in `drafts/` by path. Used for categories, categorize, edit, rules, month budgets, templates. - Read commands (list, show, status) don't sync. Write commands auto-sync when a server is configured. - If a command returns `{ code: "not-logged-in" }`, ask for the server password, run `fscl login [server-url] --password <pw>`, then retry the original command. ## How to Help Users With Their Budgets Run at the start of every session to understand the budget state: ```bash fscl status --json ``` If the command fails with "No config found," fscl hasn't been initialized. Ask whether to create a new local budget or connect to an existing Actual Budget server, then run `fscl init`. See [references/commands.md](references/commands.md) for init modes. If status returns `budget.loaded = false` with a `budget.load_error`, the budget exists but can't be opened. Report the error to the user and help troubleshoot (common causes: missing data directory, corrupted budget file, wrong budget ID in config). Otherwise, use the status metrics to determine which workflow to load. The key fields are `metrics.accounts.total`, `metrics.rules.total`, `metrics.transactions.total`, `metrics.transactions.uncategorized`, and `metrics.transactions.unreconciled`. ### Path 1: Empty Budget → Onboarding No accounts exist yet. The budget was just created and needs full setup. → **[references/workflow-onboarding.md](references/workflow-onboarding.md)** ### Path 2: Needs Triage → Optimization Accounts and transactions exist but the budget isn't well-automated. Signs: few or no rules, a high ratio of uncategorized to total transactions, or many unreconciled transactions piling up. This typically means the user connected fscl to an existing Actual Budget and hasn't set up automation yet. → **[references/workflow-optimization.md](references/workflow-optimization.md)** ### Path 3: Healthy Budget → Day-to-Day The budget has rules doing their job, the uncategorized ratio is low, and unreconciled transactions aren't piling up. The user is in maintenance mode — help with whatever they need. → **[references/workflow-maintenance.md](references/workflow-maintenance.md)** If the path isn't obvious, ask: "Is this a brand new budget, or have you been using Actual Budget already?" The user may arrive with a specific question regardless of budget state. Always answer their immediate question first. Offer workflow guidance proactively ("I noticed you have 30 uncategorized transactions — want me to help clean those up?") but don't force it. ## Reference Files **Workflows:** - [references/workflow-onboarding.md](references/workflow-onboarding.md) — New budget setup (Path 1) - [references/workflow-optimization.md](references/workflow-optimization.md) — Existing budget audit & automation (Path 2) - [references/workflow-maintenance.md](references/workflow-maintenance.md) — Monthly cycle & day-to-day (Path 3) **Commands:** - [references/commands.md](references/commands.md) — Common patterns, recipes, and conventions - [references/command-reference.md](references/command-reference.md) — Every command with flags and output columns **Guides:** - [references/budgeting.md](references/budgeting.md) — Category templates, envelope budgeting, income, overspending, joint accounts - [references/import-guide.md](references/import-guide.md) — File import formats, CSV column mapping - [references/rules.md](references/rules.md) — Rule JSON schema, conditions, actions - [references/credit-cards.md](references/credit-cards.md) — Credit card strategies and debt tracking - [references/query-library.md](references/query-library.md) — Pre-built AQL queries for reports

path: references/budgeting.md
content: # Budgeting Concepts

Envelope budgeting mechanics, category strategy, and special scenarios for Actual Budget.

## Category Templates

Use these as starting points. Adapt based on what the user tells you about their life. Don't create categories they won't use — you can always add more later.

### Single person, renting

```
Housing:          Rent, Utilities, Internet, Renter's Insurance
Food:             Groceries, Dining Out, Coffee
Transportation:   Gas/Transit, Car Insurance, Car Maintenance, Parking
Personal:         Clothing, Personal Care, Health/Medical, Subscriptions
Lifestyle:        Entertainment, Hobbies, Gifts
Financial:        Savings, Emergency Fund, Investments
Income:           Salary, Side Income
```

### Single person, homeowner

Add to the above:
```
Housing:          Mortgage, Property Tax, HOA, Home Insurance, Home Maintenance
```
Remove: Rent, Renter's Insurance

### Couple, shared budget

```
Housing:          Rent/Mortgage, Utilities, Internet, Insurance
Shared Living:    Groceries, Household Supplies, Dining Out
Transportation:   Gas/Transit, Car Insurance, Maintenance
Kids (if any):    Childcare, School, Activities, Kids Clothing
Personal - [Name A]: Clothing, Personal Care, Hobbies, Fun Money
Personal - [Name B]: Clothing, Personal Care, Hobbies, Fun Money
Financial:        Emergency Fund, Savings Goals, Investments
Debt (if any):    [Card Name] Debt
Income:           [Name A] Income, [Name B] Income, Partner Contributions
```

### Freelancer / variable income

Add:
```
Business:         Equipment, Software, Professional Services, Business Travel
Taxes:            Estimated Taxes, Tax Prep
```

Key difference: Budget conservatively using the lowest expected monthly income. In good months, put extra toward savings or taxes.

## Category Management

### Category groups

Categories are organized into groups.

```bash
# Create a group
fscl categories create-group "Fixed Expenses"

# Create categories in the group
fscl categories create "Rent" --group <group-id>
fscl categories create "Utilities" --group <group-id>
fscl categories create "Internet" --group <group-id>
```

### Merging categories

If you have duplicate or redundant categories, delete one and transfer its transactions:

```bash
# Move all "Foods" transactions to "Food", then delete "Foods"
fscl categories delete <foods-id> --transfer-to <food-id> --yes
```

### Income categories

Create income categories in the income group:

```bash
fscl categories create "Salary" --group <income-group-id> --income
fscl categories create "Freelance" --group <income-group-id> --income
```

## Month Setup

The fastest way to set up a new month is to copy from the previous month:

```bash
fscl month copy 2026-01 2026-02
```

Alternatively, use `month draft` + `month apply` for selective editing, or templates for automated budgeting.

## Goal Templates

Use category-level templates to automate month setup.

```bash
# Generate editable template draft
fscl month templates draft
# Edit templates.json (add/update template arrays)
fscl month templates apply --dry-run
fscl month templates apply

# Validate + apply for the month
fscl month templates check
fscl month templates run 2026-03
```

## Income Handling

When you receive income:

- It becomes immediately available to budget (appears in "Available Funds" / "To Budget")
- If you don't budget it this month, it rolls over to next month
- Common strategy: "hold" current month's income for next month's budget, so you're always budgeting with last month's income

Using fscl, income shows up as a positive-amount transaction categorized to an income category.

## Overspending

When you overspend in a category (balance goes negative):

- **Default behavior:** The negative balance is automatically deducted from next month's "To Budget" amount, and the category balance resets to zero
- This means overspending reduces your ability to budget next month
- To handle it: move money from another category to cover the overspent amount, or accept that next month will have less to budget

### Rollover overspending (carryover)

Sometimes you want to keep a negative balance across months (e.g., tracking reimbursable expenses). Enable per-category:

```bash
fscl month set-carryover 2026-02 <category-id> true
```

When rollover is enabled, the negative balance stays in the category instead of being deducted from "To Budget."

## Returns and Reimbursements

### Returns

A return is not income — it goes back to the category you originally spent from. Enter the return as a positive-amount transaction with the same category:

```bash
fscl transactions add <acct-id> --date 2026-02-10 --amount 32.99 \
  --payee "Amazon" --category <clothing-cat-id> --notes "Sandals return"
```

This restores $32.99 to the Clothing category balance.

### Reimbursements

For reimbursable expenses (business travel, shared costs):

1. Create a dedicated category (e.g., "Business Expenses")
2. **Option A: Pre-fund** — Budget money into the category before spending. True zero-budget approach.
3. **Option B: Carry negative** — Let the category go negative, enable rollover overspending, and refill when reimbursed.

```bash
# Create category and enable carryover
fscl categories create "Business Expenses" --group <group-id>
fscl month set-carryover 2026-02 <biz-exp-cat-id> true

# Spend (category goes negative if not pre-funded)
fscl transactions add <acct-id> --date 2026-02-05 --amount -150.00 \
  --payee "Hotel" --category <biz-exp-cat-id> --notes "Client trip"

# Receive reimbursement (positive amount, same category)
fscl transactions add <acct-id> --date 2026-02-20 --amount 150.00 \
  --payee "Employer" --category <biz-exp-cat-id> --notes "Trip reimbursement"
```

## Joint Accounts

### Shared budget (recommended for committed couples)

Both partners use the same budget file. Sync via an Actual Budget server so both can access it.

Setup:
1. Create a joint checking account
2. Use the shared budget on a synced server
3. Create a "Partner Personal Spending" category (with rollover) for tracking personal purchases made on the shared account
4. Track income contributions via a "Partners Contributions" income category

### Personal budget with shared account

Track your partner's contributions in your own budget:

1. Create the joint account on-budget
2. Your transfers to the joint account don't need a category (on-budget to on-budget transfer)
3. Your partner's deposits are categorized as income (use a "Partner Contribution" income category)
4. Budget the full bill amounts in shared expense categories

### Contribution strategy

For proportional contributions based on income:
- If Partner A earns $4,000/month and Partner B earns $6,000/month
- Total = $10,000. Partner A contributes 40%, Partner B contributes 60%
- If shared expenses are $3,000/month: A pays $1,200, B pays $1,800


path: references/command-reference.md
content: # Command Reference

Every fscl command with flags and output columns.

**Entity resolution:** Commands that accept account, category, or category group IDs also accept names. The CLI resolves names to IDs using exact case-insensitive matching. Example: `fscl month set 2026-02 "Groceries" 500.00`.

## Global Options

| Flag | Description |
|---|---|
| `--data-dir <path>` | Actual data directory |
| `--budget <id>` | Active budget ID |
| `--server-url <url>` | Actual server URL |
| `--json` | JSON output |
| `--columns <cols>` | Comma-separated column filter |

### Connection and auth resolution

`server_url` is resolved in order (first wins):

1. CLI flag (`--server-url`)
2. Environment variable (`FISCAL_SERVER_URL`)
3. Config file (`~/.config/fiscal/config.json`)

Authentication uses a session token saved by `fscl login` (`config.token`). If a server is configured and no valid token is available, commands return:

```json
{ "status": "err", "message": "Not logged in. Run 'fscl login' to authenticate.", "code": "not-logged-in" }
```

### Output format

Default: human-readable tables. `--json` for machine-readable output. Agents should always use `--json`.

Amounts in output: **integer cents** (`-4599` = -$45.99). Amounts in input: **decimal notation** (`--amount -45.99`).

**Row responses** (list, query, report commands):

```json
{ "status": "ok", "entity": "accounts", "count": 2, "data": [...] }
```

**Mutation responses** (create, update, delete — always JSON regardless of `--json` flag):

```json
{ "status": "ok", "entity": "account", "action": "create", "id": "a1b2c3" }
```

**Errors:**

```json
{ "status": "err", "message": "No budget selected." }
```

Validation errors include an `errors` array with `path` and `message` per item.

| Field | Description |
|---|---|
| `status` | `ok` or `err` |
| `entity` | Entity context (e.g. `transactions`, `import`) |
| `count` | Row count or error count |
| `data` | Array of rows |
| `action` | Mutation action (`create`, `update`, `delete`, etc.) |
| `message` | Human-readable summary or error message |
| `errors` | Detail array for parse/validation failures |

## init

`fscl init [--non-interactive --mode <local|remote>]`

| Flag | Description |
|---|---|
| `--non-interactive` | Disable prompts |
| `--mode <mode>` | `local`, `remote` |
| `--budget-name <name>` | Budget name (local) |
| `--password <pw>` | Server password (remote) |
| `--sync-id <id>` | Remote sync ID (remote) |

Modes: `local` (disk-only), `remote` (pull from server). Use `budgets push` to upload a local budget. On success, prints `fscl status` output.

When `init` is launched through `npx fscl init`, interactive mode asks whether to install fscl globally (`npm install -g fscl`) so the `fscl` command is available afterward.

Interactive `fscl init` also asks whether to install the Fiscal agent skill by running `npx skills add fiscal-sh/fscl`.

## status

`fscl status [--compact]`

Shows: budget name/ID, server connectivity, entity counts, uncategorized count, transaction date range.

## sync

`fscl sync`

Write commands auto-sync when a server is configured. Use for explicit manual sync.

## login

`fscl login [server-url] [--password <pw>]`

Authenticates with the Actual server and stores `serverURL` + `token` in config. If `--password` is omitted in a TTY, fscl prompts securely.

Server URL resolution:
1. Positional arg (`[server-url]`)
2. `FISCAL_SERVER_URL`
3. Config `serverURL`

## logout

`fscl logout`

Clears stored `serverURL` and `token`.

## budgets

| Command | Description |
|---|---|
| `budgets list` | Columns: `id`, `name`, `group_id`, `cloud_file_id`, `state` (one row per logical budget; `state` is `local`, `linked`, or `remote`) |
| `budgets create [name]` | Create a new budget (interactive if name omitted) |
| `budgets use <id>` | Set active budget |
| `budgets delete <id>` | Delete local budget copy (`--yes` required) |
| `budgets pull <syncId>` | Download remote budget, set active |
| `budgets push` | Upload local budget to server |

`budgets create` options:

| Flag | Description |
|---|---|
| `--non-interactive` | Run without prompts |
| `--mode <mode>` | `local`, `remote` |
| `--budget-name <name>` | Budget name (alternative to positional arg) |
| `--password <pw>` | Server password for `remote` mode |
| `--sync-id <id>` | Remote sync ID for `remote` |

Remote operations require a valid login token (`fscl login`).

## accounts

| Command | Key Flags |
|---|---|
| `accounts list` | |
| `accounts find <names...>` | Case-insensitive substring, OR-matched |
| `accounts create <name>` | `--offbudget`, `--balance <decimal>` |
| `accounts create-batch <json>` | Array of `{name, offbudget?, balance?}` |
| `accounts update <id>` | `--name`, `--offbudget` / `--no-offbudget` |
| `accounts close <id>` | `--transfer-to <acctId>`, `--transfer-category <catId>` |
| `accounts reopen <id>` | |
| `accounts delete <id>` | `--yes` (required) |
| `accounts balance <id>` | `--cutoff <YYYY-MM-DD>` |

Output columns: `id`, `name`, `offbudget`, `closed`, `balance_current`

## transactions

| Command | Key Flags |
|---|---|
| `transactions list <acctId>` | `--start`, `--end` (both required) |
| `transactions uncategorized` | `--account <id>`, `--start`, `--end` |
| `transactions add <acctId>` | `--date`, `--amount` (required); `--payee`, `--category`, `--notes`, `--cleared` |
| `transactions delete <id>` | `--yes` (required) |
| `transactions import <acctId> <file>` | See [Import](#importing-transactions) below |

Output columns: `id`, `date`, `account`, `account_name`, `amount`, `payee`, `payee_name`, `category`, `category_name`, `notes`, `cleared`, `reconciled`, `transfer_id`, `imported_id`

### categorize draft/apply

`fscl transactions categorize draft [--account <id>] [--start] [--end] [--limit <n>]`
`fscl transactions categorize apply [--dry-run]`

Draft shape: `[{ id, category, _meta: { date, amount, payeeName, accountName, notes } }]`

Fill in `category` with category IDs. Apply output: `id`, `category`, `result`.

### edit draft/apply

`fscl transactions edit draft --account|--category|--start|--end [--limit <n>]` (at least one filter required)
`fscl transactions edit apply [--dry-run]`

Draft shape: `[{ id, date, amount, payee, category, notes, cleared, account, _meta }]`

Amounts are decimal strings. Apply output: `id`, `fields`, `result`.

## Importing transactions

Supported formats: CSV/TSV, OFX/QFX, QIF, CAMT XML. Auto-detected from file extension.

### General flags

| Flag | Description |
|---|---|
| `--report` | Print compact import summary |
| `--dry-run` | Preview without committing |
| `--show-rows` | Print individual imported rows |
| `--no-reconcile` | Skip rule processing and deduplication |
| `--no-clear` | Don't auto-set `cleared=true` |
| `--no-import-notes` | Skip memo/notes field |
| `--date-format <fmt>` | Override date parsing format |
| `--multiplier <n>` | Multiply amounts (use `0.01` if values are in cents) |
| `--flip-amount` | Negate all amounts |

Supported date formats: `yyyy mm dd`, `yy mm dd`, `mm dd yyyy`, `mm dd yy`, `dd mm yyyy`, `dd mm yy`.

`--dry-run` requires reconcile mode (default). Cannot combine with `--no-reconcile`.

### OFX/QFX flags

| Flag | Description |
|---|---|
| `--fallback-payee-to-memo` | Use memo as payee when payee is missing |

### CSV flags

| Flag | Description |
|---|---|
| `--no-csv-header` | CSV has no header row |
| `--csv-delimiter <char>` | Delimiter character (default: `,`) |
| `--csv-skip-start <n>` | Skip N lines at start |
| `--csv-skip-end <n>` | Skip N lines at end |
| `--csv-date-col <name\|index>` | Date column |
| `--csv-amount-col <name\|index>` | Signed amount column |
| `--csv-payee-col <name\|index>` | Payee column |
| `--csv-notes-col <name\|index>` | Notes column |
| `--csv-category-col <name\|index>` | Category column |
| `--csv-inflow-col <name\|index>` | Inflow column (alternative to single amount) |
| `--csv-outflow-col <name\|index>` | Outflow column (alternative to single amount) |
| `--csv-inout-col <name\|index>` | In/out marker column |
| `--csv-out-value <value>` | Value treated as outflow in `--csv-inout-col` |

### CSV amount modes

Three ways to handle amounts:

1. **Single signed column:** `--csv-amount-col "Amount"`
2. **Separate inflow/outflow:** `--csv-inflow-col "Credit" --csv-outflow-col "Debit"`
3. **Amount with in/out marker:** `--csv-amount-col "Amount" --csv-inout-col "Type" --csv-out-value "DR"`

### Import pipeline

Parse → Normalize (date format, multiplier, flip) → Reconcile (rules + dedup, skip with `--no-reconcile`) → Commit (skip with `--dry-run`)

## month

| Command | Key Flags |
|---|---|
| `month list` | |
| `month show <month>` | |
| `month status` | `--month <YYYY-MM>`, `--compare <n>`, `--only <over\|under\|on>` |
| `month set <month> <catId> <amount>` | Amount as decimal string |
| `month copy <source> <target>` | Copy all budget amounts between months |
| `month set-carryover <month> <catId> <true\|false>` | Toggle rollover overspending |
| `month cleanup <month>` | End-of-month cleanup |

`month show` columns: `month`, `group_id`, `group_name`, `category_id`, `category_name`, `budgeted`, `spent`, `balance`. Metadata: `toBudget`.

`month status` columns: above + `remaining`, `percent_used`, `over_under`, `status`, `compare_months`, `prev_spent_avg`, `trend_delta`.

### month draft/apply

`fscl month draft <month>`
`fscl month apply <month> [--dry-run]`

Draft shape: `[{ categoryId, group, name, amount }]`. Only `categoryId` and `amount` (decimal string) are used on apply.

### month templates

| Command | Key Flags |
|---|---|
| `month templates check` | Validate templates |
| `month templates draft` | Generate editable template definitions |
| `month templates apply` | `--dry-run` |
| `month templates run <month>` | `--category <id>` for single category |

## categories

| Command | Key Flags |
|---|---|
| `categories list` | |
| `categories find <names...>` | Case-insensitive substring, OR-matched |
| `categories create <name>` | `--group <groupId>` (required), `--income` |
| `categories draft` | Writes editable category-group/category tree to draft file |
| `categories apply` | `--dry-run`; creates/renames/moves from draft |
| `categories update <id>` | `--name` |
| `categories delete <id>` | `--yes` (required), `--transfer-to <catId>` |
| `categories create-group <name>` | `--income` |
| `categories update-group <id>` | `--name` |
| `categories delete-group <id>` | `--yes` (required), `--transfer-to <catId>` |

List columns: `kind`, `id`, `group_id`, `name`, `is_income`, `hidden`
Find columns: `kind`, `id`, `name`, `group_id`, `group_name`, `is_income`, `hidden`

### categories draft/apply

`fscl categories draft`
`fscl categories apply [--dry-run]`

Draft shape: `[{ id?, name, categories: [{ id?, name }] }]`

Top-level entries are category groups. The `categories` array contains category entities. Categories do not nest inside other categories.

- `id` present + changed `name` -> rename
- category `id` moved under a different group -> reassign
- missing `id` -> create
- rows removed from draft are ignored (no deletion)

## payees

| Command | Key Flags |
|---|---|
| `payees list` | |
| `payees find <names...>` | Case-insensitive substring, OR-matched |
| `payees stats` | `--since <date>`, `--min-count <n>`, `--extended` |
| `payees create <name>` | |
| `payees update <id>` | `--name` |
| `payees delete <id>` | `--yes` (required) |
| `payees merge <targetId> <mergeIds...>` | Reassigns all transactions to target |

Output columns: `id`, `name`, `transfer_acct`

## rules

| Command | Key Flags |
|---|---|
| `rules list` | |
| `rules validate <json>` | Validate without creating |
| `rules preview <json>` | Show matching transactions |
| `rules run` | `--rule <id>`, `--dry-run`, `--and-commit` |
| `rules create <json>` | `--run` (apply retroactively) |
| `rules create-batch <json>` | All validated before any created |
| `rules draft` | Generate editable draft |
| `rules apply` | `--dry-run` |
| `rules update <jsonWithId>` | Full rule object with `id` required |
| `rules delete <id>` | `--yes` (required) |

List columns: `id`, `stage`, `conditions_op`, `conditions`, `actions`
Run columns: `id`, `date`, `account`, `account_name`, `amount`, `payee_before`, `payee_before_name`, `payee_after`, `payee_after_name`, `notes`, `category_before`, `category_before_name`, `category_after`, `category_after_name`, `matched_rule`

See [rules.md](rules.md) for JSON schema, stages, conditions, and actions.

## schedules

| Command | Key Flags |
|---|---|
| `schedules list` | |
| `schedules find <names...>` | Case-insensitive name search |
| `schedules upcoming` | `--days <n>` (default: 7) |
| `schedules missed` | `--days <n>` (default: 30) |
| `schedules summary` | Monthly/annual cost rollup |
| `schedules history <id>` | `--limit <n>` (default: 12) |
| `schedules review <id> <json>` | `{decision, note?, cadenceMonths?}` |
| `schedules reviews` | `--due` (only unreviewed/due) |
| `schedules create <json>` | |
| `schedules update <id> <json>` | |
| `schedules delete <id>` | `--yes` (required) |

List columns: `id`, `name`, `posts_transaction`, `next_date`, `amount`, `amount_op`, `account`, `account_name`, `payee`, `payee_name`, `date`
Upcoming adds: `days_until`. Missed adds: `days_overdue`.
Summary columns: `id`, `name`, `payee_name`, `account_name`, `amount`, `frequency`, `interval`, `monthly_amount`, `annual_amount`, `next_date`. Metadata: `total_monthly`, `total_annual`.
Reviews columns: `schedule_id`, `name`, `payee_name`, `amount`, `decision`, `reviewed_at`, `next_review_at`, `cadence_months`, `note`, `days_until_review`

## tags

| Command | Key Flags |
|---|---|
| `tags list` | |
| `tags find <names...>` | Case-insensitive name search |
| `tags create <name>` | `--color <hex>`, `--description <text>` |
| `tags update <id>` | `--name`, `--color`, `--description` |
| `tags delete <id>` | `--yes` (required) |

## query

Run custom [ActualQL](https://actualbudget.org/docs/api/actual-ql/) queries.

```bash
fscl query --module <path>       # Run a query module
fscl query --inline <expr>       # Run an inline expression
```

Use exactly one of `--module` or `--inline`. Module can export `default (q) => query` or a named `query`.

## Draft/apply workflow

Six commands support draft/apply: **categories**, **categorize**, **edit**, **rules**, **month**, **templates**.

1. `<command> draft [filters]` — generates JSON at `<dataDir>/<budgetId>/drafts/`
2. Edit the JSON file
3. `<command> apply --dry-run` — preview changes
4. `<command> apply` — commit (deletes draft on success)

Never hand-create a draft file by path. Generate it with `<command> draft`, then edit that generated file.

## Exit codes

| Code | Meaning |
|---|---|
| `0` | Success |
| `1` | Failure (validation errors, partial import failures, etc.) |


path: references/commands.md
content: # Command Cheat Sheet

Common patterns and recipes. For full flag/output details, see [command-reference.md](command-reference.md).

## Conventions

- Always pass `--json` — present output as human-friendly summaries, never raw JSON.
- Amounts: input as decimals (`--amount 45.99`), output in cents (`-4599`).
- Dates: `YYYY-MM-DD`. Months: `YYYY-MM`.
- JSON input: pass inline or use `@filepath` to read from file.
- Deletion: always requires `--yes`. Use `--transfer-to <id>` to reassign children before deleting.
- `find` does case-insensitive substring matching. Multiple terms are OR-matched.
- If a command returns code `not-logged-in`, run `fscl login [server-url] --password <pw>` and retry.

## Entity Resolution

Commands that accept account, category, or category group IDs also accept names. The CLI resolves names to IDs automatically using exact case-insensitive matching. If the name is ambiguous (multiple matches), the command fails with the matching IDs listed.

```bash
# These are equivalent:
fscl transactions list abc-123-def --start 2026-01-01 --end 2026-01-31
fscl transactions list "Checking" --start 2026-01-01 --end 2026-01-31

fscl month set 2026-02 "Groceries" 500.00
fscl categories create "Dining Out" --group "Food"
```

For fuzzy substring searches (multiple results), use `find`:

```bash
fscl accounts find "checking" --json
fscl categories find "groceries" --json
fscl payees find amzn amazon "whole foods" --json
fscl schedules find "rent" --json
```

Use `--columns id,name` to get concise output when you only need IDs.

## Draft/Apply Pattern

Six commands use draft/apply: **categories**, **categorize**, **edit**, **rules**, **month**, **templates**. The cycle is always the same:

```bash
fscl <command> draft [filters]     # Writes JSON to <dataDir>/<budgetId>/drafts/
# Edit the draft file
fscl <command> apply --dry-run     # Preview changes
fscl <command> apply               # Commit changes (deletes draft on success)
```

Never hand-create a new draft JSON file by guessing the `drafts/` path. Always generate it with `fscl <command> draft` first, then edit the generated file.

Draft files include `_meta` fields for context — these are ignored on apply.

Category drafts use two entity types: category-group rows at the top level and category rows inside each group's `categories` array (`[{ id?, name, categories: [{ id?, name }] }]`). Categories do not contain child categories.

## Import Pipeline

The standard sequence after getting bank files:

```bash
# 1. Preview
fscl transactions import <acct-id> <file> --dry-run --report

# 2. Import
fscl transactions import <acct-id> <file> --report

# 3. Auto-categorize with rules
fscl rules run --and-commit

# 4. Handle remaining uncategorized
fscl transactions categorize draft
# Fill in category IDs using _meta context
fscl transactions categorize apply

# 5. Create rules for new recurring payees
fscl rules draft
# Add new rules (rows without id)
fscl rules apply
fscl rules run --and-commit
```

For CSV column mapping, see [import-guide.md](import-guide.md).

## Budget Amounts

```bash
# Set one category
fscl month set 2026-02 <cat-id> 500.00

# Copy all amounts from one month to another
fscl month copy 2026-01 2026-02

# Bulk edit
fscl month draft 2026-02
# Edit amounts in the draft
fscl month apply 2026-02

# Use templates
fscl month templates run 2026-03
```

Budget mutation commands (`month set`, `month apply`, `month copy`, `month templates run`) include `toBudget` in their JSON output — the current To Budget amount for the affected month.

## Rules

```bash
# Create and immediately apply retroactively
fscl rules create '<json>' --run

# Preview what a rule would match (without creating it)
fscl rules preview '<json>'

# Batch create/edit via draft
fscl rules draft → edit → fscl rules apply → fscl rules run --and-commit
```

See [rules.md](rules.md) for JSON schema, stages, and common patterns.

## Payee Cleanup

```bash
# Find messy names
fscl payees stats --extended --json

# Rename
fscl payees update <id> --name "Clean Name"

# Merge duplicates (keeps target, reassigns transactions)
fscl payees merge <keep-id> <dup-id-1> <dup-id-2>
```

## Schedules

```bash
fscl schedules upcoming --days 7 --json     # What's due soon
fscl schedules missed --json                 # What's overdue
fscl schedules summary --json                # Recurring cost overview (monthly/annual totals)
fscl schedules history <id> --json           # Past transactions for a schedule
fscl schedules reviews --due --json          # Schedules needing review
```

## Init Modes

```bash
# Local-only budget
fscl init --non-interactive --mode local --budget-name "My Budget"

# Delete a local budget copy
fscl budgets delete <id> --yes

# Pull existing remote budget
fscl login http://actual.local:5006 --password secret
fscl init --non-interactive --mode remote \
  --server-url http://actual.local:5006 --sync-id <id>

# Create local, then upload to server
fscl init --non-interactive --mode local --budget-name "My Budget"
fscl login http://actual.local:5006 --password secret
fscl budgets push --server-url http://actual.local:5006
```

Interactive `fscl init` asks whether to install the Fiscal skill (`npx skills add fiscal-sh/fscl`) at the end. In non-interactive mode, run that command manually when needed.
If init is run as `npx fscl init`, interactive mode also asks whether to install fscl globally with `npm install -g fscl`.


path: references/credit-cards.md
content: # Credit Cards

## How credit cards work in Actual Budget

Credit card accounts are on-budget accounts with a negative balance. Actual treats them like any other account — the negative balance is subtracted from your positive deposit account balances to give your total on-budget funds.

When you make a credit card purchase and categorize it, the money comes from that budget category (just like a debit card purchase). The only difference is the timing of when the actual cash leaves your checking account (when you pay the credit card bill).

**Key insight:** If every credit card purchase is categorized to a funded budget category, you will always have enough money to pay the bill. The budget already accounted for the spending.

## Strategy: Paying in Full (Within the Budget)

Use this when you pay off your credit card statement every month.

### Rules

- Never over-budget. Only budget funds you actually have
- Check your category balance before spending
- Enter purchase transactions promptly
- Cover all overspending by moving money between categories
- Reconcile accounts at least monthly
- Pay at least your statement balance before the due date

### Setup

1. Confirm the account is a credit card (not checking/savings) before creating it.

2. Create the credit card as an on-budget account with a negative starting balance. Include account type in the name:

```bash
fscl accounts create "Chase Credit 5736" --balance -35.00
```

3. No special budget category needed — each purchase uses your regular expense categories

### Monthly workflow

1. Throughout the month, categorize every credit card purchase to a funded category
2. When the statement arrives, note the New Balance
3. Reconcile the account
4. Pay the statement balance as a linked transfer from checking:

```bash
# Add/import the payment on checking
fscl transactions add <checking-acct-id> --date 2026-02-15 --amount -213.15 \
  --payee "CHASE PAYMENT" --notes "Statement payment"

# Convert matching entries into linked transfers by setting payee to the transfer payee
# (find <chase-transfer-payee-id> via "fscl payees list", where transfer_acct=<chase-acct-id>)
fscl rules create '{
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    {"field": "account", "op": "is", "value": "<checking-acct-id>"},
    {"field": "imported_payee", "op": "contains", "value": "CHASE PAYMENT"}
  ],
  "actions": [
    {"field": "payee", "op": "set", "value": "<chase-transfer-payee-id>"}
  ]
}'
fscl rules run --dry-run
fscl rules run
```

## Strategy: Carrying Debt

Use this when you have a credit card balance you cannot pay in full. The goal is to pay it off safely without incurring more debt.

### Setup

1. Create a "Credit Card Debt" category group:

```bash
fscl categories create-group "Credit Card Debt"
```

2. Create a debt category for each card carrying debt:

```bash
fscl categories create "Citi Card Debt" --group <debt-group-id>
fscl categories create "DEMO Card Debt" --group <debt-group-id>
```

3. Enable rollover overspending on each debt category:

```bash
fscl month set-carryover 2026-02 <citi-debt-cat-id> true
fscl month set-carryover 2026-02 <demo-debt-cat-id> true
```

4. Create the credit card account with the total amount owed as the starting balance, categorized to the debt category. Include account type in the name:

```bash
fscl accounts create "Citibank Credit 4455" --balance -2590.00
```

The starting balance transaction should be categorized to the debt category.

### Why rollover is needed

Negative credit card account balances are already subtracted from your available on-budget funds. Without rollover, the negative debt category balance would also be deducted from "To Budget" — double-counting the debt. Rollover prevents this.

### Monthly workflow (card with debt, no new purchases)

1. At the start of the month, budget the expected minimum payment to the debt category:

```bash
fscl month set 2026-02 <citi-debt-cat-id> 90.00
```

2. If you have extra money after funding all expenses, add it to the debt category to pay down faster:

```bash
fscl month set 2026-02 <citi-debt-cat-id> 290.00  # 90 minimum + 200 extra
```

3. When the statement arrives, enter interest and fees as a transaction categorized to the debt category:

```bash
fscl transactions add <citi-acct-id> --date 2026-02-10 --amount -64.00 \
  --payee "Citibank" --category <citi-debt-cat-id> --notes "Interest charge"
```

4. Make the payment as a linked transfer from checking:

```bash
fscl transactions add <checking-acct-id> --date 2026-02-15 --amount -290.00 \
  --payee "CITIBANK PAYMENT" --notes "CC payment"

# Rule converts payee text to the card transfer payee ID
fscl rules create '{
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    {"field": "account", "op": "is", "value": "<checking-acct-id>"},
    {"field": "imported_payee", "op": "contains", "value": "CITIBANK PAYMENT"}
  ],
  "actions": [
    {"field": "payee", "op": "set", "value": "<citi-transfer-payee-id>"}
  ]
}'
fscl rules run --dry-run
fscl rules run
```

### Monthly workflow (card with debt AND new purchases)

If you must use a card that has existing debt:

1. Categorize new purchases to regular funded budget categories (staying Within the Budget)
2. When the statement arrives, enter interest/fees to the debt category
3. Reconcile the account
4. Calculate payment:
   - **Payment = New Purchases - Return Credits + Uncleared Total + Budgeted Column**
   - Or equivalently: **Account Balance - Debt Category Balance** (using absolute values)
5. The minimum payment must be at least the statement minimum

### Paying off the highest interest first

When carrying debt on multiple cards:
- Budget minimum payments for all cards
- After funding all necessary expenses, add extra to the highest-interest card
- Once the highest-interest card is paid off, redirect that extra payment to the next highest
- Use only one card for new purchases and pay it in full monthly

### Important notes

- All credit card accounts should be **on-budget** (not off-budget)
- Include account type in the account name (for example, `Chase Credit 5736`) so account context stays clear in future chats
- You cannot change an off-budget account to on-budget — set it correctly from the start
- Only set a credit card as off-budget if there will be no new purchases and the account will be closed once paid off
- A credit limit is not an invitation to spend it
- If you lose your grace period due to late payment, you'll incur interest from purchase date on all new purchases until you pay in full for several consecutive months


path: references/import-guide.md
content: # Importing Transactions

fscl imports bank transaction files into an account. The import pipeline parses the file, normalizes data, optionally runs rules and deduplication, and commits to the budget.

## Supported formats

| Extension | Format | Description |
|---|---|---|
| `.csv`, `.tsv` | CSV/TSV | Comma or tab-separated values |
| `.qif` | QIF | Quicken Interchange Format |
| `.ofx`, `.qfx` | OFX | Open Financial Exchange (also Quicken Financial Exchange) |
| `.xml` | CAMT | ISO 20022 CAMT XML |

Format is auto-detected by file extension.

## Basic usage

```bash
# Import with rule processing and deduplication (default)
fscl transactions import <accountId> ./export.ofx

# Preview without committing
fscl transactions import <accountId> ./export.ofx --dry-run

# See individual imported rows
fscl transactions import <accountId> ./export.ofx --show-rows

# Get a compact summary
fscl transactions import <accountId> ./export.ofx --report
```

## Import pipeline

1. **Parse** — Read file, extract transactions based on format
2. **Normalize** — Apply date format, amount multiplier, flip-amount
3. **Reconcile** (default) — Run through Actual's rule engine and deduplication
4. **Commit** — Add/update transactions in the budget

With `--no-reconcile`, steps 3 is skipped (raw add, no rules, no dedup).

## General flags

| Flag | Default | Description |
|---|---|---|
| `--no-reconcile` | off | Skip rule processing and deduplication |
| `--dry-run` | off | Preview without committing changes |
| `--show-rows` | off | Print each imported transaction row |
| `--report` | off | Print compact import summary row |
| `--no-clear` | off | Don't auto-set `cleared=true` on imported transactions |
| `--no-import-notes` | off | Skip the memo/notes field from the source file |
| `--date-format <fmt>` | auto | Override date parsing order (e.g. `mm dd yyyy`, `dd mm yyyy`) |
| `--multiplier <n>` | 1 | Multiply all parsed amounts by this factor |
| `--flip-amount` | off | Negate all amounts (useful when bank exports use opposite sign convention) |

## OFX/QFX-specific

| Flag | Default | Description |
|---|---|---|
| `--fallback-payee-to-memo` | off | Use the memo field as payee when payee is missing |

## CSV-specific flags

### File structure

| Flag | Default | Description |
|---|---|---|
| `--no-csv-header` | off | CSV has no header row (use column indices instead of names) |
| `--csv-delimiter <char>` | `,` | Field delimiter character |
| `--csv-skip-start <n>` | 0 | Skip N lines at the start of the file |
| `--csv-skip-end <n>` | 0 | Skip N lines at the end of the file |

### Column mapping

Map CSV columns to transaction fields by name or zero-based index:

| Flag | Description |
|---|---|
| `--csv-date-col <name\|index>` | Date column |
| `--csv-amount-col <name\|index>` | Signed amount column |
| `--csv-payee-col <name\|index>` | Payee/description column |
| `--csv-notes-col <name\|index>` | Notes/memo column |
| `--csv-category-col <name\|index>` | Category column |

### Amount modes

There are three ways CSV files represent amounts:

**Mode 1: Single signed amount column** (default)

One column has positive values for income and negative for expenses (or vice versa):

```bash
fscl transactions import <acct> export.csv --csv-amount-col "Amount"
```

**Mode 2: Separate inflow/outflow columns**

Two columns, one for money in and one for money out:

```bash
fscl transactions import <acct> export.csv \
  --csv-inflow-col "Credit" \
  --csv-outflow-col "Debit"
```

**Mode 3: In/out marker column**

An amount column plus a separate column that indicates direction:

```bash
fscl transactions import <acct> export.csv \
  --csv-amount-col "Amount" \
  --csv-inout-col "Type" \
  --csv-out-value "DR"
```

### Adjusting amounts

| Flag | Description |
|---|---|
| `--flip-amount` | Negate all amounts. Use when your bank uses the opposite sign convention |
| `--multiplier <n>` | Multiply all amounts before conversion to minor units. Keep `1` for normal decimal amounts (e.g. `45.67`). Use `0.01` only if the source values are already in cents (e.g. `4567` meaning `$45.67`). Use `-1` as an alternative to `--flip-amount` |

## Examples

### Standard OFX import

```bash
fscl transactions import abc123 ./checking-jan.ofx --report
# {"status":"ok","entity":"import","input":42,"added":42,"updated":0,"preview":0,"skipped":0,"errors":0}
```

### CSV with custom columns

Bank exports: `Date,Description,Debit,Credit,Balance`

```bash
fscl transactions import abc123 ./statement.csv \
  --csv-date-col "Date" \
  --csv-payee-col "Description" \
  --csv-inflow-col "Credit" \
  --csv-outflow-col "Debit" \
  --date-format "dd mm yyyy"
```

### CSV with no header and tab delimiter

```bash
fscl transactions import abc123 ./export.tsv \
  --no-csv-header \
  --csv-delimiter "\t" \
  --csv-date-col 0 \
  --csv-amount-col 1 \
  --csv-payee-col 2
```

### CSV with extra header/footer lines

Some bank exports include summary lines before/after the data:

```bash
fscl transactions import abc123 ./export.csv \
  --csv-skip-start 3 \
  --csv-skip-end 1 \
  --csv-date-col "Date" \
  --csv-amount-col "Amount" \
  --csv-payee-col "Payee"
```

### Dry-run to preview

Always preview first with unfamiliar files:

```bash
fscl transactions import abc123 ./unknown.csv --dry-run --show-rows
```

This shows what would be imported without making changes. Check the output for correct dates, amounts, and payees before committing.

### QIF import with amount flip

```bash
fscl transactions import abc123 ./download.qif --flip-amount --report
```

## Import output

### With `--report`

```json
{
  "status": "ok",
  "entity": "import",
  "input": 17,
  "added": 14,
  "updated": 3,
  "preview": 0,
  "skipped": 0,
  "errors": 0
}
```

### With `--show-rows`

You still get the JSON import status object, plus an additional JSON payload with `entity: "import-rows"`:

```json
{
  "status": "ok",
  "entity": "import-rows",
  "count": 2,
  "data": [
    { "date": "2026-01-05", "amount": -4599, "payee_name": "STARBUCKS #1234", "category": null, "notes": "coffee" },
    { "date": "2026-01-06", "amount": -12300, "payee_name": "WHOLE FOODS MKT", "category": null, "notes": "groceries" }
  ]
}
```

### With `--dry-run`

Same output format but no changes are committed. The `added` and `updated` counts show what *would* happen.

## Workflow: CSV import with custom mapping

Common scenario: bank exports CSV with non-standard columns.

Example bank CSV:
```
Transaction Date,Post Date,Description,Category,Type,Amount,Memo
01/15/2026,01/16/2026,WHOLE FOODS #10234,Groceries,Sale,-45.67,
01/16/2026,01/17/2026,PAYROLL DEPOSIT,,Direct Dep,2500.00,Monthly salary
```

```bash
# Preview first
fscl transactions import <acct-id> ./bank-export.csv \
  --csv-date-col "Transaction Date" \
  --csv-payee-col "Description" \
  --csv-amount-col "Amount" \
  --csv-notes-col "Memo" \
  --date-format "mm dd yyyy" \
  --dry-run --show-rows

# If it looks correct, import
fscl transactions import <acct-id> ./bank-export.csv \
  --csv-date-col "Transaction Date" \
  --csv-payee-col "Description" \
  --csv-amount-col "Amount" \
  --csv-notes-col "Memo" \
  --date-format "mm dd yyyy" \
  --report
```

For a CSV with separate debit/credit columns:
```
Date,Description,Debit,Credit
15/01/2026,WHOLE FOODS,45.67,
16/01/2026,PAYROLL,,2500.00
```

```bash
fscl transactions import <acct-id> ./export.csv \
  --csv-date-col "Date" \
  --csv-payee-col "Description" \
  --csv-outflow-col "Debit" \
  --csv-inflow-col "Credit" \
  --date-format "dd mm yyyy" \
  --report
```


path: references/query-library.md
content: # Query Library

Pre-built AQL queries for common reporting needs. Each query is shown as an inline one-liner and a reusable module file.

## 1. Spending by category (current month)

Total spent per category for a given month.

```bash
fscl query --inline "q('transactions').filter({date:{$gte:'2026-02-01',$lte:'2026-02-28'},amount:{$lt:0}}).select(['category.name',{total:{$sum:'$amount'}}]).groupBy('category.name').orderBy({total:'asc'})"
```

```javascript
// spending-by-category.mjs
export default (q) =>
  q('transactions')
    .filter({
      date: { $gte: '2026-02-01', $lte: '2026-02-28' },
      amount: { $lt: 0 },
    })
    .select(['category.name', { total: { $sum: '$amount' } }])
    .groupBy('category.name')
    .orderBy({ total: 'asc' });
```

## 2. Income vs expenses

Total income and total spending for a period.

```bash
fscl query --inline "q('transactions').filter({date:{$gte:'2026-01-01',$lte:'2026-12-31'}}).select([{income:{$sum:{$case:[{$gt:['$amount',0]},'$amount',0]}}},{expenses:{$sum:{$case:[{$lt:['$amount',0]},'$amount',0]}}}])"
```

```javascript
// income-vs-expenses.mjs
export default (q) =>
  q('transactions')
    .filter({
      date: { $gte: '2026-01-01', $lte: '2026-12-31' },
    })
    .select([
      { income: { $sum: { $case: [{ $gt: ['$amount', 0] }, '$amount', 0] } } },
      { expenses: { $sum: { $case: [{ $lt: ['$amount', 0] }, '$amount', 0] } } },
    ]);
```

## 3. Top payees by spending

Top N payees ranked by total outflow.

```bash
fscl query --inline "q('transactions').filter({date:{$gte:'2026-01-01',$lte:'2026-12-31'},amount:{$lt:0}}).select(['payee.name',{total:{$sum:'$amount'}}]).groupBy('payee.name').orderBy({total:'asc'}).limit(20)"
```

```javascript
// top-payees.mjs
export default (q) =>
  q('transactions')
    .filter({
      date: { $gte: '2026-01-01', $lte: '2026-12-31' },
      amount: { $lt: 0 },
    })
    .select(['payee.name', { total: { $sum: '$amount' } }])
    .groupBy('payee.name')
    .orderBy({ total: 'asc' })
    .limit(20);
```

## 4. Monthly spending trend

Total spending per month for the past 12 months.

```bash
fscl query --inline "q('transactions').filter({date:{$gte:'2025-03-01',$lte:'2026-02-28'},amount:{$lt:0}}).select([{month:{$month:'$date'}},{total:{$sum:'$amount'}}]).groupBy({month:{$month:'$date'}}).orderBy({month:'asc'})"
```

```javascript
// monthly-trend.mjs
export default (q) =>
  q('transactions')
    .filter({
      date: { $gte: '2025-03-01', $lte: '2026-02-28' },
      amount: { $lt: 0 },
    })
    .select([
      { month: { $month: '$date' } },
      { total: { $sum: '$amount' } },
    ])
    .groupBy({ month: { $month: '$date' } })
    .orderBy({ month: 'asc' });
```

## 5. Large transactions

Transactions above a threshold amount (e.g. $100 expenses).

```bash
fscl query --inline "q('transactions').filter({date:{$gte:'2026-02-01'},amount:{$lt:-10000}}).select(['date','payee.name','category.name','amount','notes']).orderBy({amount:'asc'})"
```

```javascript
// large-transactions.mjs
export default (q) =>
  q('transactions')
    .filter({
      date: { $gte: '2026-02-01' },
      amount: { $lt: -10000 }, // -$100.00 in minor units
    })
    .select(['date', 'payee.name', 'category.name', 'amount', 'notes'])
    .orderBy({ amount: 'asc' });
```

## 6. Net worth snapshot

Sum of all account balances (current).

```bash
fscl query --inline "q('transactions').select([{net_worth:{$sum:'$amount'}}])"
```

```javascript
// net-worth.mjs
export default (q) =>
  q('transactions')
    .select([{ net_worth: { $sum: '$amount' } }]);
```

## Usage notes

- All amounts in AQL are integer minor units: `-4599` = -$45.99
- Date filters use ISO format: `YYYY-MM-DD`
- `$month` extracts the month from a date for grouping
- Run with `fscl query --module ./file.mjs` or `fscl query --inline "..."`
- Adjust date ranges and thresholds to match your needs

## Workflow: advanced queries

```bash
# Inline query: total spending by category this month
fscl query --inline "q('transactions').filter({date:{$gte:'2026-02-01'}}).select(['category.name','amount']).groupBy('category.name')"

# Module query: save reusable queries as files
fscl query --module ./top-payees.mjs
```


path: references/rules.md
content: # Transaction Rules

Rules automatically process transactions during import or when applied retroactively. They can rename payees, set categories, update notes, and more.

## Rule JSON schema

```json
{
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    { "field": "imported_payee", "op": "contains", "value": "AMAZON" }
  ],
  "actions": [
    { "field": "payee", "op": "set", "value": "<payee-id>" },
    { "field": "category", "op": "set", "value": "<category-id>" }
  ]
}
```

## Stages

Rules run in three stages, in order:

| Stage | Purpose | Typical use |
|---|---|---|
| `"pre"` | Runs first | Payee renaming (clean up raw imported names) |
| `null` (default) | Auto-ranked by specificity | Categorization, most rules |
| `"post"` | Runs last, always wins | Overrides, forced assignments |

Within each stage, rules are automatically ranked from least to most specific. An `is` condition always ranks higher than `contains`, so more specific rules override broader ones.

When a transaction is imported, it runs through all rules in stage order. Each rule runs at most once. If multiple rules set the same field, the last one wins.

## Condition fields

| Field | Description |
|---|---|
| `imported_payee` | Raw payee/description text from the import file. Always the original text, unaffected by rules |
| `payee` | Payee entity in Actual (after any previous rule renames it) |
| `account` | Account the transaction belongs to |
| `category` | Category assigned to the transaction |
| `date` | Transaction date |
| `notes` | Notes/memo field |
| `amount` | Signed amount in minor units (negative = expense) |
| `amount_inflow` | Amount filtered to positive values only |
| `amount_outflow` | Amount filtered to negative values only (as positive number) |

**Important:** Use `imported_payee` (not `payee`) for matching raw bank text. The `payee` field references an entity in Actual and changes as previous rules rename it. Multiple rules checking `imported_payee` won't interfere with each other.

## Condition operators

| Operator | Applies to | Description |
|---|---|---|
| `is` | strings, payee, account, category | Exact match |
| `isNot` | strings, payee, account, category | Not exact match |
| `contains` | strings | Substring match (case-insensitive) |
| `doesNotContain` | strings | No substring match |
| `matches` | strings | Regular expression match |
| `oneOf` | payee, account, category | Matches any in a list |
| `notOneOf` | payee, account, category | Matches none in a list |
| `gt` | amount, date | Greater than |
| `lt` | amount, date | Less than |
| `gte` | amount, date | Greater than or equal |
| `lte` | amount, date | Less than or equal |
| `isapprox` | date, amount | Approximately equal |

All string matching is case-insensitive.

## Action fields

| Field | Description |
|---|---|
| `category` | Set the transaction's category |
| `payee` | Set the transaction's payee |
| `notes` | Set the notes field |
| `cleared` | Set cleared status (value: `true` or `false`) |
| `account` | Set the account |
| `date` | Set the date |
| `amount` | Set the amount |

For `op: "set"` actions, you can also pass advanced options:
- `options.template` for handlebars-style rule action templating
- `options.formula` for Excel-style rule formulas

## Action operators

| Operator | Description |
|---|---|
| `set` | Replace the field value |
| `prepend-notes` | Prepend text to the notes field |
| `append-notes` | Append text to the notes field |
| `link-schedule` | Link the transaction to a schedule |
| `set-split-amount` | Set amount on a split transaction |

## Automatic rules

Actual automatically creates rules based on user behavior:

1. **Payee renaming** — When you rename a payee and the old name is no longer used, Actual creates a `pre` stage rule with an `imported_payee` `is` condition that sets the payee. This means future imports auto-clean the payee name.

2. **Categorization** — When you categorize a transaction, Actual determines the most common category for that payee and creates/updates a default stage rule. Over time, most categories auto-apply on import.

You can edit or delete these auto-created rules at any time.

## Workflow: creating rules via fscl

### 1. Validate rule JSON first

```bash
fscl rules validate '{
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    {"field": "imported_payee", "op": "contains", "value": "UBER EATS"}
  ],
  "actions": [
    {"field": "category", "op": "set", "value": "<dining-cat-id>"},
    {"field": "payee", "op": "set", "value": "<uber-eats-payee-id>"}
  ]
}'
```

### 2. Preview what the rule would match

```bash
fscl rules preview '{
  "conditions": [
    {"field": "imported_payee", "op": "contains", "value": "UBER EATS"}
  ],
  "conditionsOp": "and",
  "actions": [
    {"field": "category", "op": "set", "value": "<dining-cat-id>"},
    {"field": "payee", "op": "set", "value": "<uber-eats-payee-id>"}
  ]
}'
```

This shows matching transactions without creating the rule.

### 3. Create the rule

```bash
fscl rules create '{
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    {"field": "imported_payee", "op": "contains", "value": "UBER EATS"}
  ],
  "actions": [
    {"field": "category", "op": "set", "value": "<dining-cat-id>"},
    {"field": "payee", "op": "set", "value": "<uber-eats-payee-id>"}
  ]
}'
```

### 4. Apply retroactively

```bash
# Preview what would change
fscl rules run --dry-run

# Apply all rules to uncategorized transactions
fscl rules run

# Apply just one rule
fscl rules run --rule <rule-id> --dry-run
fscl rules run --rule <rule-id>
```

### 5. Update a rule

You must provide the full rule object including `id`:

```bash
fscl rules update '{
  "id": "<rule-id>",
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    {"field": "imported_payee", "op": "contains", "value": "UBER"}
  ],
  "actions": [
    {"field": "category", "op": "set", "value": "<dining-cat-id>"},
    {"field": "payee", "op": "set", "value": "<uber-eats-payee-id>"}
  ]
}'
```

## Common rule patterns

### Payee cleanup (pre stage)

Clean up ugly bank payee names:

```json
{
  "stage": "pre",
  "conditionsOp": "and",
  "conditions": [
    { "field": "imported_payee", "op": "contains", "value": "AMAZON" }
  ],
  "actions": [
    { "field": "payee", "op": "set", "value": "<amazon-payee-id>" }
  ]
}
```

### Auto-categorize by payee

```json
{
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    { "field": "payee", "op": "is", "value": "<grocery-store-payee-id>" }
  ],
  "actions": [
    { "field": "category", "op": "set", "value": "<groceries-cat-id>" }
  ]
}
```

### Formula action (set amount from formula)

```json
{
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    { "field": "imported_payee", "op": "contains", "value": "TIP" }
  ],
  "actions": [
    {
      "field": "amount",
      "op": "set",
      "value": null,
      "options": { "formula": "=ROUND(amount*1.2,0)" }
    }
  ]
}
```

### Template action (set notes from template)

```json
{
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    { "field": "payee", "op": "is", "value": "<payee-id>" }
  ],
  "actions": [
    {
      "field": "notes",
      "op": "set",
      "value": "",
      "options": { "template": "{{payee_name}} {{date}}" }
    }
  ]
}
```

### Auto-clear for instant accounts (post stage)

Cash or Venmo accounts where transactions clear immediately:

```json
{
  "stage": "post",
  "conditionsOp": "and",
  "conditions": [
    { "field": "account", "op": "oneOf", "value": ["<cash-acct-id>", "<venmo-acct-id>"] }
  ],
  "actions": [
    { "field": "cleared", "op": "set", "value": true }
  ]
}
```

### Create transfers between accounts

Set the payee to the target account's transfer payee to create a transfer:

```json
{
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    { "field": "account", "op": "is", "value": "<checking-acct-id>" },
    { "field": "imported_payee", "op": "contains", "value": "TRANSFER TO SAVINGS" }
  ],
  "actions": [
    { "field": "payee", "op": "set", "value": "<savings-transfer-payee-id>" }
  ]
}
```

If both accounts use bank sync, create matching rules for both to avoid duplicate transfers.

## Workflow: Payee cleanup with rules

Clean up messy bank payee names and set up auto-categorization.

```bash
# See payee statistics to identify messy names
fscl payees stats --min-count 2 --extended

# List payees to find duplicates
fscl payees list

# Create a clean payee if needed (create returns the new ID)
fscl payees create "Amazon"

# Create a pre-stage rule to clean the payee on import
fscl rules create '{
  "stage": "pre",
  "conditionsOp": "and",
  "conditions": [
    {"field": "imported_payee", "op": "contains", "value": "AMAZON"}
  ],
  "actions": [
    {"field": "payee", "op": "set", "value": "<amazon-payee-id>"}
  ]
}'

# Create a default-stage rule to auto-categorize
fscl rules create '{
  "stage": null,
  "conditionsOp": "and",
  "conditions": [
    {"field": "payee", "op": "is", "value": "<amazon-payee-id>"}
  ],
  "actions": [
    {"field": "category", "op": "set", "value": "<shopping-cat-id>"}
  ]
}'

# Apply rules retroactively
fscl rules run --and-commit

# Merge duplicate payees
fscl payees merge <amazon-payee-id> <amazon-com-payee-id> <amzn-payee-id>
```


path: references/workflow-maintenance.md
content: # Workflow: Budget Maintenance

The budget is established and the user is using it day-to-day. Handle their requests and run the monthly cycle.

## Monthly Cycle

The complete monthly routine. Run these steps when the user imports new transactions or at the start of a new month.

### 1. Import Transactions

```bash
fscl transactions import <account-id> <file> --report
```

For CSV files with custom columns, see [import-guide.md](import-guide.md). Always preview first with `--dry-run` if it's a new format.

### 2. Run Rules

```bash
fscl rules run --and-commit
```

### 3. Clean Up Payees

Review uncategorized transactions for messy or duplicate payee names:

```bash
fscl transactions uncategorized --json
fscl payees update <id> --name "Clean Name"
fscl payees merge <target-id> <dup-ids...>
```

### 4. Categorize Remaining

```bash
fscl transactions categorize draft
# Fill in category IDs using _meta context
# Do not create categorize.json manually; edit the generated draft file
fscl transactions categorize apply
```

### 5. Create Rules for New Patterns

Any payee appearing 2+ times with the same category should get a rule. See [rules.md](rules.md).

```bash
fscl rules draft
# Add new rules (no id), update existing (with id)
fscl rules apply
fscl rules run --and-commit
```

### 6. Review Budget

```bash
fscl month status --compare 3 --json
fscl month status --only over --json
```

Lead with what matters:
1. Categories that are overspent or near limit
2. Total spent vs total budgeted
3. "To Budget" amount
4. Trends compared to previous months

### 7. Adjust Budget

Move money between categories to cover overages:

```bash
fscl month set <month> <overspent-cat> <new-amount>
fscl month set <month> <surplus-cat> <reduced-amount>
```

Or use draft/apply for bulk changes:

```bash
fscl month draft <month>
# Edit amounts
fscl month apply <month>
```

### 8. New Month Setup

```bash
# Apply templates if configured
fscl month templates check
fscl month templates run <next-month>

# Or draft/apply manually
fscl month draft <next-month>
fscl month apply <next-month>

# Optional: end-of-month cleanup
fscl month cleanup <prev-month>
```

## Common Scenarios

### "How am I doing this month?"

For a quick health check, use compact status: `fscl status --compact --json`. For a full review:

```bash
fscl month status --month <current> --compare 3 --json
fscl month status --month <current> --only over --json
fscl transactions uncategorized --json
```

Present a summary:
> **February Budget Check-in**
> - Spent $2,340 of $3,200 (73%) with 2 weeks left
> - **Watch out:** Dining Out at $185 of $200 (92%)
> - **On track:** Groceries at $290 of $500 (58%)
> - **Under budget:** Transportation at $45 of $150 (30%)
> - 3 uncategorized transactions need attention
> - Compared to the last 3 months, dining spending is up 15%

### "I overspent on [category]"

1. Check how much they're over
2. Find categories with surplus: `fscl month show <month> --json`
3. Suggest a transfer: "You're $45 over on Dining. Entertainment has $80 left. Want me to move $45?"
4. If no surplus anywhere, explain the overspending will reduce next month's available budget
5. After handling it, consider if the budget needs a permanent adjustment

### "I just got paid"

1. Check if the income transaction was imported or needs manual entry
2. Show updated "To Budget" amount
3. If following a month-ahead strategy, this income funds next month's budget
4. If not month-ahead yet, allocate to current month's underfunded categories

### "I want to save for [goal]"

1. Create a savings category: `fscl categories create "Vacation Fund" --group <savings-group-id>`
2. Calculate monthly contribution: goal amount / months
3. Budget it: `fscl month set <month> <vacation-id> 300.00`
4. Track and report progress each month: "You've saved $900 of $3,000 (30%). At $300/month, you'll reach it by November."

### "I returned something"

Enter as a positive-amount transaction in the original expense category — this restores the category balance:

```bash
fscl transactions add <acct-id> --date <date> --amount 32.99 \
  --payee "Amazon" --category <clothing-id> --notes "Return"
```

### "Import my bank transactions"

See [import-guide.md](import-guide.md). Always preview with `--dry-run` first. For CSV files, ask what the columns mean and use `--csv-date-col`, `--csv-amount-col`, `--csv-payee-col` to map them.

### Credit card questions

See [credit-cards.md](credit-cards.md).

### Shared expenses

See [budgeting.md](budgeting.md) for joint account and partner contribution strategies.

### "My balance doesn't match the bank"

When fscl's account balance differs from the user's bank balance:

1. Check the account balance with a cutoff date matching the bank statement: `fscl accounts balance <id> --cutoff <statement-date> --json`
2. Compare to the bank's statement balance
3. If they differ, common causes:
   - **Pending transactions:** Bank shows pending charges not yet imported — wait for them to clear and re-import
   - **Duplicate import:** Same file imported twice — find and delete duplicates: `fscl transactions list <id> --start <date> --end <date> --json`
   - **Missing transactions:** Manual entries not recorded — add them with `fscl transactions add`
   - **Wrong amount:** A transaction was entered incorrectly — fix with `fscl transactions edit draft` / `edit apply`
4. After fixing, verify: `fscl accounts balance <id> --json`

## Proactive Monitoring

Flag these issues without being asked:

**Immediate:**
- Overspent categories (negative balance)
- Large uncategorized transactions (>$100)
- Negative "To Budget" (over-budgeted)
- Overdue schedules

**Monthly:**
- Categories consistently over/under budget for 2-3 months → suggest permanent adjustment
- Unused categories (2+ months with $0 spent) → suggest removal
- Payees appearing 3+ times without a rule → create one
- Duplicate payees → merge

**Quarterly:**
- Spending trends across months (use [query-library.md](query-library.md) for trend queries)
- Savings goal progress
- Debt paydown progress (if applicable)


path: references/workflow-onboarding.md
content: # Workflow: New Budget Onboarding

Guide the user through setting up their first budget. This is a conversation — ask questions, explain decisions, and take action on their behalf.

## Step 1: Understand Their Situation

Ask these questions (adapt based on what they volunteer):

1. **Accounts:** "What accounts do you have? For each one, tell me institution, account type (checking/savings/credit card), last 4 digits or nickname, and current balance."
2. **Income:** "What's your monthly take-home pay? If it varies, use your lowest typical month."
3. **Fixed expenses:** "What bills are the same every month? (rent, insurance, subscriptions, car payment, etc.)"
4. **Debt:** "Are you carrying any credit card balances you can't pay off in full this month?"
5. **Lifestyle:** Use their answers to infer a persona (single renter, homeowner, couple, freelancer) for category template selection.

Don't ask everything at once — start with accounts and income, then drill into expenses as you build the budget. If any account type is unclear, ask a follow-up before creating accounts.

## Step 2: Create Accounts

Create every account they mention. Use negative balances for credit cards (the amount owed).

Normalize account names to include account type so future sessions stay unambiguous:

- `<Institution> <Type> <Last4-or-nickname>`
- Examples: `Chase Checking 5736`, `Ally Savings 1120`, `AmEx Credit 1008`

```bash
fscl accounts create-batch '[
  {"name": "Chase Checking 5736", "balance": "3500.00"},
  {"name": "Ally Savings 1120", "balance": "12000.00"},
  {"name": "Chase Credit 8891", "balance": "-840.00"}
]'
```

Credit cards must be on-budget (the default). If they have credit card debt they can't pay in full, see [credit-cards.md](credit-cards.md) for the debt tracking setup (rollover category + carryover flag).

## Step 3: Set Up Categories

Select a template from [budgeting.md](budgeting.md) based on their persona. Adapt it — remove categories they won't use, add ones they mention.

```bash
# Create groups
fscl categories create-group "Fixed Expenses"
fscl categories create-group "Food"
fscl categories create-group "Transportation"
fscl categories create-group "Personal"
fscl categories create-group "Savings & Goals"

# Generate full tree draft, then edit
fscl categories draft
# Edit categories.json:
# - top-level rows are category groups; nested rows are categories (not nested categories)
# - add rows without id to create groups/categories
# - change name on rows with id to rename
# - move category rows between groups to reassign
fscl categories apply --dry-run
fscl categories apply
```

Present the proposed categories to the user before creating: "Based on what you've told me, here's how I'd organize your budget. Does this look right?"

## Step 4: Set Initial Budget Amounts

Use their income and fixed expenses to build the first budget. As a starting point, apply the 50/30/20 split:

| Bucket | % of Income | What goes here |
|---|---|---|
| **Needs** | 50% | Housing, utilities, groceries, insurance, transportation |
| **Wants** | 30% | Dining, entertainment, subscriptions, hobbies |
| **Savings** | 20% | Emergency fund, savings goals, extra debt payments |

Start with known fixed expenses (rent, insurance, car payment). Subtract from income to see what's left for variable categories. Budget variable categories conservatively. Put the remainder toward savings.

```bash
fscl month draft <current-month>
# Edit the draft — set amount for each category as a decimal string (e.g. "500.00")
fscl month apply <current-month>
```

Present a summary before applying: "Here's your budget for February — does this look right?"

Tell the user: **"This is a starting point. After a month of tracking actual spending, we'll adjust based on real numbers."**

Verify the budget:

```bash
fscl month show <current-month> --json
```

## Step 5: Import Transactions

Ask: "Do you have any bank export files? Most banks let you download OFX, QFX, or CSV files from the last few months. This helps us see where your money has been going."

If they don't have files yet, give them directions:
- "Log into each bank account online"
- "Look for 'Download Transactions' or 'Export' — choose OFX or QFX if available, CSV as fallback"
- "Download the last 1-2 months"
- "Come back with the files and I'll import them"

If they have files:

```bash
# Preview first
fscl transactions import <account-id> <file> --dry-run --report

# Import
fscl transactions import <account-id> <file> --report
```

For CSV files that need column mapping, see [import-guide.md](import-guide.md).

## Step 6: Process Imported Transactions

After import, clean up in this order — payees first, then categorize, then rules:

```bash
# 1. Run any existing rules
fscl rules run --and-commit

# 2. Review uncategorized transactions for messy payee names
fscl transactions uncategorized --json

# 3. Clean payees — rename ugly bank names, merge duplicates
fscl payees update <id> --name "Whole Foods"
fscl payees merge <clean-id> <duplicate-id-1> <duplicate-id-2>

# 4. Categorize remaining uncategorized transactions
fscl transactions categorize draft
# Fill in category IDs in categorize.json using _meta context
# Do not hand-create categorize.json; edit the file generated by the draft command
fscl transactions categorize apply

# 5. Create rules for recurring payees so future imports auto-categorize
fscl rules draft
# Add new rules (rows without id), update existing (rows with id)
fscl rules apply
fscl rules run --and-commit
```

For rule creation patterns (pre-stage payee cleanup + default-stage categorization), see [rules.md](rules.md).

Report results to the user: "I imported 47 transactions. 39 were auto-categorized by rules. I categorized 6 more and need your input on 2."

## What's Next

Once setup is complete, tell the user:
- "Your budget is set up! Going forward, import new transactions periodically and I'll help categorize them."
- "At the start of each month, we'll review your spending and set the next month's budget."
- "The more transactions you import, the smarter the rules get — eventually most categorization happens automatically."


path: references/workflow-optimization.md
content: # Workflow: Budget Optimization

The user connected fscl to an existing Actual Budget. Accounts, transactions, and categories already exist, but fscl automation (rules, payee cleanup) hasn't been set up. Your job: audit the current state, clean it up, and build automation.

## Step 1: Audit Current State

Gather the full picture:

```bash
fscl accounts list --json
fscl categories list --json
fscl transactions uncategorized --json
fscl payees stats --extended --json
fscl rules list --json
fscl month status --json
```

Assess and report to the user:
- How many accounts? Are balances reasonable?
- How many categories? Any duplicates or obvious gaps?
- How many uncategorized transactions?
- How many payees? Are there messy bank names?
- Any rules already set up?
- Current budget status — is money allocated?

Present a summary: "Here's what I found in your budget. I have some suggestions to clean things up — want to go through them?"

## Step 2: Category Cleanup

Compare against templates in [budgeting.md](budgeting.md) — don't force a template, but use it to spot gaps.

Look for:
- **Duplicates** (e.g., "Food" and "Foods") → merge: `fscl categories delete <dup-id> --transfer-to <keep-id> --yes`
- **Empty groups** → delete if unused
- **Missing categories** → suggest additions based on transaction patterns
- **Miscategorized income** → ensure income categories have the `--income` flag

Confirm changes with the user before making them.

## Step 3: Payee Cleanup

Use payee stats to find the worst offenders:

```bash
fscl payees stats --extended --json
```

Look for:
- Multiple payees for the same merchant (e.g., "AMZN MKTP US", "AMAZON.COM", "AMZN DIGITAL")
- Ugly bank names that should be human-readable

Rename and merge:

```bash
fscl payees update <id> --name "Amazon"
fscl payees merge <amazon-id> <amzn-mktp-id> <amzn-digital-id>
```

## Step 4: Process Uncategorized Transactions

If `transactions.uncategorized > 0`, work through them:

```bash
# Categorize (payee names are now clean from Step 3)
fscl transactions categorize draft
# Fill in category IDs using _meta context
# Do not create categorize.json manually; edit the generated draft file
fscl transactions categorize apply
```

For large backlogs, use `--limit` to work in batches:

```bash
fscl transactions categorize draft --limit 50
```

## Step 5: Create Rules

After cleaning payees and categorizing, build rules for recurring patterns so future imports auto-categorize.

Use the draft/apply pattern:

```bash
fscl rules draft
# Edit rules.json — add new rules (no id field), update existing (with id)
fscl rules apply
fscl rules run --and-commit
```

For high-volume payees with name variations, create pre-stage rules:

```bash
fscl rules create '{
  "stage": "pre",
  "conditionsOp": "and",
  "conditions": [{"field": "imported_payee", "op": "matches", "value": "AMZN|AMAZON"}],
  "actions": [{"field": "payee", "op": "set", "value": "<amazon-payee-id>"}]
}'
```

See [rules.md](rules.md) for the full schema (conditions, actions, stages).

## Step 6: Review Budget Amounts

Compare actual spending to budgeted amounts:

```bash
fscl month status --compare 3 --json
```

Suggest adjustments for categories that are consistently over or under budget.

If the budget has no amounts set for the current month, offer to set them up using the approach in [workflow-onboarding.md](workflow-onboarding.md) Step 4.

## Step 7: Credit Card Check

If any accounts have negative balances (credit cards), verify the setup:
- **Paying in full monthly?** No special setup needed — purchases use regular expense categories.
- **Carrying debt?** See [credit-cards.md](credit-cards.md) for rollover category + carryover setup.

## What's Next

Once optimization is complete, tell the user:
- "Your budget is cleaned up! I've set up rules so future imports will auto-categorize most transactions."
- "Going forward, import new transactions periodically and I'll handle the rest."
- "At the start of each month, we'll review spending and set the next month's budget."


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)