jaz-clio 4.23.0 → 4.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,23 +3,40 @@
3
3
  <p align="center">
4
4
  <a href="https://www.npmjs.com/package/jaz-clio"><img src="https://img.shields.io/npm/v/jaz-clio?style=for-the-badge&logo=npm" alt="npm"></a>
5
5
  <a href="https://www.npmjs.com/package/jaz-clio"><img src="https://img.shields.io/npm/dm/jaz-clio?style=for-the-badge&label=downloads" alt="npm downloads"></a>
6
+ <img src="https://img.shields.io/badge/tools-203-blue?style=for-the-badge" alt="203 Tools">
6
7
  <img src="https://img.shields.io/badge/calculators-13-red?style=for-the-badge" alt="13 Calculators">
7
8
  <img src="https://img.shields.io/badge/jobs-12-teal?style=for-the-badge" alt="12 Jobs">
8
9
  <a href="https://github.com/teamtinvio/jaz-ai/blob/main/LICENSE"><img src="https://img.shields.io/github/license/teamtinvio/jaz-ai?style=for-the-badge&color=green" alt="License"></a>
9
10
  </p>
10
11
 
11
- CLI for the [Jaz](https://jaz.ai) accounting platform. Create invoices, record bills, manage contacts, run financial calculators, execute accounting jobs, and install AI agent skills — all from your terminal.
12
+ **Complete agent stack for all of Jaz, built for AI agents and accountants.**
13
+ Create invoices, record bills, manage contacts, run financial calculators, execute accounting jobs, and install AI agent skills — all from your terminal.
12
14
 
13
15
  > Also fully compatible with [Juan Accounting](https://juan.ac).
14
16
 
15
- ## Prerequisites
17
+ ## Contents
16
18
 
17
- **Node.js 18 or later** is required. If `node --version` works, skip ahead.
19
+ - [Quickstart](#quickstart)
20
+ - [Install](#install)
21
+ - [Authenticate](#authenticate)
22
+ - [Commands](#commands)
23
+ - [MCP Server](#mcp-server)
24
+ - [Install AI Skills](#install-ai-skills)
25
+ - [Privacy & Security](#privacy--security)
26
+ - [Support](#support)
18
27
 
19
- Otherwise: download the LTS installer from [nodejs.org](https://nodejs.org). It includes `npm`.
28
+ ## Quickstart
29
+
30
+ ```bash
31
+ npm install -g jaz-clio
32
+ clio auth add <your-api-key> # Get key from Settings > API in Jaz app
33
+ clio invoices list # You're in
34
+ ```
20
35
 
21
36
  ## Install
22
37
 
38
+ **Node.js 18+** required. If `node --version` works, you're set. Otherwise: [nodejs.org](https://nodejs.org) (LTS).
39
+
23
40
  ```bash
24
41
  npm install -g jaz-clio
25
42
  ```
@@ -27,18 +44,43 @@ npm install -g jaz-clio
27
44
  ## Authenticate
28
45
 
29
46
  ```bash
30
- # Add your Jaz API key (get one from Settings > API in the Jaz app)
31
- clio auth add <your-api-key>
47
+ clio auth add <your-api-key> # Add your Jaz API key
48
+ clio auth whoami # Verify it works
49
+ ```
50
+
51
+ | I have... | Use |
52
+ |-----------|-----|
53
+ | API key from Jaz app | `clio auth add <key>` |
54
+ | Environment variable | `export JAZ_API_KEY=jk-...` |
55
+ | Multiple orgs | `clio auth add <key>` + `clio auth switch <label>` |
56
+
57
+ ### Auth Precedence
58
+
59
+ When multiple credentials are available, Clio resolves them in this order:
60
+
61
+ | Priority | Source | Set via |
62
+ |----------|--------|---------|
63
+ | 1 | `--api-key` flag | Explicit per-command |
64
+ | 2 | `JAZ_API_KEY` env var | Shell or `.env` |
65
+ | 3 | `--org` flag / `JAZ_ORG` env | Named profile lookup |
66
+ | 4 | Active profile | `clio auth switch` |
32
67
 
33
- # Verify it works
34
- clio auth whoami
68
+ > **Warning:** If `JAZ_API_KEY` is set in your shell, it overrides `--org` and the active profile. Unset it (`unset JAZ_API_KEY`) before switching tenants with `clio auth switch`, or use per-command `--api-key` instead.
35
69
 
36
- # Manage multiple orgs
37
- clio auth add <another-key> # Adds a second profile
38
- clio auth list # See all profiles
39
- clio auth switch <label> # Switch active org
70
+ <details>
71
+ <summary>All auth subcommands</summary>
72
+
73
+ ```bash
74
+ clio auth add <key> # Add API key (validates against API)
75
+ clio auth list # List all saved profiles
76
+ clio auth switch <label> # Switch active org
77
+ clio auth remove <label> # Remove a profile
78
+ clio auth whoami # Show current org
79
+ clio auth clear # Remove all profiles
40
80
  ```
41
81
 
82
+ </details>
83
+
42
84
  ## Commands
43
85
 
44
86
  ### Transactions
@@ -55,6 +97,7 @@ clio journals list # List journal entries
55
97
  clio cash-in list # List cash-in entries
56
98
  clio cash-out list # List cash-out entries
57
99
  clio cash-transfer list # List cash transfers
100
+ clio capsules list # List capsules (transaction groups)
58
101
  ```
59
102
 
60
103
  ### Contacts, Accounts & Items
@@ -65,11 +108,46 @@ clio contacts create --name "ACME Ltd" # Create a contact
65
108
  clio accounts list # Chart of accounts
66
109
  clio items list # Products and services
67
110
  clio tags list # Tracking tags
111
+ clio contact-groups list # Contact groups
112
+ clio custom-fields list # Custom field definitions
113
+ ```
114
+
115
+ ### Bank & Reconciliation
116
+
117
+ ```bash
118
+ clio bank import --file statement.csv # Import bank statement
119
+ clio bank accounts # List bank accounts
120
+ clio bank records # List bank records
121
+ clio bank add-records # Create bank records (JSON)
122
+ clio bank auto-recon # Auto-reconciliation
123
+ clio bank-rules list # List auto-tagging rules
124
+ clio bank-rules create # Create auto-tagging rule
125
+ ```
126
+
127
+ ### Fixed Assets
128
+
129
+ ```bash
130
+ clio fixed-assets list # List fixed assets (alias: clio fa list)
131
+ clio fixed-assets get <id> # Get fixed asset details
132
+ clio fixed-assets create # Register a new asset
133
+ clio fixed-assets sell <id> # Record asset sale
134
+ clio fixed-assets discard <id> # Discard/scrap an asset
135
+ clio fixed-assets transfer <id> # Transfer between accounts
136
+ clio fixed-assets undo-disposal <id> # Reverse a disposal
137
+ ```
138
+
139
+ ### Subscriptions
140
+
141
+ ```bash
142
+ clio subscriptions list # List recurring subscriptions (alias: clio subs)
143
+ clio subscriptions create # Create a subscription
144
+ clio subscriptions cancel <id> # Cancel a subscription
145
+ clio subscriptions search-scheduled # Search scheduled transactions
68
146
  ```
69
147
 
70
148
  ### Financial Calculators
71
149
 
72
- 10 IFRS-compliant calculators that output structured blueprints with journal entries, workings, and execution plans.
150
+ 13 IFRS-compliant calculators that output structured blueprints with journal entries, workings, and execution plans.
73
151
 
74
152
  ```bash
75
153
  clio calc loan --principal 100000 --rate 6 --term 60 --json
@@ -80,8 +158,23 @@ clio calc provision --amount 100000 --rate 4 --periods 24 --json
80
158
  clio calc fx-reval --account "USD Cash" --balance 10000 --old-rate 1.35 --new-rate 1.34 --json
81
159
  clio calc fixed-deposit --principal 100000 --rate 3.5 --term 12 --json
82
160
  clio calc disposal --cost 50000 --accum-dep 30000 --proceeds 15000 --json
83
- clio calc amortization --amount 12000 --periods 12 --start 2025-01-01 --json
84
- clio calc hire-purchase --value 80000 --term 48 --rate 4.5 --json
161
+ clio calc prepaid-expense --amount 12000 --periods 12 --start 2025-01-01 --json
162
+ clio calc deferred-revenue --amount 24000 --periods 12 --start 2025-01-01 --json
163
+ clio calc accrued-expense --amount 5000 --periods 3 --json
164
+ clio calc leave-accrual --employees 50 --avg-daily-rate 200 --avg-days 15 --json
165
+ clio calc dividend --amount 100000 --json
166
+ ```
167
+
168
+ ### Transaction Recipes (Capsule Transactions)
169
+
170
+ 13 recipe subcommands that compute, plan, and optionally execute multi-step transactions from calculator output.
171
+
172
+ ```bash
173
+ clio capsule-transaction loan --principal 100000 --rate 6 --term 60 # alias: clio ct loan
174
+ clio ct lease --payment 5000 --term 36 --rate 5
175
+ clio ct depreciation --cost 50000 --salvage 5000 --life 5
176
+ clio ct prepaid-expense --amount 12000 --periods 12 --start 2025-01-01
177
+ clio ct fx-reval --account "USD Cash" --balance 10000 --old-rate 1.35 --new-rate 1.34
85
178
  ```
86
179
 
87
180
  ### Accounting Jobs
@@ -103,23 +196,37 @@ clio jobs fa-review # Fixed asset re
103
196
  clio jobs statutory-filing sg-cs --ya 2026 --revenue 500000 --profit 120000 --json # SG Form C-S
104
197
  ```
105
198
 
106
- ### Other
199
+ ### Reports
200
+
201
+ 16 report types:
202
+
203
+ ```bash
204
+ clio reports generate <type> # Generate a report
205
+ clio reports pdf <type> # Download report as PDF
206
+ ```
207
+
208
+ Types: `trial-balance`, `balance-sheet`, `profit-loss`, `cashflow`, `aged-ar`, `aged-ap`, `cash-balance`, `general-ledger`, `vat-ledger`, `equity-movement`, `bank-balance-summary`, `bank-recon-summary`, `bank-recon-details`, `fa-summary`, `fa-recon-summary`, `ar-report`.
209
+
210
+ ### Other Commands
107
211
 
108
212
  ```bash
109
213
  clio org info # Current org details
214
+ clio org-users list # Organization members
110
215
  clio currencies list # Enabled currencies
111
216
  clio currency-rates list # Exchange rates
112
217
  clio payments search # Search payments
113
- clio bank import --file statement.csv # Import bank statement
114
- clio reports generate --type pnl # Generate reports
115
- clio capsules list # List capsules (transaction groups)
116
- clio schedulers list-invoices # List scheduled (recurring) invoices
117
- clio exports download --type INVOICES # Download data export
118
- clio attachments list <id> # List attachments on a transaction
119
- clio tax-profiles list # List tax profiles
120
- clio magic create --file receipt.pdf # AI-extract from attachment
121
- clio cashflow search # Search cashflow transactions
122
- clio context # Generate agent context
218
+ clio tax-profiles list # Tax profiles
219
+ clio cashflow search # Search cashflow transactions
220
+ clio schedulers list-invoices # Scheduled (recurring) invoices
221
+ clio exports download --type INVOICES # Download data export
222
+ clio attachments list <id> # List attachments on a transaction
223
+ clio bookmarks list # Saved bookmarks
224
+ clio inventory items # Inventory items
225
+ clio inventory balance <item-id> # Inventory balance for an item
226
+ clio search "query" # Universal cross-entity search
227
+ clio magic create --file receipt.pdf # AI-extract from attachment
228
+ clio kb "topic" # Search help center (alias: clio hc)
229
+ clio context # Generate agent context summary
123
230
  clio versions # Show version info
124
231
  clio update # Update to latest
125
232
  ```
@@ -128,7 +235,7 @@ Every command supports `--json` for structured output — ideal for piping to ot
128
235
 
129
236
  ## MCP Server
130
237
 
131
- Expose all 203 CLI tools to AI coworking and coding agents via the Model Context Protocol (MCP). The server runs locally on your machine — no cloud, no ports. API calls go directly from your machine to the Jaz API.
238
+ Expose all 203 CLI tools to AI coding and coworking agents via the Model Context Protocol (MCP). The server runs locally on your machine — no cloud, no ports. API calls go directly from your machine to the Jaz API.
132
239
 
133
240
  **Claude Code:**
134
241
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-api
3
- version: 4.23.0
3
+ version: 4.24.0
4
4
  description: Complete reference for the Jaz REST API — the accounting platform backend. Use this skill whenever building, modifying, debugging, or extending any code that calls the API — including API clients, integrations, data seeding, test data, or new endpoint work. Contains every field name, response shape, error, gotcha, and edge case discovered through live production testing.
5
5
  license: MIT
6
6
  compatibility: Requires Jaz API key (x-jk-api-key header). Works with Claude Code, Google Antigravity, OpenAI Codex, GitHub Copilot, Cursor, and any agent that reads markdown.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-conversion
3
- version: 4.23.0
3
+ version: 4.24.0
4
4
  description: Accounting data conversion skill — migrates customer data from Xero, QuickBooks, Sage, MYOB, and Excel exports to Jaz. Covers config, quick, and full conversion workflows, Excel parsing, CoA/contact/tax/items mapping, clearing accounts, TTB, and TB verification.
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-jobs
3
- version: 4.23.0
3
+ version: 4.24.0
4
4
  description: 12 accounting jobs for SMB bookkeepers and accountants — month-end, quarter-end, and year-end close playbooks plus 9 ad-hoc operational jobs (bank recon, document collection, GST/VAT filing, payment runs, credit control, supplier recon, audit prep, fixed asset review, statutory filing). Jobs can have paired tools as nested subcommands (e.g., `clio jobs bank-recon match`, `clio jobs document-collection ingest`, `clio jobs statutory-filing sg-cs`). Paired with an interactive CLI blueprint generator (clio jobs).
5
5
  license: MIT
6
6
  compatibility: Works with Claude Code, Claude Cowork, Claude.ai, and any agent that reads markdown. For API payloads, load the jaz-api skill. For individual transaction patterns, load the jaz-recipes skill.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-recipes
3
- version: 4.23.0
3
+ version: 4.24.0
4
4
  description: 16 IFRS-compliant recipes for complex multi-step accounting in Jaz — prepaid amortization, deferred revenue, loan schedules, IFRS 16 leases, hire purchase, fixed deposits, asset disposal, FX revaluation, ECL provisioning, IAS 37 provisions, dividends, intercompany, and capital WIP. Each recipe includes journal entries, capsule structure, and verification steps. Paired with 13 financial calculators that produce execution-ready blueprints with workings.
5
5
  license: MIT
6
6
  compatibility: Works with Claude Code, Claude Cowork, Claude.ai, and any agent that reads markdown. For API payloads, load the jaz-api skill alongside this one.
@@ -1,8 +1,16 @@
1
1
  import chalk from 'chalk';
2
2
  import { listAccounts, searchAccounts, createAccount, deleteAccount } from '../core/api/chart-of-accounts.js';
3
3
  import { apiAction } from './api-action.js';
4
+ import { outputList } from './output.js';
4
5
  import { parsePositiveInt, parseNonNegativeInt, readBodyInput, requireFields } from './parsers.js';
5
- import { paginatedFetch, paginatedJson, displaySlice } from './pagination.js';
6
+ import { paginatedFetch } from './pagination.js';
7
+ import { formatId } from './format-helpers.js';
8
+ const ACCOUNTS_COLUMNS = [
9
+ { key: 'resourceId', header: 'ID', format: formatId },
10
+ { key: 'code', header: 'Code' },
11
+ { key: 'name', header: 'Name' },
12
+ { key: 'accountType', header: 'Type' },
13
+ ];
6
14
  export function registerAccountsCommand(program) {
7
15
  const accounts = program
8
16
  .command('accounts')
@@ -16,21 +24,11 @@ export function registerAccountsCommand(program) {
16
24
  .option('--all', 'Fetch all pages')
17
25
  .option('--max-rows <n>', 'Max rows for --all (default 10000)', parsePositiveInt)
18
26
  .option('--api-key <key>', 'API key (overrides stored/env)')
27
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
19
28
  .option('--json', 'Output as JSON')
20
29
  .action(apiAction(async (client, opts) => {
21
30
  const result = await paginatedFetch(opts, (p) => listAccounts(client, p), { label: 'Fetching accounts' });
22
- if (opts.json) {
23
- console.log(paginatedJson(result, opts));
24
- }
25
- else {
26
- console.log(chalk.bold(`Accounts (${result.data.length} of ${result.totalElements}):\n`));
27
- const { items, overflow } = displaySlice(result.data);
28
- for (const a of items) {
29
- console.log(` ${chalk.cyan(a.resourceId)} ${a.code ?? ''} ${a.name} ${chalk.dim(a.accountType)}`);
30
- }
31
- if (overflow > 0)
32
- console.log(chalk.dim(` ... and ${overflow.toLocaleString()} more (use --json for full output)`));
33
- }
31
+ outputList(result, ACCOUNTS_COLUMNS, opts, 'Accounts'); // eslint-disable-line @typescript-eslint/no-explicit-any
34
32
  }));
35
33
  // ── clio accounts search ────────────────────────────────────────
36
34
  accounts
@@ -43,27 +41,13 @@ export function registerAccountsCommand(program) {
43
41
  .option('--all', 'Fetch all pages')
44
42
  .option('--max-rows <n>', 'Max rows for --all (default 10000)', parsePositiveInt)
45
43
  .option('--api-key <key>', 'API key (overrides stored/env)')
44
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
46
45
  .option('--json', 'Output as JSON')
47
46
  .action((query, opts) => apiAction(async (client) => {
48
47
  const filter = { or: { name: { contains: query }, code: { contains: query } } };
49
48
  const sort = { sortBy: [opts.sort ?? 'code'], order: (opts.order ?? 'ASC') };
50
49
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchAccounts(client, { filter, limit, offset, sort }), { label: 'Searching accounts', defaultLimit: 20 });
51
- if (opts.json) {
52
- console.log(paginatedJson(result, opts));
53
- }
54
- else {
55
- if (result.data.length === 0) {
56
- console.log('No accounts found.');
57
- return;
58
- }
59
- console.log(chalk.bold(`Found ${result.data.length} account(s):\n`));
60
- const { items, overflow } = displaySlice(result.data);
61
- for (const a of items) {
62
- console.log(` ${chalk.cyan(a.resourceId)} ${a.code ?? ''} ${a.name} ${chalk.dim(a.accountType)}`);
63
- }
64
- if (overflow > 0)
65
- console.log(chalk.dim(` ... and ${overflow.toLocaleString()} more (use --json for full output)`));
66
- }
50
+ outputList(result, ACCOUNTS_COLUMNS, opts, 'Accounts'); // eslint-disable-line @typescript-eslint/no-explicit-any
67
51
  })(opts));
68
52
  // ── clio accounts create ──────────────────────────────────────
69
53
  accounts
@@ -76,6 +60,7 @@ export function registerAccountsCommand(program) {
76
60
  .option('--status <status>', 'Account status (ACTIVE or INACTIVE)')
77
61
  .option('--input <file>', 'Read full request body from JSON file (or pipe via stdin)')
78
62
  .option('--api-key <key>', 'API key (overrides stored/env)')
63
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
79
64
  .option('--json', 'Output as JSON')
80
65
  .action(apiAction(async (client, opts) => {
81
66
  const body = readBodyInput(opts);
@@ -113,6 +98,7 @@ export function registerAccountsCommand(program) {
113
98
  .command('delete <resourceId>')
114
99
  .description('Delete an account from the chart of accounts')
115
100
  .option('--api-key <key>', 'API key (overrides stored/env)')
101
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
116
102
  .option('--json', 'Output as JSON')
117
103
  .action((resourceId, opts) => apiAction(async (client) => {
118
104
  await deleteAccount(client, resourceId);
@@ -1,6 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { JazClient, JazApiError } from '../core/api/client.js';
3
3
  import { requireAuth, AuthError, resolvedProfileLabel, resolvedAuthSource, getProfile, listProfiles } from '../core/auth/index.js';
4
+ import { isMachineFormat } from './output.js';
4
5
  /**
5
6
  * Shared action wrapper for all online CLI commands.
6
7
  * Handles auth resolution, client creation, org banner, and error formatting.
@@ -16,9 +17,9 @@ export function apiAction(fn) {
16
17
  }
17
18
  const auth = requireAuth(opts.apiKey);
18
19
  const client = new JazClient(auth);
19
- // Org banner — show which org we're hitting (suppressed in --json mode)
20
+ // Org banner — show which org we're hitting (suppressed in machine formats)
20
21
  // Visual guard: yellow warning when unpinned + multi-org, dim banner otherwise
21
- if (!opts.json) {
22
+ if (!isMachineFormat(opts)) {
22
23
  const label = resolvedProfileLabel();
23
24
  if (label) {
24
25
  const entry = getProfile(label);
@@ -46,22 +47,23 @@ export function apiAction(fn) {
46
47
  await fn(client, opts, auth);
47
48
  }
48
49
  catch (err) {
50
+ const machine = isMachineFormat(opts);
49
51
  if (err instanceof AuthError) {
50
- if (opts.json)
52
+ if (machine)
51
53
  console.log(JSON.stringify({ error: { code: 'AUTH_ERROR', message: err.message } }));
52
54
  else
53
55
  console.error(chalk.red(`Error: ${err.message}`));
54
56
  process.exit(3);
55
57
  }
56
58
  if (err instanceof JazApiError) {
57
- if (opts.json)
59
+ if (machine)
58
60
  console.log(JSON.stringify({ error: { code: 'API_ERROR', status: err.status, message: err.message } }));
59
61
  else
60
62
  console.error(chalk.red(`API Error (${err.status}): ${err.message}`));
61
63
  process.exit(2);
62
64
  }
63
65
  const message = err.message;
64
- if (opts.json)
66
+ if (machine)
65
67
  console.log(JSON.stringify({ error: { code: 'UNKNOWN_ERROR', message } }));
66
68
  else
67
69
  console.error(chalk.red(`Error: ${message}`));
@@ -1,8 +1,14 @@
1
1
  import chalk from 'chalk';
2
2
  import { listBankRules, getBankRule, searchBankRules, createBankRule, updateBankRule, deleteBankRule, } from '../core/api/bank-rules.js';
3
3
  import { apiAction } from './api-action.js';
4
+ import { outputList } from './output.js';
4
5
  import { parsePositiveInt, parseNonNegativeInt, readBodyInput } from './parsers.js';
5
- import { paginatedFetch, paginatedJson, displaySlice } from './pagination.js';
6
+ import { paginatedFetch } from './pagination.js';
7
+ import { formatId } from './format-helpers.js';
8
+ const BANK_RULES_COLUMNS = [
9
+ { key: 'resourceId', header: 'ID', format: formatId },
10
+ { key: 'name', header: 'Name' },
11
+ ];
6
12
  export function registerBankRulesCommand(program) {
7
13
  const cmd = program
8
14
  .command('bank-rules')
@@ -14,24 +20,17 @@ export function registerBankRulesCommand(program) {
14
20
  .option('--offset <n>', 'Offset', parseNonNegativeInt)
15
21
  .option('--all', 'Fetch all pages')
16
22
  .option('--api-key <key>', 'API key')
23
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
17
24
  .option('--json', 'JSON output')
18
25
  .action(apiAction(async (client, opts) => {
19
26
  const result = await paginatedFetch(opts, (p) => listBankRules(client, p), { label: 'Fetching bank rules' });
20
- if (opts.json) {
21
- console.log(paginatedJson(result, opts));
22
- return;
23
- }
24
- console.log(chalk.bold(`Bank Rules (${result.data.length} of ${result.totalElements}):\n`));
25
- const { items, overflow } = displaySlice(result.data);
26
- for (const r of items)
27
- console.log(` ${chalk.cyan(r.resourceId)} ${r.name} ${chalk.dim(r.actionType)}`);
28
- if (overflow > 0)
29
- console.log(chalk.dim(` ... and ${overflow} more`));
27
+ outputList(result, BANK_RULES_COLUMNS, opts, 'Bank Rules'); // eslint-disable-line @typescript-eslint/no-explicit-any
30
28
  }));
31
29
  cmd
32
30
  .command('get <resourceId>')
33
31
  .description('Get a bank rule')
34
32
  .option('--api-key <key>', 'API key')
33
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
35
34
  .option('--json', 'JSON output')
36
35
  .action((resourceId, opts) => apiAction(async (client) => {
37
36
  const res = await getBankRule(client, resourceId);
@@ -49,26 +48,18 @@ export function registerBankRulesCommand(program) {
49
48
  .option('--limit <n>', 'Max results', parsePositiveInt)
50
49
  .option('--offset <n>', 'Offset', parseNonNegativeInt)
51
50
  .option('--api-key <key>', 'API key')
51
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
52
52
  .option('--json', 'JSON output')
53
53
  .action((query, opts) => apiAction(async (client) => {
54
54
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchBankRules(client, { filter: { name: { contains: query } }, limit, offset }), { label: 'Searching bank rules', defaultLimit: 20 });
55
- if (opts.json) {
56
- console.log(paginatedJson(result, opts));
57
- return;
58
- }
59
- if (result.data.length === 0) {
60
- console.log('No bank rules found.');
61
- return;
62
- }
63
- console.log(chalk.bold(`Found ${result.data.length} rule(s):\n`));
64
- for (const r of result.data)
65
- console.log(` ${chalk.cyan(r.resourceId)} ${r.name}`);
55
+ outputList(result, BANK_RULES_COLUMNS, opts, 'Bank Rules'); // eslint-disable-line @typescript-eslint/no-explicit-any
66
56
  })(opts));
67
57
  cmd
68
58
  .command('create')
69
59
  .description('Create a bank rule')
70
60
  .option('--input <file>', 'Read request body from JSON file')
71
61
  .option('--api-key <key>', 'API key')
62
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
72
63
  .option('--json', 'JSON output')
73
64
  .action(apiAction(async (client, opts) => {
74
65
  const body = readBodyInput(opts);
@@ -91,6 +82,7 @@ export function registerBankRulesCommand(program) {
91
82
  .option('--name <name>', 'New name')
92
83
  .option('--input <file>', 'Read full update body from JSON file')
93
84
  .option('--api-key <key>', 'API key')
85
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
94
86
  .option('--json', 'JSON output')
95
87
  .action((resourceId, opts) => apiAction(async (client) => {
96
88
  const body = readBodyInput(opts);
@@ -114,6 +106,7 @@ export function registerBankRulesCommand(program) {
114
106
  .command('delete <resourceId>')
115
107
  .description('Delete a bank rule')
116
108
  .option('--api-key <key>', 'API key')
109
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
117
110
  .option('--json', 'JSON output')
118
111
  .action((resourceId, opts) => apiAction(async (client) => {
119
112
  await deleteBankRule(client, resourceId);
@@ -20,6 +20,7 @@ export function registerBankCommand(program) {
20
20
  .command('accounts')
21
21
  .description('List bank accounts')
22
22
  .option('--limit <n>', 'Max results', parsePositiveInt)
23
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
23
24
  .option('--api-key <key>', 'API key (overrides stored/env)')
24
25
  .option('--json', 'Output as JSON')
25
26
  .action(apiAction(async (client, opts) => {
@@ -40,6 +41,7 @@ export function registerBankCommand(program) {
40
41
  bank
41
42
  .command('get <resourceId>')
42
43
  .description('Get a bank account by resourceId')
44
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
43
45
  .option('--api-key <key>', 'API key (overrides stored/env)')
44
46
  .option('--json', 'Output as JSON')
45
47
  .action((resourceId, opts) => apiAction(async (client) => {
@@ -69,6 +71,7 @@ export function registerBankCommand(program) {
69
71
  .option('--amount-min <n>', 'Filter by minimum amount', parseFloat)
70
72
  .option('--amount-max <n>', 'Filter by maximum amount', parseFloat)
71
73
  .option('--limit <n>', 'Max results (default 50)', parsePositiveInt)
74
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
72
75
  .option('--api-key <key>', 'API key (overrides stored/env)')
73
76
  .option('--json', 'Output as JSON')
74
77
  .action((accountResourceId, opts) => apiAction(async (client) => {
@@ -112,6 +115,7 @@ export function registerBankCommand(program) {
112
115
  .command('add-records <accountResourceId>')
113
116
  .description('Add bank records via JSON (1-100 records per call)')
114
117
  .requiredOption('--records <json>', 'JSON array: [{amount, transactionDate, description?, payerOrPayee?, reference?}]')
118
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
115
119
  .option('--api-key <key>', 'API key (overrides stored/env)')
116
120
  .option('--json', 'Output as JSON')
117
121
  .action((accountResourceId, opts) => apiAction(async (client) => {
@@ -151,6 +155,7 @@ export function registerBankCommand(program) {
151
155
  .description('Get auto-reconciliation recommendations for a bank account')
152
156
  .requiredOption('--type <type>', `Recommendation type (${RECON_TYPES.join(', ')})`)
153
157
  .option('--account <resourceId>', 'Bank account resourceId (omit for all accounts)')
158
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
154
159
  .option('--api-key <key>', 'API key (overrides stored/env)')
155
160
  .option('--json', 'Output as JSON')
156
161
  .action(apiAction(async (client, opts) => {
@@ -202,6 +207,7 @@ export function registerBankCommand(program) {
202
207
  .description('Import a bank statement file (CSV, OFX, XLS, XLSX)')
203
208
  .requiredOption('--file <path>', 'Bank statement file path')
204
209
  .requiredOption('--account <resourceId>', 'Bank account resourceId')
210
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
205
211
  .option('--api-key <key>', 'API key (overrides stored/env)')
206
212
  .option('--json', 'Output as JSON')
207
213
  .action(apiAction(async (client, opts) => {
@@ -1,12 +1,20 @@
1
1
  import chalk from 'chalk';
2
- import { formatStatus } from './format-helpers.js';
2
+ import { formatStatus, formatId, formatReference, formatCurrency } from './format-helpers.js';
3
3
  import { listBills, getBill, searchBills, createBill, updateBill, deleteBill, createBillPayment, applyCreditsToBill, finalizeBill, } from '../core/api/bills.js';
4
4
  import { listAttachments } from '../core/api/attachments.js';
5
5
  import { apiAction } from './api-action.js';
6
6
  import { resolveContactFlag, resolveAccountFlag, resolveTaxProfileFlag } from './resolve.js';
7
7
  import { parsePositiveInt, parseNonNegativeInt, parseMoney, parseRate, parseLineItems, parseCustomFields, readBodyInput, requireFields } from './parsers.js';
8
- import { paginatedFetch, paginatedJson, displaySlice } from './pagination.js';
8
+ import { paginatedFetch } from './pagination.js';
9
+ import { outputList } from './output.js';
9
10
  import { BILL_REQUIRED_FIELDS, buildDraftReport, formatDraftTable, addDraftFinalizeOptions, mergeDraftFlags, validateDraft, buildValidation, normalizeDate, sanitizeLineItem, } from './draft-helpers.js';
11
+ const BILL_COLUMNS = [
12
+ { key: 'resourceId', header: 'ID', format: formatId },
13
+ { key: 'reference', header: 'Reference', format: formatReference },
14
+ { key: 'status', header: 'Status', format: (v) => formatStatus(String(v)) },
15
+ { key: 'totalAmount', header: 'Amount', align: 'right', format: formatCurrency },
16
+ { key: 'valueDate', header: 'Date', format: (v) => normalizeDate(String(v)) ?? '-' },
17
+ ];
10
18
  export function registerBillsCommand(program) {
11
19
  const bills = program
12
20
  .command('bills')
@@ -19,24 +27,12 @@ export function registerBillsCommand(program) {
19
27
  .option('--offset <n>', 'Page number offset (0-indexed)', parseNonNegativeInt)
20
28
  .option('--all', 'Fetch all pages')
21
29
  .option('--max-rows <n>', 'Max rows for --all (default 10000)', parsePositiveInt)
30
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
22
31
  .option('--api-key <key>', 'API key (overrides stored/env)')
23
32
  .option('--json', 'Output as JSON')
24
33
  .action(apiAction(async (client, opts) => {
25
34
  const result = await paginatedFetch(opts, (p) => listBills(client, p), { label: 'Fetching bills' });
26
- if (opts.json) {
27
- console.log(paginatedJson(result, opts));
28
- }
29
- else {
30
- console.log(chalk.bold(`Bills (${result.data.length} of ${result.totalElements}):\n`));
31
- const { items, overflow } = displaySlice(result.data);
32
- for (const b of items) {
33
- const amount = b.totalAmount !== undefined ? chalk.dim(` $${b.totalAmount.toFixed(2)}`) : '';
34
- const status = formatStatus(b.status);
35
- console.log(` ${chalk.cyan(b.resourceId)} ${b.reference || '(no ref)'} ${status}${amount} ${normalizeDate(b.valueDate)}`);
36
- }
37
- if (overflow > 0)
38
- console.log(chalk.dim(` ... and ${overflow.toLocaleString()} more (use --json for full output)`));
39
- }
35
+ outputList(result, BILL_COLUMNS, opts, 'Bills'); // eslint-disable-line @typescript-eslint/no-explicit-any
40
36
  }));
41
37
  // ── clio bills get ──────────────────────────────────────────────
42
38
  bills
@@ -87,6 +83,7 @@ export function registerBillsCommand(program) {
87
83
  .option('--offset <n>', 'Page number offset (0-indexed)', parseNonNegativeInt)
88
84
  .option('--all', 'Fetch all pages')
89
85
  .option('--max-rows <n>', 'Max rows for --all (default 10000)', parsePositiveInt)
86
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
90
87
  .option('--api-key <key>', 'API key (overrides stored/env)')
91
88
  .option('--json', 'Output as JSON')
92
89
  .action(apiAction(async (client, opts) => {
@@ -113,24 +110,7 @@ export function registerBillsCommand(program) {
113
110
  const searchFilter = Object.keys(filter).length > 0 ? filter : undefined;
114
111
  const sort = { sortBy: [opts.sort ?? 'valueDate'], order: (opts.order ?? 'DESC') };
115
112
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchBills(client, { filter: searchFilter, limit, offset, sort }), { label: 'Searching bills', defaultLimit: 20 });
116
- if (opts.json) {
117
- console.log(paginatedJson(result, opts));
118
- }
119
- else {
120
- if (result.data.length === 0) {
121
- console.log('No bills found.');
122
- return;
123
- }
124
- console.log(chalk.bold(`Found ${result.data.length} bill(s):\n`));
125
- const { items, overflow } = displaySlice(result.data);
126
- for (const b of items) {
127
- const amount = b.totalAmount !== undefined ? chalk.dim(` $${b.totalAmount.toFixed(2)}`) : '';
128
- const status = formatStatus(b.status);
129
- console.log(` ${chalk.cyan(b.resourceId)} ${b.reference || '(no ref)'} ${status}${amount} ${normalizeDate(b.valueDate)}`);
130
- }
131
- if (overflow > 0)
132
- console.log(chalk.dim(` ... and ${overflow.toLocaleString()} more (use --json for full output)`));
133
- }
113
+ outputList(result, BILL_COLUMNS, opts, 'Bills'); // eslint-disable-line @typescript-eslint/no-explicit-any
134
114
  }));
135
115
  // ── clio bills create ───────────────────────────────────────────
136
116
  bills