jaz-clio 4.32.5 → 4.33.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,7 +3,7 @@
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-204-blue?style=for-the-badge" alt="204 Tools">
6
+ <img src="https://img.shields.io/badge/tools-211-blue?style=for-the-badge" alt="211 Tools">
7
7
  <img src="https://img.shields.io/badge/calculators-13-red?style=for-the-badge" alt="13 Calculators">
8
8
  <img src="https://img.shields.io/badge/jobs-12-teal?style=for-the-badge" alt="12 Jobs">
9
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>
@@ -235,7 +235,7 @@ Every command supports `--json` for structured output — ideal for piping to ot
235
235
 
236
236
  ## MCP Server
237
237
 
238
- Expose all 211 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.
238
+ Expose all 235 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.
239
239
 
240
240
  **Claude Code:**
241
241
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-api
3
- version: 4.32.5
3
+ version: 4.33.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.32.5
3
+ version: 4.33.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.32.5
3
+ version: 4.33.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.32.5
3
+ version: 4.33.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,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { formatStatus, formatId, formatReference, formatCurrency } from './format-helpers.js';
3
- import { listBills, getBill, searchBills, createBill, updateBill, deleteBill, createBillPayment, applyCreditsToBill, finalizeBill, } from '../core/api/bills.js';
3
+ import { listBills, getBill, searchBills, createBill, updateBill, deleteBill, createBillPayment, applyCreditsToBill, finalizeBill, listBillPayments, listBillCredits, reverseBillCredit, } 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';
@@ -579,4 +579,65 @@ export function registerBillsCommand(program) {
579
579
  }
580
580
  }
581
581
  })(opts));
582
+ // ── clio bills list-payments ────────────────────────────────────
583
+ bills
584
+ .command('list-payments <resourceId>')
585
+ .description('List payments recorded against a bill')
586
+ .option('--api-key <key>', 'API key (overrides stored/env)')
587
+ .option('--json', 'Output as JSON')
588
+ .action((resourceId, opts) => apiAction(async (client) => {
589
+ const res = await listBillPayments(client, resourceId);
590
+ if (opts.json) {
591
+ console.log(JSON.stringify(res.data, null, 2));
592
+ }
593
+ else {
594
+ const payments = res.data;
595
+ if (payments.length === 0) {
596
+ console.log('No payments recorded for this bill.');
597
+ return;
598
+ }
599
+ console.log(chalk.bold(`Payments for bill ${resourceId}:\n`));
600
+ for (const p of payments) { // eslint-disable-line @typescript-eslint/no-explicit-any
601
+ console.log(` ${chalk.cyan(p.resourceId)} ${p.paymentAmount ?? p.amount ?? ''} on ${p.valueDate ?? ''}`);
602
+ }
603
+ }
604
+ })(opts));
605
+ // ── clio bills list-credits ─────────────────────────────────────
606
+ bills
607
+ .command('list-credits <resourceId>')
608
+ .description('List credit notes applied to a bill')
609
+ .option('--api-key <key>', 'API key (overrides stored/env)')
610
+ .option('--json', 'Output as JSON')
611
+ .action((resourceId, opts) => apiAction(async (client) => {
612
+ const res = await listBillCredits(client, resourceId);
613
+ if (opts.json) {
614
+ console.log(JSON.stringify(res.data, null, 2));
615
+ }
616
+ else {
617
+ const credits = res.data;
618
+ if (credits.length === 0) {
619
+ console.log('No credits applied to this bill.');
620
+ return;
621
+ }
622
+ console.log(chalk.bold(`Credits for bill ${resourceId}:\n`));
623
+ for (const c of credits) { // eslint-disable-line @typescript-eslint/no-explicit-any
624
+ console.log(` ${chalk.cyan(c.resourceId)} ${c.amount ?? ''}`);
625
+ }
626
+ }
627
+ })(opts));
628
+ // ── clio bills reverse-credit ───────────────────────────────────
629
+ bills
630
+ .command('reverse-credit <resourceId> <creditResourceId>')
631
+ .description('Reverse (unapply) a supplier credit note from a bill')
632
+ .option('--api-key <key>', 'API key (overrides stored/env)')
633
+ .option('--json', 'Output as JSON')
634
+ .action((resourceId, creditResourceId, opts) => apiAction(async (client) => {
635
+ await reverseBillCredit(client, resourceId, creditResourceId);
636
+ if (opts.json) {
637
+ console.log(JSON.stringify({ reversed: true, resourceId, creditResourceId }));
638
+ }
639
+ else {
640
+ console.log(chalk.green(`Credit ${creditResourceId} reversed from bill ${resourceId}.`));
641
+ }
642
+ })(opts));
582
643
  }
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { formatStatus, formatId, formatReference, formatCurrency } from './format-helpers.js';
3
- import { listInvoices, getInvoice, searchInvoices, createInvoice, updateInvoice, deleteInvoice, createInvoicePayment, applyCreditsToInvoice, downloadInvoicePdf, finalizeInvoice, } from '../core/api/invoices.js';
3
+ import { listInvoices, getInvoice, searchInvoices, createInvoice, updateInvoice, deleteInvoice, createInvoicePayment, applyCreditsToInvoice, downloadInvoicePdf, finalizeInvoice, listInvoicePayments, listInvoiceCredits, reverseInvoiceCredit, } from '../core/api/invoices.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';
@@ -556,4 +556,65 @@ export function registerInvoicesCommand(program) {
556
556
  }
557
557
  }
558
558
  })(opts));
559
+ // ── clio invoices list-payments ─────────────────────────────────
560
+ invoices
561
+ .command('list-payments <resourceId>')
562
+ .description('List payments recorded against an invoice')
563
+ .option('--api-key <key>', 'API key (overrides stored/env)')
564
+ .option('--json', 'Output as JSON')
565
+ .action((resourceId, opts) => apiAction(async (client) => {
566
+ const res = await listInvoicePayments(client, resourceId);
567
+ if (opts.json) {
568
+ console.log(JSON.stringify(res.data, null, 2));
569
+ }
570
+ else {
571
+ const payments = res.data;
572
+ if (payments.length === 0) {
573
+ console.log('No payments recorded for this invoice.');
574
+ return;
575
+ }
576
+ console.log(chalk.bold(`Payments for invoice ${resourceId}:\n`));
577
+ for (const p of payments) { // eslint-disable-line @typescript-eslint/no-explicit-any
578
+ console.log(` ${chalk.cyan(p.resourceId)} ${p.paymentAmount ?? p.amount ?? ''} on ${p.valueDate ?? ''}`);
579
+ }
580
+ }
581
+ })(opts));
582
+ // ── clio invoices list-credits ──────────────────────────────────
583
+ invoices
584
+ .command('list-credits <resourceId>')
585
+ .description('List credit notes applied to an invoice')
586
+ .option('--api-key <key>', 'API key (overrides stored/env)')
587
+ .option('--json', 'Output as JSON')
588
+ .action((resourceId, opts) => apiAction(async (client) => {
589
+ const res = await listInvoiceCredits(client, resourceId);
590
+ if (opts.json) {
591
+ console.log(JSON.stringify(res.data, null, 2));
592
+ }
593
+ else {
594
+ const credits = res.data;
595
+ if (credits.length === 0) {
596
+ console.log('No credits applied to this invoice.');
597
+ return;
598
+ }
599
+ console.log(chalk.bold(`Credits for invoice ${resourceId}:\n`));
600
+ for (const c of credits) { // eslint-disable-line @typescript-eslint/no-explicit-any
601
+ console.log(` ${chalk.cyan(c.resourceId)} ${c.amount ?? ''}`);
602
+ }
603
+ }
604
+ })(opts));
605
+ // ── clio invoices reverse-credit ────────────────────────────────
606
+ invoices
607
+ .command('reverse-credit <resourceId> <creditResourceId>')
608
+ .description('Reverse (unapply) a credit note from an invoice')
609
+ .option('--api-key <key>', 'API key (overrides stored/env)')
610
+ .option('--json', 'Output as JSON')
611
+ .action((resourceId, creditResourceId, opts) => apiAction(async (client) => {
612
+ await reverseInvoiceCredit(client, resourceId, creditResourceId);
613
+ if (opts.json) {
614
+ console.log(JSON.stringify({ reversed: true, resourceId, creditResourceId }));
615
+ }
616
+ else {
617
+ console.log(chalk.green(`Credit ${creditResourceId} reversed from invoice ${resourceId}.`));
618
+ }
619
+ })(opts));
559
620
  }
@@ -0,0 +1,173 @@
1
+ import chalk from 'chalk';
2
+ import { listNanoClassifiers, getNanoClassifier, searchNanoClassifiers, createNanoClassifier, updateNanoClassifier, deleteNanoClassifier, } from '../core/api/nano-classifiers.js';
3
+ import { apiAction } from './api-action.js';
4
+ import { outputList } from './output.js';
5
+ import { parsePositiveInt, parseNonNegativeInt, readBodyInput, requireFields } from './parsers.js';
6
+ import { paginatedFetch } from './pagination.js';
7
+ import { formatId } from './format-helpers.js';
8
+ const NC_COLUMNS = [
9
+ { key: 'resourceId', header: 'ID', format: formatId },
10
+ { key: 'type', header: 'Type' },
11
+ { key: 'classes', header: 'Classes', format: (v) => Array.isArray(v) ? v.map((c) => c.className).join(', ') : '' },
12
+ ];
13
+ export function registerNanoClassifiersCommand(program) {
14
+ const cmd = program
15
+ .command('nano-classifiers')
16
+ .description('Manage nano classifiers (tracking categories)');
17
+ // ── clio nano-classifiers list ──────────────────────────────────
18
+ cmd
19
+ .command('list')
20
+ .description('List nano classifiers')
21
+ .option('--limit <n>', 'Max results (default 100)', parsePositiveInt)
22
+ .option('--offset <n>', 'Page number offset (0-indexed)', parseNonNegativeInt)
23
+ .option('--all', 'Fetch all pages')
24
+ .option('--max-rows <n>', 'Max rows for --all (default 10000)', parsePositiveInt)
25
+ .option('--api-key <key>', 'API key (overrides stored/env)')
26
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
27
+ .option('--json', 'Output as JSON')
28
+ .action(apiAction(async (client, opts) => {
29
+ const result = await paginatedFetch(opts, (p) => listNanoClassifiers(client, p), { label: 'Fetching nano classifiers' });
30
+ outputList(result, NC_COLUMNS, opts, 'Nano Classifiers'); // eslint-disable-line @typescript-eslint/no-explicit-any
31
+ }));
32
+ // ── clio nano-classifiers get ───────────────────────────────────
33
+ cmd
34
+ .command('get <resourceId>')
35
+ .description('Get a nano classifier by resourceId')
36
+ .option('--api-key <key>', 'API key (overrides stored/env)')
37
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
38
+ .option('--json', 'Output as JSON')
39
+ .action((resourceId, opts) => apiAction(async (client) => {
40
+ const res = await getNanoClassifier(client, resourceId);
41
+ const nc = res.data;
42
+ if (opts.json) {
43
+ console.log(JSON.stringify(nc, null, 2));
44
+ }
45
+ else {
46
+ console.log(chalk.bold('Type:'), nc.type);
47
+ console.log(chalk.bold('ID:'), nc.resourceId);
48
+ console.log(chalk.bold('Classes:'));
49
+ for (const cls of nc.classes) {
50
+ console.log(` ${cls.className} (${cls.resourceId})`);
51
+ }
52
+ }
53
+ })(opts));
54
+ // ── clio nano-classifiers search ────────────────────────────────
55
+ cmd
56
+ .command('search <query>')
57
+ .description('Search nano classifiers by type')
58
+ .option('--limit <n>', 'Max results (default 20)', parsePositiveInt)
59
+ .option('--offset <n>', 'Page number offset (0-indexed)', parseNonNegativeInt)
60
+ .option('--all', 'Fetch all pages')
61
+ .option('--max-rows <n>', 'Max rows for --all (default 10000)', parsePositiveInt)
62
+ .option('--api-key <key>', 'API key (overrides stored/env)')
63
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
64
+ .option('--json', 'Output as JSON')
65
+ .action((query, opts) => apiAction(async (client) => {
66
+ const filter = { type: { contains: query } };
67
+ const sort = { sortBy: ['type'], order: 'ASC' };
68
+ const result = await paginatedFetch(opts, ({ limit, offset }) => searchNanoClassifiers(client, { filter, limit, offset, sort }), { label: 'Searching nano classifiers', defaultLimit: 20 });
69
+ outputList(result, NC_COLUMNS, opts, 'Nano Classifiers'); // eslint-disable-line @typescript-eslint/no-explicit-any
70
+ })(opts));
71
+ // ── clio nano-classifiers create ────────────────────────────────
72
+ cmd
73
+ .command('create')
74
+ .description('Create a nano classifier')
75
+ .option('--type <type>', 'Classifier type name')
76
+ .option('--classes <json>', 'Class names as JSON array: ["Sales","Marketing"] or comma-separated: Sales,Marketing')
77
+ .option('--printable', 'Show on printed documents (default: false)')
78
+ .option('--input <file>', 'Read full request body from JSON file (or pipe via stdin)')
79
+ .option('--api-key <key>', 'API key (overrides stored/env)')
80
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
81
+ .option('--json', 'Output as JSON')
82
+ .action(apiAction(async (client, opts) => {
83
+ const body = readBodyInput(opts);
84
+ let res;
85
+ if (body) {
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- user-provided JSON, API validates
87
+ res = await createNanoClassifier(client, body);
88
+ }
89
+ else {
90
+ requireFields(opts, [
91
+ { flag: '--type', key: 'type' },
92
+ { flag: '--classes', key: 'classes' },
93
+ ]);
94
+ let classes;
95
+ try {
96
+ classes = JSON.parse(opts.classes);
97
+ }
98
+ catch {
99
+ classes = opts.classes.split(',').map((s) => s.trim());
100
+ }
101
+ res = await createNanoClassifier(client, {
102
+ type: opts.type,
103
+ classes,
104
+ printable: opts.printable === true,
105
+ });
106
+ }
107
+ if (opts.json) {
108
+ console.log(JSON.stringify(res.data, null, 2));
109
+ }
110
+ else {
111
+ console.log(chalk.green(`Nano classifier created: ${res.data.type ?? opts.type}`));
112
+ console.log(chalk.bold('ID:'), res.data.resourceId);
113
+ }
114
+ }));
115
+ // ── clio nano-classifiers update ────────────────────────────────
116
+ cmd
117
+ .command('update <resourceId>')
118
+ .description('Update a nano classifier')
119
+ .option('--type <type>', 'New classifier type name')
120
+ .option('--classes <json>', 'Updated class names as JSON array or comma-separated')
121
+ .option('--printable', 'Show on printed documents')
122
+ .option('--input <file>', 'Read full request body from JSON file (or pipe via stdin)')
123
+ .option('--api-key <key>', 'API key (overrides stored/env)')
124
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
125
+ .option('--json', 'Output as JSON')
126
+ .action((resourceId, opts) => apiAction(async (client) => {
127
+ const body = readBodyInput(opts);
128
+ let res;
129
+ if (body) {
130
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- user-provided JSON, API validates
131
+ res = await updateNanoClassifier(client, resourceId, body);
132
+ }
133
+ else {
134
+ const data = {};
135
+ if (opts.type)
136
+ data.type = opts.type;
137
+ if (opts.classes) {
138
+ try {
139
+ data.classes = JSON.parse(opts.classes);
140
+ }
141
+ catch {
142
+ data.classes = opts.classes.split(',').map((s) => s.trim());
143
+ }
144
+ }
145
+ if (opts.printable !== undefined)
146
+ data.printable = opts.printable;
147
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
+ res = await updateNanoClassifier(client, resourceId, data);
149
+ }
150
+ if (opts.json) {
151
+ console.log(JSON.stringify(res.data, null, 2));
152
+ }
153
+ else {
154
+ console.log(chalk.green(`Nano classifier ${resourceId} updated.`));
155
+ }
156
+ })(opts));
157
+ // ── clio nano-classifiers delete ────────────────────────────────
158
+ cmd
159
+ .command('delete <resourceId>')
160
+ .description('Delete a nano classifier')
161
+ .option('--api-key <key>', 'API key (overrides stored/env)')
162
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
163
+ .option('--json', 'Output as JSON')
164
+ .action((resourceId, opts) => apiAction(async (client) => {
165
+ await deleteNanoClassifier(client, resourceId);
166
+ if (opts.json) {
167
+ console.log(JSON.stringify({ deleted: true, resourceId }));
168
+ }
169
+ else {
170
+ console.log(chalk.green(`Nano classifier ${resourceId} deleted.`));
171
+ }
172
+ })(opts));
173
+ }
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { listPayments, searchPayments, } from '../core/api/payments.js';
2
+ import { listPayments, searchPayments, getPayment, updatePayment, deletePayment, } from '../core/api/payments.js';
3
3
  import { apiAction } from './api-action.js';
4
4
  import { parsePositiveInt, parseNonNegativeInt } from './parsers.js';
5
5
  import { paginatedFetch } from './pagination.js';
@@ -75,4 +75,69 @@ export function registerPaymentsCommand(program) {
75
75
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchPayments(client, { filter: searchFilter, limit, offset, sort }), { label: 'Searching transactions', defaultLimit: 20 });
76
76
  outputList(result, PAYMENT_COLUMNS, opts, 'Payments'); // eslint-disable-line @typescript-eslint/no-explicit-any
77
77
  }));
78
+ // ── clio payments get ───────────────────────────────────────────
79
+ payments
80
+ .command('get <resourceId>')
81
+ .description('Get a specific payment record')
82
+ .option('--api-key <key>', 'API key (overrides stored/env)')
83
+ .option('--format <type>', 'Output format: table, json, csv, yaml')
84
+ .option('--json', 'Output as JSON')
85
+ .action((resourceId, opts) => apiAction(async (client) => {
86
+ const res = await getPayment(client, resourceId);
87
+ const p = res.data;
88
+ if (opts.json) {
89
+ console.log(JSON.stringify(p, null, 2));
90
+ }
91
+ else {
92
+ console.log(chalk.bold('ID:'), p.resourceId);
93
+ console.log(chalk.bold('Amount:'), p.paymentAmount);
94
+ console.log(chalk.bold('Date:'), p.valueDate);
95
+ console.log(chalk.bold('Method:'), p.paymentMethod);
96
+ console.log(chalk.bold('Reference:'), p.reference);
97
+ }
98
+ })(opts));
99
+ // ── clio payments update ────────────────────────────────────────
100
+ payments
101
+ .command('update <resourceId>')
102
+ .description('Update a payment record')
103
+ .option('--amount <n>', 'Corrected payment amount')
104
+ .option('--reference <ref>', 'Payment reference')
105
+ .option('--date <YYYY-MM-DD>', 'Payment date')
106
+ .option('--method <method>', 'Payment method (BANK_TRANSFER, CASH, CHEQUE, etc.)')
107
+ .option('--api-key <key>', 'API key (overrides stored/env)')
108
+ .option('--json', 'Output as JSON')
109
+ .action((resourceId, opts) => apiAction(async (client) => {
110
+ const data = {};
111
+ if (opts.amount)
112
+ data.paymentAmount = parseFloat(opts.amount);
113
+ if (opts.reference)
114
+ data.reference = opts.reference;
115
+ if (opts.date)
116
+ data.valueDate = opts.date;
117
+ if (opts.method)
118
+ data.paymentMethod = opts.method;
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+ const res = await updatePayment(client, resourceId, data);
121
+ if (opts.json) {
122
+ console.log(JSON.stringify(res.data, null, 2));
123
+ }
124
+ else {
125
+ console.log(chalk.green(`Payment ${resourceId} updated.`));
126
+ }
127
+ })(opts));
128
+ // ── clio payments delete ────────────────────────────────────────
129
+ payments
130
+ .command('delete <resourceId>')
131
+ .description('Delete (void) a payment record')
132
+ .option('--api-key <key>', 'API key (overrides stored/env)')
133
+ .option('--json', 'Output as JSON')
134
+ .action((resourceId, opts) => apiAction(async (client) => {
135
+ await deletePayment(client, resourceId);
136
+ if (opts.json) {
137
+ console.log(JSON.stringify({ deleted: true, resourceId }));
138
+ }
139
+ else {
140
+ console.log(chalk.green(`Payment ${resourceId} deleted.`));
141
+ }
142
+ })(opts));
78
143
  }
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { listScheduledInvoices, listScheduledBills, listScheduledJournals, } from '../core/api/schedulers.js';
2
+ import { listScheduledInvoices, listScheduledBills, listScheduledJournals, getScheduledInvoice, updateScheduledInvoice, deleteScheduledInvoice, getScheduledBill, updateScheduledBill, deleteScheduledBill, getScheduledJournal, updateScheduledJournal, deleteScheduledJournal, } from '../core/api/schedulers.js';
3
3
  import { createScheduledInvoice } from '../core/api/invoices.js';
4
4
  import { createScheduledBill } from '../core/api/bills.js';
5
5
  import { createScheduledJournal } from '../core/api/journals.js';
@@ -10,7 +10,7 @@ import { parsePositiveInt, parseNonNegativeInt, readBodyInput, requireFields, pa
10
10
  import { paginatedFetch } from './pagination.js';
11
11
  const SCHEDULER_COLUMNS = [
12
12
  { key: 'resourceId', header: 'ID', format: formatId },
13
- { key: 'repeat', header: 'Repeat' },
13
+ { key: 'interval', header: 'Repeat' },
14
14
  { key: 'startDate', header: 'Start' },
15
15
  { key: 'status', header: 'Status' },
16
16
  ];
@@ -239,4 +239,211 @@ Strings are replaced with values relative to the transaction date.`);
239
239
  console.log(chalk.green('Scheduled journal created'));
240
240
  }
241
241
  }));
242
+ // ── clio schedulers get-invoice ─────────────────────────────────
243
+ cmd
244
+ .command('get-invoice <resourceId>')
245
+ .description('Get a scheduled invoice by resourceId')
246
+ .option('--api-key <key>', 'API key (overrides stored/env)')
247
+ .option('--json', 'Output as JSON')
248
+ .action((resourceId, opts) => apiAction(async (client) => {
249
+ const res = await getScheduledInvoice(client, resourceId);
250
+ if (opts.json) {
251
+ console.log(JSON.stringify(res.data, null, 2));
252
+ }
253
+ else {
254
+ const s = res.data;
255
+ console.log(chalk.bold('ID:'), s.resourceId);
256
+ console.log(chalk.bold('Status:'), s.status);
257
+ console.log(chalk.bold('Repeat:'), s.interval ?? s.repeat);
258
+ console.log(chalk.bold('Start:'), s.startDate);
259
+ if (s.endDate)
260
+ console.log(chalk.bold('End:'), s.endDate);
261
+ }
262
+ })(opts));
263
+ // ── clio schedulers update-invoice ──────────────────────────────
264
+ cmd
265
+ .command('update-invoice <resourceId>')
266
+ .description('Update a scheduled invoice')
267
+ .option('--repeat <interval>', 'Recurrence: WEEKLY, MONTHLY, QUARTERLY, YEARLY')
268
+ .option('--start-date <YYYY-MM-DD>', 'First occurrence date')
269
+ .option('--end-date <YYYY-MM-DD>', 'Last occurrence date')
270
+ .option('--status <status>', 'Status: ACTIVE or PAUSED')
271
+ .option('--input <file>', 'Read full request body from JSON file (or pipe via stdin)')
272
+ .option('--api-key <key>', 'API key (overrides stored/env)')
273
+ .option('--json', 'Output as JSON')
274
+ .action((resourceId, opts) => apiAction(async (client) => {
275
+ const body = readBodyInput(opts);
276
+ const data = body ?? {};
277
+ if (!body) {
278
+ if (opts.repeat)
279
+ data.repeat = opts.repeat;
280
+ if (opts.startDate)
281
+ data.startDate = opts.startDate;
282
+ if (opts.endDate)
283
+ data.endDate = opts.endDate;
284
+ if (opts.status)
285
+ data.status = opts.status;
286
+ }
287
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
288
+ const res = await updateScheduledInvoice(client, resourceId, data);
289
+ if (opts.json) {
290
+ console.log(JSON.stringify(res.data, null, 2));
291
+ }
292
+ else {
293
+ console.log(chalk.green(`Scheduled invoice ${resourceId} updated.`));
294
+ }
295
+ })(opts));
296
+ // ── clio schedulers delete-invoice ──────────────────────────────
297
+ cmd
298
+ .command('delete-invoice <resourceId>')
299
+ .description('Delete a scheduled invoice')
300
+ .option('--api-key <key>', 'API key (overrides stored/env)')
301
+ .option('--json', 'Output as JSON')
302
+ .action((resourceId, opts) => apiAction(async (client) => {
303
+ await deleteScheduledInvoice(client, resourceId);
304
+ if (opts.json) {
305
+ console.log(JSON.stringify({ deleted: true, resourceId }));
306
+ }
307
+ else {
308
+ console.log(chalk.green(`Scheduled invoice ${resourceId} deleted.`));
309
+ }
310
+ })(opts));
311
+ // ── clio schedulers get-bill ────────────────────────────────────
312
+ cmd
313
+ .command('get-bill <resourceId>')
314
+ .description('Get a scheduled bill by resourceId')
315
+ .option('--api-key <key>', 'API key (overrides stored/env)')
316
+ .option('--json', 'Output as JSON')
317
+ .action((resourceId, opts) => apiAction(async (client) => {
318
+ const res = await getScheduledBill(client, resourceId);
319
+ if (opts.json) {
320
+ console.log(JSON.stringify(res.data, null, 2));
321
+ }
322
+ else {
323
+ const s = res.data;
324
+ console.log(chalk.bold('ID:'), s.resourceId);
325
+ console.log(chalk.bold('Status:'), s.status);
326
+ console.log(chalk.bold('Repeat:'), s.interval ?? s.repeat);
327
+ console.log(chalk.bold('Start:'), s.startDate);
328
+ if (s.endDate)
329
+ console.log(chalk.bold('End:'), s.endDate);
330
+ }
331
+ })(opts));
332
+ // ── clio schedulers update-bill ─────────────────────────────────
333
+ cmd
334
+ .command('update-bill <resourceId>')
335
+ .description('Update a scheduled bill')
336
+ .option('--repeat <interval>', 'Recurrence: WEEKLY, MONTHLY, QUARTERLY, YEARLY')
337
+ .option('--start-date <YYYY-MM-DD>', 'First occurrence date')
338
+ .option('--end-date <YYYY-MM-DD>', 'Last occurrence date')
339
+ .option('--status <status>', 'Status: ACTIVE or PAUSED')
340
+ .option('--input <file>', 'Read full request body from JSON file (or pipe via stdin)')
341
+ .option('--api-key <key>', 'API key (overrides stored/env)')
342
+ .option('--json', 'Output as JSON')
343
+ .action((resourceId, opts) => apiAction(async (client) => {
344
+ const body = readBodyInput(opts);
345
+ const data = body ?? {};
346
+ if (!body) {
347
+ if (opts.repeat)
348
+ data.repeat = opts.repeat;
349
+ if (opts.startDate)
350
+ data.startDate = opts.startDate;
351
+ if (opts.endDate)
352
+ data.endDate = opts.endDate;
353
+ if (opts.status)
354
+ data.status = opts.status;
355
+ }
356
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
357
+ const res = await updateScheduledBill(client, resourceId, data);
358
+ if (opts.json) {
359
+ console.log(JSON.stringify(res.data, null, 2));
360
+ }
361
+ else {
362
+ console.log(chalk.green(`Scheduled bill ${resourceId} updated.`));
363
+ }
364
+ })(opts));
365
+ // ── clio schedulers delete-bill ─────────────────────────────────
366
+ cmd
367
+ .command('delete-bill <resourceId>')
368
+ .description('Delete a scheduled bill')
369
+ .option('--api-key <key>', 'API key (overrides stored/env)')
370
+ .option('--json', 'Output as JSON')
371
+ .action((resourceId, opts) => apiAction(async (client) => {
372
+ await deleteScheduledBill(client, resourceId);
373
+ if (opts.json) {
374
+ console.log(JSON.stringify({ deleted: true, resourceId }));
375
+ }
376
+ else {
377
+ console.log(chalk.green(`Scheduled bill ${resourceId} deleted.`));
378
+ }
379
+ })(opts));
380
+ // ── clio schedulers get-journal ─────────────────────────────────
381
+ cmd
382
+ .command('get-journal <resourceId>')
383
+ .description('Get a scheduled journal by resourceId')
384
+ .option('--api-key <key>', 'API key (overrides stored/env)')
385
+ .option('--json', 'Output as JSON')
386
+ .action((resourceId, opts) => apiAction(async (client) => {
387
+ const res = await getScheduledJournal(client, resourceId);
388
+ if (opts.json) {
389
+ console.log(JSON.stringify(res.data, null, 2));
390
+ }
391
+ else {
392
+ const s = res.data;
393
+ console.log(chalk.bold('ID:'), s.resourceId);
394
+ console.log(chalk.bold('Status:'), s.status);
395
+ console.log(chalk.bold('Repeat:'), s.interval ?? s.repeat);
396
+ console.log(chalk.bold('Start:'), s.startDate);
397
+ if (s.endDate)
398
+ console.log(chalk.bold('End:'), s.endDate);
399
+ }
400
+ })(opts));
401
+ // ── clio schedulers update-journal ──────────────────────────────
402
+ cmd
403
+ .command('update-journal <resourceId>')
404
+ .description('Update a scheduled journal')
405
+ .option('--repeat <interval>', 'Recurrence: WEEKLY, MONTHLY, QUARTERLY, YEARLY')
406
+ .option('--start-date <YYYY-MM-DD>', 'First occurrence date')
407
+ .option('--end-date <YYYY-MM-DD>', 'Last occurrence date')
408
+ .option('--status <status>', 'Status: ACTIVE or PAUSED')
409
+ .option('--input <file>', 'Read full request body from JSON file (or pipe via stdin)')
410
+ .option('--api-key <key>', 'API key (overrides stored/env)')
411
+ .option('--json', 'Output as JSON')
412
+ .action((resourceId, opts) => apiAction(async (client) => {
413
+ const body = readBodyInput(opts);
414
+ const data = body ?? {};
415
+ if (!body) {
416
+ if (opts.repeat)
417
+ data.repeat = opts.repeat;
418
+ if (opts.startDate)
419
+ data.startDate = opts.startDate;
420
+ if (opts.endDate)
421
+ data.endDate = opts.endDate;
422
+ if (opts.status)
423
+ data.status = opts.status;
424
+ }
425
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
426
+ const res = await updateScheduledJournal(client, resourceId, data);
427
+ if (opts.json) {
428
+ console.log(JSON.stringify(res.data, null, 2));
429
+ }
430
+ else {
431
+ console.log(chalk.green(`Scheduled journal ${resourceId} updated.`));
432
+ }
433
+ })(opts));
434
+ // ── clio schedulers delete-journal ──────────────────────────────
435
+ cmd
436
+ .command('delete-journal <resourceId>')
437
+ .description('Delete a scheduled journal')
438
+ .option('--api-key <key>', 'API key (overrides stored/env)')
439
+ .option('--json', 'Output as JSON')
440
+ .action((resourceId, opts) => apiAction(async (client) => {
441
+ await deleteScheduledJournal(client, resourceId);
442
+ if (opts.json) {
443
+ console.log(JSON.stringify({ deleted: true, resourceId }));
444
+ }
445
+ else {
446
+ console.log(chalk.green(`Scheduled journal ${resourceId} deleted.`));
447
+ }
448
+ })(opts));
242
449
  }
@@ -39,3 +39,23 @@ export async function applyCreditsToBill(client, billResourceId, credits) {
39
39
  export async function createScheduledBill(client, data) {
40
40
  return client.post('/api/v1/scheduled/bills', data);
41
41
  }
42
+ // ── Payment & Credit Sub-resources ───────────────────────────────
43
+ /**
44
+ * List payments recorded against a bill.
45
+ * API returns raw array (not wrapped in {data: [...]}).
46
+ */
47
+ export async function listBillPayments(client, billId) {
48
+ const res = await client.get(`/api/v1/bills/${billId}/payments`);
49
+ return { data: Array.isArray(res) ? res : res.data ?? [] };
50
+ }
51
+ /**
52
+ * List credit notes applied to a bill.
53
+ * API returns raw array (not wrapped in {data: [...]}).
54
+ */
55
+ export async function listBillCredits(client, billId) {
56
+ const res = await client.get(`/api/v1/bills/${billId}/credits`);
57
+ return { data: Array.isArray(res) ? res : res.data ?? [] };
58
+ }
59
+ export async function reverseBillCredit(client, billId, creditId) {
60
+ await client.delete(`/api/v1/bills/${billId}/credits/${creditId}`);
61
+ }
@@ -34,5 +34,6 @@ export * as contactGroups from './contact-groups.js';
34
34
  export * as inventory from './inventory.js';
35
35
  export * as search from './search.js';
36
36
  export * as quickFix from './quick-fix.js';
37
+ export * as nanoClassifiers from './nano-classifiers.js';
37
38
  // ── Guards (pre-flight checks for create operations) ────────────
38
39
  export * from './guards.js';
@@ -50,3 +50,23 @@ export async function finalizeInvoice(client, resourceId, data) {
50
50
  export async function downloadInvoicePdf(client, resourceId) {
51
51
  return client.get(`/api/v1/invoices/${resourceId}/download`);
52
52
  }
53
+ // ── Payment & Credit Sub-resources ───────────────────────────────
54
+ /**
55
+ * List payments recorded against an invoice.
56
+ * API returns raw array (not wrapped in {data: [...]}).
57
+ */
58
+ export async function listInvoicePayments(client, invoiceId) {
59
+ const res = await client.get(`/api/v1/invoices/${invoiceId}/payments`);
60
+ return { data: Array.isArray(res) ? res : res.data ?? [] };
61
+ }
62
+ /**
63
+ * List credit notes applied to an invoice.
64
+ * API returns raw array (not wrapped in {data: [...]}).
65
+ */
66
+ export async function listInvoiceCredits(client, invoiceId) {
67
+ const res = await client.get(`/api/v1/invoices/${invoiceId}/credits`);
68
+ return { data: Array.isArray(res) ? res : res.data ?? [] };
69
+ }
70
+ export async function reverseInvoiceCredit(client, invoiceId, creditId) {
71
+ await client.delete(`/api/v1/invoices/${invoiceId}/credits/${creditId}`);
72
+ }
@@ -0,0 +1,37 @@
1
+ export async function listNanoClassifiers(client, params) {
2
+ return client.list('/api/v1/nano-classifiers', params);
3
+ }
4
+ /**
5
+ * Get a nano classifier by resourceId.
6
+ * API returns double-wrapped: `{ data: { data: [...], totalElements, totalPages } }`.
7
+ * Extract the first element from the inner paginated response.
8
+ */
9
+ export async function getNanoClassifier(client, resourceId) {
10
+ const res = await client.get(`/api/v1/nano-classifiers/${resourceId}`);
11
+ return { data: res.data.data[0] };
12
+ }
13
+ export async function searchNanoClassifiers(client, params) {
14
+ return client.search('/api/v1/nano-classifiers/search', params);
15
+ }
16
+ /**
17
+ * Create a nano classifier.
18
+ * API expects `classes: string[]` (simple string array, not `[{className}]`)
19
+ * and `printable: boolean` is required.
20
+ */
21
+ export async function createNanoClassifier(client, data) {
22
+ return client.post('/api/v1/nano-classifiers', {
23
+ ...data,
24
+ printable: data.printable ?? false,
25
+ });
26
+ }
27
+ /**
28
+ * Update a nano classifier.
29
+ * `classes` can be either string[] (new class names) or
30
+ * NanoClassifierClass[] (existing classes with resourceIds).
31
+ */
32
+ export async function updateNanoClassifier(client, resourceId, data) {
33
+ return client.put(`/api/v1/nano-classifiers/${resourceId}`, data);
34
+ }
35
+ export async function deleteNanoClassifier(client, resourceId) {
36
+ await client.delete(`/api/v1/nano-classifiers/${resourceId}`);
37
+ }
@@ -16,3 +16,12 @@ export async function listPayments(client, params) {
16
16
  export async function searchPayments(client, params) {
17
17
  return client.search('/api/v1/cashflow-transactions/search', params);
18
18
  }
19
+ export async function getPayment(client, resourceId) {
20
+ return client.get(`/api/v1/payments/${resourceId}`);
21
+ }
22
+ export async function updatePayment(client, resourceId, data) {
23
+ return client.put(`/api/v1/payments/${resourceId}`, data);
24
+ }
25
+ export async function deletePayment(client, resourceId) {
26
+ await client.delete(`/api/v1/payments/${resourceId}`);
27
+ }
@@ -7,3 +7,48 @@ export async function listScheduledBills(client, params) {
7
7
  export async function listScheduledJournals(client, params) {
8
8
  return client.list('/api/v1/scheduled/journals', params);
9
9
  }
10
+ // ── Scheduled Invoice CRUD ───────────────────────────────────────
11
+ export async function getScheduledInvoice(client, resourceId) {
12
+ return client.get(`/api/v1/scheduled/invoices/${resourceId}`);
13
+ }
14
+ /**
15
+ * Update a scheduled invoice.
16
+ * Accepts scheduling fields (repeat, startDate, endDate, status) AND the full
17
+ * invoice template (invoice: { reference, valueDate, dueDate, contactResourceId, lineItems, ... }).
18
+ */
19
+ export async function updateScheduledInvoice(client, resourceId, data) {
20
+ return client.put(`/api/v1/scheduled/invoices/${resourceId}`, data);
21
+ }
22
+ export async function deleteScheduledInvoice(client, resourceId) {
23
+ await client.delete(`/api/v1/scheduled/invoices/${resourceId}`);
24
+ }
25
+ // ── Scheduled Bill CRUD ──────────────────────────────────────────
26
+ export async function getScheduledBill(client, resourceId) {
27
+ return client.get(`/api/v1/scheduled/bills/${resourceId}`);
28
+ }
29
+ /**
30
+ * Update a scheduled bill.
31
+ * Accepts scheduling fields (repeat, startDate, endDate, status) AND the full
32
+ * bill template (bill: { reference, valueDate, dueDate, contactResourceId, lineItems, ... }).
33
+ */
34
+ export async function updateScheduledBill(client, resourceId, data) {
35
+ return client.put(`/api/v1/scheduled/bills/${resourceId}`, data);
36
+ }
37
+ export async function deleteScheduledBill(client, resourceId) {
38
+ await client.delete(`/api/v1/scheduled/bills/${resourceId}`);
39
+ }
40
+ // ── Scheduled Journal CRUD ───────────────────────────────────────
41
+ export async function getScheduledJournal(client, resourceId) {
42
+ return client.get(`/api/v1/scheduled/journals/${resourceId}`);
43
+ }
44
+ /**
45
+ * Update a scheduled journal.
46
+ * Accepts scheduling fields (repeat, startDate, endDate, status) AND the full
47
+ * journal template (valueDate, schedulerEntries, reference, notes).
48
+ */
49
+ export async function updateScheduledJournal(client, resourceId, data) {
50
+ return client.put(`/api/v1/scheduled/journals/${resourceId}`, data);
51
+ }
52
+ export async function deleteScheduledJournal(client, resourceId) {
53
+ await client.delete(`/api/v1/scheduled/journals/${resourceId}`);
54
+ }
@@ -73,6 +73,11 @@ export const TOOL_NAMESPACES = [
73
73
  description: 'Tags for categorizing transactions. Custom fields for adding metadata (text, date, dropdown). Create, search, delete tags and custom fields.',
74
74
  groups: ['tags', 'custom_fields'],
75
75
  },
76
+ {
77
+ name: 'nano_classifiers',
78
+ description: 'Nano classifiers (tracking categories/dimensions). List, search, create, update, delete classifiers and their classes. Used for line-item tagging and dimensional reporting. Also: tracking categories, cost centers, departments, projects.',
79
+ groups: ['nano_classifiers'],
80
+ },
76
81
  // ── Accounting Setup ────────────────────────────────────────
77
82
  {
78
83
  name: 'chart_of_accounts',
@@ -127,7 +132,7 @@ export const TOOL_NAMESPACES = [
127
132
  // ── Payments & Search ───────────────────────────────────────
128
133
  {
129
134
  name: 'payments_and_search',
130
- description: 'Payments search/list across all transaction types. Cashflow transaction ledger. Universal cross-entity search. Transaction summary (fetch any transaction + attachments + payment history in one call).',
135
+ description: 'Payment records: get, update, delete individual payments. List payments/credits on invoices and bills. Reverse credit applications. Cashflow transaction search. Universal cross-entity search. Also: void payment, payment history, credit note applications.',
131
136
  groups: ['payments', 'cashflow', 'search'],
132
137
  },
133
138
  // ── Quick Fix (Bulk Update) ────────────────────────────────
@@ -2,8 +2,8 @@ import { findExistingContact, findExistingItem, findExistingAccount, findExistin
2
2
  import { getOrganization } from '../api/organization.js';
3
3
  import { listAccounts, searchAccounts, getAccount, createAccount, updateAccount, deleteAccount, } from '../api/chart-of-accounts.js';
4
4
  import { listContacts, searchContacts, getContact, createContact, updateContact, deleteContact, } from '../api/contacts.js';
5
- import { listInvoices, searchInvoices, getInvoice, createInvoice, updateInvoice, deleteInvoice, createInvoicePayment, createScheduledInvoice, finalizeInvoice, applyCreditsToInvoice, downloadInvoicePdf, } from '../api/invoices.js';
6
- import { listBills, searchBills, getBill, createBill, updateBill, deleteBill, createBillPayment, createScheduledBill, finalizeBill, applyCreditsToBill, } from '../api/bills.js';
5
+ import { listInvoices, searchInvoices, getInvoice, createInvoice, updateInvoice, deleteInvoice, createInvoicePayment, createScheduledInvoice, finalizeInvoice, applyCreditsToInvoice, downloadInvoicePdf, listInvoicePayments, listInvoiceCredits, reverseInvoiceCredit, } from '../api/invoices.js';
6
+ import { listBills, searchBills, getBill, createBill, updateBill, deleteBill, createBillPayment, createScheduledBill, finalizeBill, applyCreditsToBill, listBillPayments, listBillCredits, reverseBillCredit, } from '../api/bills.js';
7
7
  import { listJournals, searchJournals, getJournal, createJournal, deleteJournal, updateJournal, createScheduledJournal, } from '../api/journals.js';
8
8
  import { generateTrialBalance, generateBalanceSheet, generateProfitAndLoss, generateCashflow, generateArSummary, generateApSummary, generateCashBalance, generateGeneralLedger, generateVatLedger, generateEquityMovement, generateBankBalanceSummary, generateBankReconSummary, generateBankReconDetails, generateFaSummary, generateFaReconSummary, generateArReport, } from '../api/reports.js';
9
9
  import { listBankAccounts, addBankRecords, importBankStatement } from '../api/bank.js';
@@ -16,7 +16,7 @@ import { listCurrencies, addCurrency, listCurrencyRates, addCurrencyRate, update
16
16
  import { listTaxProfiles, listTaxTypes, createTaxProfile, searchTaxProfiles, getTaxProfile, updateTaxProfile, listWithholdingTaxCodes, } from '../api/tax-profiles.js';
17
17
  import { createCashIn, listCashIn, getCashIn, updateCashIn, createCashOut, listCashOut, getCashOut, updateCashOut, } from '../api/cash-entries.js';
18
18
  import { createCashTransfer, listCashTransfers, getCashTransfer } from '../api/cash-transfers.js';
19
- import { listScheduledInvoices, listScheduledBills, listScheduledJournals } from '../api/schedulers.js';
19
+ import { listScheduledInvoices, listScheduledBills, listScheduledJournals, getScheduledInvoice, updateScheduledInvoice, deleteScheduledInvoice, getScheduledBill, updateScheduledBill, deleteScheduledBill, getScheduledJournal, updateScheduledJournal, deleteScheduledJournal, } from '../api/schedulers.js';
20
20
  import { runCalculator, RECIPE_TYPES } from '../recipe/dispatch.js';
21
21
  import { planRecipe, extractBlueprint } from '../recipe/plan.js';
22
22
  import { executeRecipe } from '../recipe/engine.js';
@@ -26,7 +26,7 @@ import { deleteCashEntry, searchCashflowTransactions } from '../api/cashflow.js'
26
26
  import { createTransferTrialBalance } from '../api/transfer-trial-balance.js';
27
27
  import { listBookmarks, getBookmark, createBookmarks, updateBookmark } from '../api/bookmarks.js';
28
28
  import { listOrgUsers, searchOrgUsers, inviteOrgUser, updateOrgUser, removeOrgUser } from '../api/org-users.js';
29
- import { listPayments, searchPayments } from '../api/payments.js';
29
+ import { listPayments, searchPayments, getPayment, updatePayment, deletePayment } from '../api/payments.js';
30
30
  import { downloadExport } from '../api/data-exports.js';
31
31
  import { listAttachments, addAttachment, deleteAttachment, fetchAttachmentTable } from '../api/attachments.js';
32
32
  import { createFromAttachment, searchMagicWorkflows } from '../api/magic.js';
@@ -41,6 +41,7 @@ import { listInventoryItems, getInventoryBalance } from '../api/inventory.js';
41
41
  import { universalSearch } from '../api/search.js';
42
42
  import { listCustomFields, getCustomField, searchCustomFields, createCustomField, updateCustomField, deleteCustomField } from '../api/custom-fields.js';
43
43
  import { quickFix, quickFixLineItems, QUICK_FIX_ENTITIES } from '../api/quick-fix.js';
44
+ import { listNanoClassifiers, getNanoClassifier, searchNanoClassifiers, createNanoClassifier, updateNanoClassifier, deleteNanoClassifier, } from '../api/nano-classifiers.js';
44
45
  // Job blueprints (offline — no API calls)
45
46
  import { generateMonthEndBlueprint } from '../jobs/month-end/blueprint.js';
46
47
  import { generateQuarterEndBlueprint } from '../jobs/quarter-end/blueprint.js';
@@ -4053,15 +4054,25 @@ Response: { updated: string[], failed: [{ resourceId, error, errorCode }] }. On
4053
4054
  params: {
4054
4055
  entity: { type: 'string', enum: [...QUICK_FIX_ENTITIES], description: 'Transaction type to update' },
4055
4056
  resourceIds: { type: 'array', items: { type: 'string' }, description: 'Array of transaction resourceIds to update' },
4056
- attributes: { type: 'object', description: 'Fields to update only included fields are changed, omitted fields left unchanged. See tool description for per-entity attribute list.' },
4057
+ attributes: { type: 'object', description: 'REQUIRED object with fields to update. Only included fields are changed, omitted fields left unchanged. Example: { "valueDate": "2026-03-15", "tags": ["Q1"] }. See tool description for per-entity attribute list.' },
4057
4058
  },
4058
- required: ['entity', 'resourceIds', 'attributes'],
4059
+ required: ['entity', 'resourceIds'],
4059
4060
  group: 'quick_fix',
4060
4061
  readOnly: false,
4061
- execute: async (ctx, input) => quickFix(ctx.client, input.entity, {
4062
- resourceIds: input.resourceIds,
4063
- attributes: input.attributes,
4064
- }),
4062
+ execute: async (ctx, input) => {
4063
+ // Agents sometimes pass attribute fields at top level instead of nesting in `attributes`.
4064
+ // Auto-sweep unrecognized keys into attributes for resilience.
4065
+ const KNOWN_KEYS = new Set(['entity', 'resourceIds', 'attributes']);
4066
+ let attrs = input.attributes ?? {};
4067
+ for (const [k, v] of Object.entries(input)) {
4068
+ if (!KNOWN_KEYS.has(k))
4069
+ attrs = { ...attrs, [k]: v };
4070
+ }
4071
+ return quickFix(ctx.client, input.entity, {
4072
+ resourceIds: input.resourceIds,
4073
+ attributes: attrs,
4074
+ });
4075
+ },
4065
4076
  },
4066
4077
  {
4067
4078
  name: 'quick_fix_line_items',
@@ -4104,4 +4115,293 @@ Response: { updated: string[], failed: [{ resourceId, error, errorCode }] }. On
4104
4115
  return quickFixLineItems(ctx.client, entity, body);
4105
4116
  },
4106
4117
  },
4118
+ // ── Nano Classifiers (Tracking Categories) ─────────────────────
4119
+ listTool('list_nano_classifiers', 'List nano classifiers (tracking categories). Paginated. Nano classifiers tag line items with structured categories.', 'nano_classifiers', (client, off, lim) => listNanoClassifiers(client, { limit: lim, offset: off })),
4120
+ getTool('get_nano_classifier', 'Get a nano classifier by resourceId. Returns type and all classes.', 'nano_classifiers', (client, id) => getNanoClassifier(client, id)),
4121
+ {
4122
+ name: 'search_nano_classifiers',
4123
+ description: 'Search nano classifiers by type name. Returns up to 100 by default.',
4124
+ params: {
4125
+ query: { type: 'string', description: 'Search term (classifier type)' },
4126
+ ...SEARCH_PARAMS,
4127
+ },
4128
+ required: ['query'],
4129
+ group: 'nano_classifiers',
4130
+ readOnly: true,
4131
+ execute: async (ctx, input) => {
4132
+ const { limit, offset } = extractPaginationInput(input);
4133
+ const query = input.query;
4134
+ return handlePagination((off, lim) => searchNanoClassifiers(ctx.client, {
4135
+ filter: { type: { contains: query } },
4136
+ limit: lim, offset: off,
4137
+ sort: { sortBy: ['type'], order: 'ASC' },
4138
+ }), limit, offset, 20);
4139
+ },
4140
+ },
4141
+ {
4142
+ name: 'create_nano_classifier',
4143
+ description: 'Create a nano classifier (tracking category). Provide a type name and list of class names. printable defaults to true (NOTE: printable: false is currently rejected by a server bug — always use true).',
4144
+ params: {
4145
+ type: { type: 'string', description: 'Classifier type name (e.g., "Department", "Project")' },
4146
+ classes: {
4147
+ type: 'array',
4148
+ items: { type: 'string' },
4149
+ description: 'Class names: ["Sales", "Marketing", "Engineering"]',
4150
+ },
4151
+ printable: { type: 'boolean', description: 'Show on printed documents (default: true). NOTE: false is rejected by a server bug.' },
4152
+ },
4153
+ required: ['type', 'classes'],
4154
+ group: 'nano_classifiers',
4155
+ readOnly: false,
4156
+ execute: async (ctx, input) => {
4157
+ return createNanoClassifier(ctx.client, {
4158
+ type: input.type,
4159
+ classes: input.classes,
4160
+ printable: input.printable ?? true,
4161
+ });
4162
+ },
4163
+ },
4164
+ {
4165
+ name: 'update_nano_classifier',
4166
+ description: 'Update a nano classifier — change type name, modify classes, or toggle printable.',
4167
+ params: {
4168
+ resourceId: { type: 'string', description: 'Nano classifier resourceId' },
4169
+ type: { type: 'string', description: 'New type name' },
4170
+ classes: {
4171
+ type: 'array',
4172
+ items: { type: 'string' },
4173
+ description: 'Updated class names: ["Sales", "Marketing"]',
4174
+ },
4175
+ printable: { type: 'boolean', description: 'Show on printed documents' },
4176
+ },
4177
+ required: ['resourceId'],
4178
+ group: 'nano_classifiers',
4179
+ readOnly: false,
4180
+ execute: async (ctx, input) => {
4181
+ const data = {};
4182
+ if (input.type)
4183
+ data.type = input.type;
4184
+ if (input.classes)
4185
+ data.classes = input.classes;
4186
+ if (input.printable !== undefined)
4187
+ data.printable = input.printable;
4188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4189
+ return updateNanoClassifier(ctx.client, input.resourceId, data);
4190
+ },
4191
+ },
4192
+ deleteTool('delete_nano_classifier', 'Delete a nano classifier.', 'nano_classifiers', (client, id) => deleteNanoClassifier(client, id)),
4193
+ // ── Invoice/Bill Payment & Credit Sub-resources ────────────────
4194
+ {
4195
+ name: 'list_invoice_payments',
4196
+ description: 'List all payments recorded against an invoice. Returns payment records with amounts, dates, and methods.',
4197
+ params: {
4198
+ resourceId: { type: 'string', description: 'Invoice resourceId' },
4199
+ },
4200
+ required: ['resourceId'],
4201
+ group: 'payments',
4202
+ readOnly: true,
4203
+ execute: async (ctx, input) => listInvoicePayments(ctx.client, input.resourceId),
4204
+ },
4205
+ {
4206
+ name: 'list_invoice_credits',
4207
+ description: 'List all credit notes applied to an invoice.',
4208
+ params: {
4209
+ resourceId: { type: 'string', description: 'Invoice resourceId' },
4210
+ },
4211
+ required: ['resourceId'],
4212
+ group: 'payments',
4213
+ readOnly: true,
4214
+ execute: async (ctx, input) => listInvoiceCredits(ctx.client, input.resourceId),
4215
+ },
4216
+ {
4217
+ name: 'reverse_invoice_credit',
4218
+ description: 'Reverse (unapply) a credit note from an invoice. The credit note becomes UNAPPLIED again.',
4219
+ params: {
4220
+ resourceId: { type: 'string', description: 'Invoice resourceId' },
4221
+ creditResourceId: { type: 'string', description: 'Credit application resourceId to reverse' },
4222
+ },
4223
+ required: ['resourceId', 'creditResourceId'],
4224
+ group: 'payments',
4225
+ readOnly: false,
4226
+ execute: async (ctx, input) => {
4227
+ await reverseInvoiceCredit(ctx.client, input.resourceId, input.creditResourceId);
4228
+ return { reversed: true, invoiceId: input.resourceId, creditId: input.creditResourceId };
4229
+ },
4230
+ },
4231
+ {
4232
+ name: 'list_bill_payments',
4233
+ description: 'List all payments recorded against a bill. Returns payment records with amounts, dates, and methods.',
4234
+ params: {
4235
+ resourceId: { type: 'string', description: 'Bill resourceId' },
4236
+ },
4237
+ required: ['resourceId'],
4238
+ group: 'payments',
4239
+ readOnly: true,
4240
+ execute: async (ctx, input) => listBillPayments(ctx.client, input.resourceId),
4241
+ },
4242
+ {
4243
+ name: 'list_bill_credits',
4244
+ description: 'List all credit notes applied to a bill.',
4245
+ params: {
4246
+ resourceId: { type: 'string', description: 'Bill resourceId' },
4247
+ },
4248
+ required: ['resourceId'],
4249
+ group: 'payments',
4250
+ readOnly: true,
4251
+ execute: async (ctx, input) => listBillCredits(ctx.client, input.resourceId),
4252
+ },
4253
+ {
4254
+ name: 'reverse_bill_credit',
4255
+ description: 'Reverse (unapply) a supplier credit note from a bill. The credit note becomes UNAPPLIED again.',
4256
+ params: {
4257
+ resourceId: { type: 'string', description: 'Bill resourceId' },
4258
+ creditResourceId: { type: 'string', description: 'Credit application resourceId to reverse' },
4259
+ },
4260
+ required: ['resourceId', 'creditResourceId'],
4261
+ group: 'payments',
4262
+ readOnly: false,
4263
+ execute: async (ctx, input) => {
4264
+ await reverseBillCredit(ctx.client, input.resourceId, input.creditResourceId);
4265
+ return { reversed: true, billId: input.resourceId, creditId: input.creditResourceId };
4266
+ },
4267
+ },
4268
+ // ── Scheduled Transaction CRUD (Get, Update, Delete) ───────────
4269
+ getTool('get_scheduled_invoice', 'Get a scheduled (recurring) invoice by resourceId.', 'schedulers', (client, id) => getScheduledInvoice(client, id)),
4270
+ {
4271
+ name: 'update_scheduled_invoice',
4272
+ description: 'Update a scheduled invoice — change schedule settings and/or the invoice template.',
4273
+ params: {
4274
+ resourceId: { type: 'string', description: 'Scheduled invoice resourceId' },
4275
+ repeat: { type: 'string', description: 'Recurrence: WEEKLY, MONTHLY, QUARTERLY, YEARLY' },
4276
+ startDate: { type: 'string', description: 'First occurrence date (YYYY-MM-DD)' },
4277
+ endDate: { type: 'string', description: 'Last occurrence date (YYYY-MM-DD)' },
4278
+ status: { type: 'string', description: 'Status: ACTIVE or PAUSED' },
4279
+ invoice: { type: 'object', description: 'Invoice template: { reference, valueDate, dueDate, contactResourceId, lineItems, currency, tag, saveAsDraft }' },
4280
+ },
4281
+ required: ['resourceId'],
4282
+ group: 'schedulers',
4283
+ readOnly: false,
4284
+ execute: async (ctx, input) => {
4285
+ const data = {};
4286
+ if (input.repeat)
4287
+ data.repeat = input.repeat;
4288
+ if (input.startDate)
4289
+ data.startDate = input.startDate;
4290
+ if (input.endDate)
4291
+ data.endDate = input.endDate;
4292
+ if (input.status)
4293
+ data.status = input.status;
4294
+ if (input.invoice)
4295
+ data.invoice = input.invoice;
4296
+ return updateScheduledInvoice(ctx.client, input.resourceId, data);
4297
+ },
4298
+ },
4299
+ deleteTool('delete_scheduled_invoice', 'Delete a scheduled (recurring) invoice.', 'schedulers', (client, id) => deleteScheduledInvoice(client, id)),
4300
+ getTool('get_scheduled_bill', 'Get a scheduled (recurring) bill by resourceId.', 'schedulers', (client, id) => getScheduledBill(client, id)),
4301
+ {
4302
+ name: 'update_scheduled_bill',
4303
+ description: 'Update a scheduled bill — change schedule settings and/or the bill template.',
4304
+ params: {
4305
+ resourceId: { type: 'string', description: 'Scheduled bill resourceId' },
4306
+ repeat: { type: 'string', description: 'Recurrence: WEEKLY, MONTHLY, QUARTERLY, YEARLY' },
4307
+ startDate: { type: 'string', description: 'First occurrence date (YYYY-MM-DD)' },
4308
+ endDate: { type: 'string', description: 'Last occurrence date (YYYY-MM-DD)' },
4309
+ status: { type: 'string', description: 'Status: ACTIVE or PAUSED' },
4310
+ bill: { type: 'object', description: 'Bill template: { reference, valueDate, dueDate, contactResourceId, lineItems, currency, tag, saveAsDraft }' },
4311
+ },
4312
+ required: ['resourceId'],
4313
+ group: 'schedulers',
4314
+ readOnly: false,
4315
+ execute: async (ctx, input) => {
4316
+ const data = {};
4317
+ if (input.repeat)
4318
+ data.repeat = input.repeat;
4319
+ if (input.startDate)
4320
+ data.startDate = input.startDate;
4321
+ if (input.endDate)
4322
+ data.endDate = input.endDate;
4323
+ if (input.status)
4324
+ data.status = input.status;
4325
+ if (input.bill)
4326
+ data.bill = input.bill;
4327
+ return updateScheduledBill(ctx.client, input.resourceId, data);
4328
+ },
4329
+ },
4330
+ deleteTool('delete_scheduled_bill', 'Delete a scheduled (recurring) bill.', 'schedulers', (client, id) => deleteScheduledBill(client, id)),
4331
+ getTool('get_scheduled_journal', 'Get a scheduled (recurring) journal by resourceId.', 'schedulers', (client, id) => getScheduledJournal(client, id)),
4332
+ {
4333
+ name: 'update_scheduled_journal',
4334
+ description: 'Update a scheduled journal — change schedule settings and/or the journal template.',
4335
+ params: {
4336
+ resourceId: { type: 'string', description: 'Scheduled journal resourceId' },
4337
+ repeat: { type: 'string', description: 'Recurrence: WEEKLY, MONTHLY, QUARTERLY, YEARLY' },
4338
+ startDate: { type: 'string', description: 'First occurrence date (YYYY-MM-DD)' },
4339
+ endDate: { type: 'string', description: 'Last occurrence date (YYYY-MM-DD)' },
4340
+ status: { type: 'string', description: 'Status: ACTIVE or PAUSED' },
4341
+ valueDate: { type: 'string', description: 'Journal date (YYYY-MM-DD)' },
4342
+ schedulerEntries: { type: 'array', description: 'Journal entries: [{ accountResourceId, type: CREDIT|DEBIT, amount, ... }]' },
4343
+ reference: { type: 'string', description: 'Journal reference' },
4344
+ notes: { type: 'string', description: 'Journal notes' },
4345
+ },
4346
+ required: ['resourceId'],
4347
+ group: 'schedulers',
4348
+ readOnly: false,
4349
+ execute: async (ctx, input) => {
4350
+ const data = {};
4351
+ if (input.repeat)
4352
+ data.repeat = input.repeat;
4353
+ if (input.startDate)
4354
+ data.startDate = input.startDate;
4355
+ if (input.endDate)
4356
+ data.endDate = input.endDate;
4357
+ if (input.status)
4358
+ data.status = input.status;
4359
+ if (input.valueDate)
4360
+ data.valueDate = input.valueDate;
4361
+ if (input.schedulerEntries)
4362
+ data.schedulerEntries = input.schedulerEntries;
4363
+ if (input.reference)
4364
+ data.reference = input.reference;
4365
+ if (input.notes)
4366
+ data.notes = input.notes;
4367
+ return updateScheduledJournal(ctx.client, input.resourceId, data);
4368
+ },
4369
+ },
4370
+ deleteTool('delete_scheduled_journal', 'Delete a scheduled (recurring) journal.', 'schedulers', (client, id) => deleteScheduledJournal(client, id)),
4371
+ // ── Generic Payment CRUD ───────────────────────────────────────
4372
+ getTool('get_payment', 'Get a specific payment record by resourceId. Returns payment amount, method, date, and reference.', 'payments', (client, id) => getPayment(client, id)),
4373
+ {
4374
+ name: 'update_payment',
4375
+ description: 'Update a payment record — correct amount, reference, date, method, or account.',
4376
+ params: {
4377
+ resourceId: { type: 'string', description: 'Payment resourceId (from invoice/bill paymentRecords)' },
4378
+ paymentAmount: { type: 'number', description: 'Corrected payment amount (bank currency)' },
4379
+ reference: { type: 'string', description: 'Payment reference' },
4380
+ valueDate: { type: 'string', description: 'Payment date (YYYY-MM-DD)' },
4381
+ paymentMethod: { type: 'string', description: 'Payment method (BANK_TRANSFER, CASH, CHEQUE, etc.)' },
4382
+ accountResourceId: { type: 'string', description: 'Bank/cash account resourceId' },
4383
+ transactionFee: { type: 'number', description: 'Transaction fee amount' },
4384
+ },
4385
+ required: ['resourceId'],
4386
+ group: 'payments',
4387
+ readOnly: false,
4388
+ execute: async (ctx, input) => {
4389
+ const data = {};
4390
+ if (input.paymentAmount !== undefined)
4391
+ data.paymentAmount = input.paymentAmount;
4392
+ if (input.reference)
4393
+ data.reference = input.reference;
4394
+ if (input.valueDate)
4395
+ data.valueDate = input.valueDate;
4396
+ if (input.paymentMethod)
4397
+ data.paymentMethod = input.paymentMethod;
4398
+ if (input.accountResourceId)
4399
+ data.accountResourceId = input.accountResourceId;
4400
+ if (input.transactionFee !== undefined)
4401
+ data.transactionFee = input.transactionFee;
4402
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4403
+ return updatePayment(ctx.client, input.resourceId, data);
4404
+ },
4405
+ },
4406
+ deleteTool('delete_payment', 'Delete (void) a payment record. The associated invoice/bill balance is restored.', 'payments', (client, id) => deletePayment(client, id)),
4107
4407
  ];
package/dist/index.js CHANGED
@@ -50,6 +50,7 @@ import { registerInventoryCommand } from './commands/inventory.js';
50
50
  import { registerSearchCommand } from './commands/search.js';
51
51
  import { registerCustomFieldsCommand } from './commands/custom-fields.js';
52
52
  import { registerQuickFixCommand } from './commands/quick-fix.js';
53
+ import { registerNanoClassifiersCommand } from './commands/nano-classifiers.js';
53
54
  import { registerSchemaCommand } from './commands/schema.js';
54
55
  import { applyAllExamples } from './commands/help-examples.js';
55
56
  import { shouldShowPicker, showCommandPicker, attachSubcommandPickers } from './commands/picker.js';
@@ -163,6 +164,7 @@ registerInventoryCommand(program);
163
164
  registerSearchCommand(program);
164
165
  registerCustomFieldsCommand(program);
165
166
  registerQuickFixCommand(program);
167
+ registerNanoClassifiersCommand(program);
166
168
  registerSchemaCommand(program);
167
169
  applyAllExamples(program);
168
170
  // Add --org to every command that has --api-key (DRY: zero changes to command files)
@@ -1,6 +1,6 @@
1
1
  export const SKILL_TYPES = ['jaz-api', 'jaz-conversion', 'jaz-recipes', 'jaz-jobs', 'all'];
2
2
  export const SKILL_DESCRIPTIONS = {
3
- 'jaz-api': 'Jaz REST API reference — 55 rules, endpoint catalog, error catalog, field mapping',
3
+ 'jaz-api': 'Jaz REST API reference — 111 rules, endpoint catalog, error catalog, field mapping',
4
4
  'jaz-conversion': 'Data conversion pipeline — Xero, QuickBooks, Sage, Excel migration to Jaz',
5
5
  'jaz-recipes': 'Complex accounting recipes — prepaid, deferred revenue, loans, IFRS 16, depreciation',
6
6
  'jaz-jobs': 'Accounting job blueprints — month/quarter/year-end close + 7 ad-hoc operational workflows',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jaz-clio",
3
- "version": "4.32.5",
3
+ "version": "4.33.0",
4
4
  "description": "Clio — Command Line Interface Orchestrator for Jaz AI.",
5
5
  "type": "module",
6
6
  "bin": {