jaz-clio 4.28.0 → 4.29.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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-api
3
- version: 4.28.0
3
+ version: 4.29.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.28.0
3
+ version: 4.29.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.28.0
3
+ version: 4.29.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.28.0
3
+ version: 4.29.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.
@@ -6,6 +6,7 @@ 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
8
  import { paginatedFetch } from './pagination.js';
9
+ import { buildInvoiceBillFilter } from '../core/registry/pagination.js';
9
10
  import { outputList } from './output.js';
10
11
  import { BILL_REQUIRED_FIELDS, buildDraftReport, formatDraftTable, addDraftFinalizeOptions, mergeDraftFlags, validateDraft, buildValidation, normalizeDate, sanitizeLineItem, } from './draft-helpers.js';
11
12
  const BILL_COLUMNS = [
@@ -73,7 +74,7 @@ export function registerBillsCommand(program) {
73
74
  .command('search')
74
75
  .description('Search bills with filters')
75
76
  .option('--ref <reference>', 'Filter by reference (contains)')
76
- .option('--status <status>', 'Filter by status (DRAFT, UNPAID, PAID, OVERDUE, VOIDED)')
77
+ .option('--status <status>', 'Filter by status (DRAFT, UNPAID, PARTIALLY_PAID, PAID, OVERDUE, VOID)')
77
78
  .option('--contact <resourceId>', 'Filter by contact name or resourceId')
78
79
  .option('--tag <name>', 'Filter by tag')
79
80
  .option('--min-amount <n>', 'Minimum total amount', parseMoney)
@@ -97,40 +98,13 @@ export function registerBillsCommand(program) {
97
98
  const resolved = await resolveContactFlag(client, opts.contact, { silent: opts.json });
98
99
  opts.contact = resolved.resourceId;
99
100
  }
100
- const filter = {};
101
- if (opts.ref)
102
- filter.reference = { contains: opts.ref };
103
- if (opts.status)
104
- filter.status = { eq: opts.status };
105
- if (opts.contact)
106
- filter.contactResourceId = { eq: opts.contact };
107
- if (opts.tag)
108
- filter.tags = { eq: opts.tag };
109
- if (opts.minAmount !== undefined || opts.maxAmount !== undefined) {
110
- const af = {};
111
- if (opts.minAmount !== undefined)
112
- af.gte = opts.minAmount;
113
- if (opts.maxAmount !== undefined)
114
- af.lte = opts.maxAmount;
115
- filter.totalAmount = af;
116
- }
117
- if (opts.from || opts.to) {
118
- const dateFilter = {};
119
- if (opts.from)
120
- dateFilter.gte = opts.from;
121
- if (opts.to)
122
- dateFilter.lte = opts.to;
123
- filter.valueDate = dateFilter;
124
- }
125
- if (opts.dueFrom || opts.dueTo) {
126
- const df = {};
127
- if (opts.dueFrom)
128
- df.gte = opts.dueFrom;
129
- if (opts.dueTo)
130
- df.lte = opts.dueTo;
131
- filter.dueDate = df;
132
- }
133
- const searchFilter = Object.keys(filter).length > 0 ? filter : undefined;
101
+ const searchFilter = buildInvoiceBillFilter({
102
+ reference: opts.ref, status: opts.status,
103
+ contactResourceId: opts.contact, tag: opts.tag,
104
+ minAmount: opts.minAmount, maxAmount: opts.maxAmount,
105
+ startDate: opts.from, endDate: opts.to,
106
+ dueDateFrom: opts.dueFrom, dueDateTo: opts.dueTo,
107
+ });
134
108
  const sort = { sortBy: [opts.sort ?? 'valueDate'], order: (opts.order ?? 'DESC') };
135
109
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchBills(client, { filter: searchFilter, limit, offset, sort }), { label: 'Searching bills', defaultLimit: 20 });
136
110
  outputList(result, BILL_COLUMNS, opts, 'Bills'); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -3,6 +3,7 @@ import { searchCashflowTransactions } from '../core/api/cashflow.js';
3
3
  import { apiAction } from './api-action.js';
4
4
  import { parsePositiveInt, parseNonNegativeInt } from './parsers.js';
5
5
  import { paginatedFetch } from './pagination.js';
6
+ import { buildCashflowFilter } from '../core/registry/pagination.js';
6
7
  import { outputList } from './output.js';
7
8
  import { formatId, formatReference, formatDirection, formatEpochDate } from './format-helpers.js';
8
9
  const CASHFLOW_COLUMNS = [
@@ -37,22 +38,10 @@ export function registerCashflowCommand(program) {
37
38
  .option('--api-key <key>', 'API key (overrides stored/env)')
38
39
  .option('--json', 'Output as JSON')
39
40
  .action(apiAction(async (client, opts) => {
40
- const filter = {};
41
- if (opts.ref)
42
- filter.transactionReference = { contains: opts.ref };
43
- if (opts.type)
44
- filter.businessTransactionType = { eq: opts.type };
45
- if (opts.direction)
46
- filter.direction = { eq: opts.direction };
47
- if (opts.from || opts.to) {
48
- const dateFilter = {};
49
- if (opts.from)
50
- dateFilter.gte = opts.from;
51
- if (opts.to)
52
- dateFilter.lte = opts.to;
53
- filter.valueDate = dateFilter;
54
- }
55
- const searchFilter = Object.keys(filter).length > 0 ? filter : undefined;
41
+ const searchFilter = buildCashflowFilter({
42
+ reference: opts.ref, businessTransactionType: opts.type,
43
+ direction: opts.direction, startDate: opts.from, endDate: opts.to,
44
+ });
56
45
  const sort = { sortBy: [opts.sort ?? 'valueDate'], order: (opts.order ?? 'DESC') };
57
46
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchCashflowTransactions(client, { filter: searchFilter, limit, offset, sort }), { label: 'Searching cashflow transactions', defaultLimit: 20 });
58
47
  outputList(result, CASHFLOW_COLUMNS, opts, 'Cashflow'); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -4,6 +4,7 @@ import { apiAction } from './api-action.js';
4
4
  import { outputList } from './output.js';
5
5
  import { parsePositiveInt, parseNonNegativeInt, readBodyInput, requireFields } from './parsers.js';
6
6
  import { paginatedFetch } from './pagination.js';
7
+ import { buildContactFilter } from '../core/registry/pagination.js';
7
8
  import { formatId } from './format-helpers.js';
8
9
  const CONTACTS_COLUMNS = [
9
10
  { key: 'resourceId', header: 'ID', format: formatId },
@@ -48,16 +49,13 @@ export function registerContactsCommand(program) {
48
49
  .option('--format <type>', 'Output format: table, json, csv, yaml')
49
50
  .option('--json', 'Output as JSON')
50
51
  .action((query, opts) => apiAction(async (client) => {
51
- const filter = {
52
- status: { eq: opts.status ?? 'ACTIVE' },
53
- name: { contains: query },
54
- };
55
- if (opts.customer)
56
- filter.customer = { eq: true };
57
- if (opts.supplier)
58
- filter.supplier = { eq: true };
59
- if (opts.email)
60
- filter.email = { contains: opts.email };
52
+ const filter = buildContactFilter({
53
+ query,
54
+ isCustomer: opts.customer ? true : undefined,
55
+ isSupplier: opts.supplier ? true : undefined,
56
+ status: opts.status ?? 'ACTIVE',
57
+ email: opts.email,
58
+ });
61
59
  const sort = { sortBy: [opts.sort ?? 'name'], order: (opts.order ?? 'ASC') };
62
60
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchContacts(client, { filter, limit, offset, sort }), { label: 'Searching contacts', defaultLimit: 20 });
63
61
  outputList(result, CONTACTS_COLUMNS, opts, 'Contacts'); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { formatStatus, formatId, formatReference, formatCurrency } from './format-helpers.js';
3
+ import { buildCnFilter } from '../core/registry/index.js';
3
4
  import { listCustomerCreditNotes, getCustomerCreditNote, searchCustomerCreditNotes, createCustomerCreditNote, updateCustomerCreditNote, deleteCustomerCreditNote, createCustomerCreditNoteRefund, listCustomerCreditNoteRefunds, finalizeCustomerCreditNote, downloadCustomerCreditNotePdf, } from '../core/api/customer-cn.js';
4
5
  import { listAttachments } from '../core/api/attachments.js';
5
6
  import { apiAction } from './api-action.js';
@@ -87,14 +88,7 @@ export function registerCustomerCreditNotesCommand(program) {
87
88
  const resolved = await resolveContactFlag(client, opts.contact, { silent: opts.json });
88
89
  opts.contact = resolved.resourceId;
89
90
  }
90
- const filter = {};
91
- if (opts.ref)
92
- filter.reference = { contains: opts.ref };
93
- if (opts.status)
94
- filter.status = { eq: opts.status };
95
- if (opts.contact)
96
- filter.contactResourceId = { eq: opts.contact };
97
- const searchFilter = Object.keys(filter).length > 0 ? filter : undefined;
91
+ const searchFilter = buildCnFilter({ reference: opts.ref, status: opts.status, contactResourceId: opts.contact });
98
92
  const sort = { sortBy: [opts.sort ?? 'valueDate'], order: (opts.order ?? 'DESC') };
99
93
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchCustomerCreditNotes(client, { filter: searchFilter, limit, offset, sort }), { label: 'Searching customer credit notes', defaultLimit: 20 });
100
94
  outputList(result, CCN_COLUMNS, opts, 'Customer Credit Notes'); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -72,7 +72,7 @@ export function registerFixedAssetsCommand(program) {
72
72
  if (opts.status)
73
73
  filter.status = { eq: opts.status };
74
74
  if (opts.tag)
75
- filter.tags = { eq: opts.tag };
75
+ filter.tags = { name: { eq: opts.tag } };
76
76
  if (opts.category)
77
77
  filter.category = { eq: opts.category };
78
78
  if (opts.reference)
@@ -6,6 +6,7 @@ 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
8
  import { paginatedFetch } from './pagination.js';
9
+ import { buildInvoiceBillFilter } from '../core/registry/pagination.js';
9
10
  import { outputList } from './output.js';
10
11
  import { INVOICE_REQUIRED_FIELDS, buildDraftReport, formatDraftTable, addDraftFinalizeOptions, mergeDraftFlags, validateDraft, buildValidation, normalizeDate, sanitizeLineItem, } from './draft-helpers.js';
11
12
  const INVOICE_COLUMNS = [
@@ -75,7 +76,7 @@ export function registerInvoicesCommand(program) {
75
76
  .command('search')
76
77
  .description('Search invoices with filters')
77
78
  .option('--ref <reference>', 'Filter by reference (contains)')
78
- .option('--status <status>', 'Filter by status (DRAFT, UNPAID, PAID, OVERDUE, VOIDED)')
79
+ .option('--status <status>', 'Filter by status (DRAFT, UNPAID, PARTIALLY_PAID, PAID, OVERDUE, VOID)')
79
80
  .option('--contact <resourceId>', 'Filter by contact name or resourceId')
80
81
  .option('--tag <name>', 'Filter by tag')
81
82
  .option('--min-amount <n>', 'Minimum total amount', parseMoney)
@@ -99,40 +100,13 @@ export function registerInvoicesCommand(program) {
99
100
  const resolved = await resolveContactFlag(client, opts.contact, { silent: opts.json });
100
101
  opts.contact = resolved.resourceId;
101
102
  }
102
- const filter = {};
103
- if (opts.ref)
104
- filter.reference = { contains: opts.ref };
105
- if (opts.status)
106
- filter.status = { eq: opts.status };
107
- if (opts.contact)
108
- filter.contactResourceId = { eq: opts.contact };
109
- if (opts.tag)
110
- filter.tags = { eq: opts.tag };
111
- if (opts.minAmount !== undefined || opts.maxAmount !== undefined) {
112
- const af = {};
113
- if (opts.minAmount !== undefined)
114
- af.gte = opts.minAmount;
115
- if (opts.maxAmount !== undefined)
116
- af.lte = opts.maxAmount;
117
- filter.totalAmount = af;
118
- }
119
- if (opts.from || opts.to) {
120
- const dateFilter = {};
121
- if (opts.from)
122
- dateFilter.gte = opts.from;
123
- if (opts.to)
124
- dateFilter.lte = opts.to;
125
- filter.valueDate = dateFilter;
126
- }
127
- if (opts.dueFrom || opts.dueTo) {
128
- const df = {};
129
- if (opts.dueFrom)
130
- df.gte = opts.dueFrom;
131
- if (opts.dueTo)
132
- df.lte = opts.dueTo;
133
- filter.dueDate = df;
134
- }
135
- const searchFilter = Object.keys(filter).length > 0 ? filter : undefined;
103
+ const searchFilter = buildInvoiceBillFilter({
104
+ reference: opts.ref, status: opts.status,
105
+ contactResourceId: opts.contact, tag: opts.tag,
106
+ minAmount: opts.minAmount, maxAmount: opts.maxAmount,
107
+ startDate: opts.from, endDate: opts.to,
108
+ dueDateFrom: opts.dueFrom, dueDateTo: opts.dueTo,
109
+ });
136
110
  const sort = { sortBy: [opts.sort ?? 'valueDate'], order: (opts.order ?? 'DESC') };
137
111
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchInvoices(client, { filter: searchFilter, limit, offset, sort }), { label: 'Searching invoices', defaultLimit: 20 });
138
112
  outputList(result, INVOICE_COLUMNS, opts, 'Invoices'); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -6,6 +6,7 @@ import { apiAction } from './api-action.js';
6
6
  import { resolveAccountFlag } from './resolve.js';
7
7
  import { parsePositiveInt, parseNonNegativeInt, parseJournalEntries, readBodyInput, requireFields } from './parsers.js';
8
8
  import { paginatedFetch } from './pagination.js';
9
+ import { buildJournalFilter } from '../core/registry/pagination.js';
9
10
  import { outputList } from './output.js';
10
11
  import { JOURNAL_REQUIRED_FIELDS, buildDraftReport, formatDraftTable, addJournalDraftFinalizeOptions, mergeJournalDraftFlags, validateDraft, buildValidation, normalizeDate, sanitizeJournalEntry, } from './draft-helpers.js';
11
12
  const JOURNAL_COLUMNS = [
@@ -41,7 +42,7 @@ export function registerJournalsCommand(program) {
41
42
  .option('--ref <reference>', 'Filter by reference (contains)')
42
43
  .option('--from <YYYY-MM-DD>', 'Filter from date (inclusive)')
43
44
  .option('--to <YYYY-MM-DD>', 'Filter to date (inclusive)')
44
- .option('--status <status>', 'Filter by status (DRAFT, FINALIZED)')
45
+ .option('--status <status>', 'Filter by status (DRAFT, FINALIZED, VOID)')
45
46
  .option('--tag <name>', 'Filter by tag')
46
47
  .option('--type <type>', 'Filter by type (JOURNAL_MANUAL, JOURNAL_DIRECT_CASH_IN, JOURNAL_DIRECT_CASH_OUT)')
47
48
  .option('--sort <field>', 'Sort field (default: valueDate)')
@@ -54,24 +55,10 @@ export function registerJournalsCommand(program) {
54
55
  .option('--api-key <key>', 'API key (overrides stored/env)')
55
56
  .option('--json', 'Output as JSON')
56
57
  .action(apiAction(async (client, opts) => {
57
- const filter = {};
58
- if (opts.ref)
59
- filter.reference = { contains: opts.ref };
60
- if (opts.status)
61
- filter.status = { eq: opts.status };
62
- if (opts.tag)
63
- filter.tags = { eq: opts.tag };
64
- if (opts.type)
65
- filter.type = { eq: opts.type };
66
- if (opts.from || opts.to) {
67
- const dateFilter = {};
68
- if (opts.from)
69
- dateFilter.gte = opts.from;
70
- if (opts.to)
71
- dateFilter.lte = opts.to;
72
- filter.valueDate = dateFilter;
73
- }
74
- const searchFilter = Object.keys(filter).length > 0 ? filter : undefined;
58
+ const searchFilter = buildJournalFilter({
59
+ reference: opts.ref, status: opts.status, tag: opts.tag,
60
+ type: opts.type, startDate: opts.from, endDate: opts.to,
61
+ });
75
62
  const sort = { sortBy: [opts.sort ?? 'valueDate'], order: (opts.order ?? 'DESC') };
76
63
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchJournals(client, { filter: searchFilter, limit, offset, sort }), { label: 'Searching journals', defaultLimit: 20 });
77
64
  outputList(result, JOURNAL_COLUMNS, opts, 'Journals'); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { formatStatus, formatId, formatReference, formatCurrency } from './format-helpers.js';
3
+ import { buildCnFilter } from '../core/registry/index.js';
3
4
  import { listSupplierCreditNotes, getSupplierCreditNote, searchSupplierCreditNotes, createSupplierCreditNote, updateSupplierCreditNote, deleteSupplierCreditNote, createSupplierCreditNoteRefund, listSupplierCreditNoteRefunds, finalizeSupplierCreditNote, } from '../core/api/supplier-cn.js';
4
5
  import { listAttachments } from '../core/api/attachments.js';
5
6
  import { apiAction } from './api-action.js';
@@ -87,14 +88,7 @@ export function registerSupplierCreditNotesCommand(program) {
87
88
  const resolved = await resolveContactFlag(client, opts.contact, { silent: opts.json });
88
89
  opts.contact = resolved.resourceId;
89
90
  }
90
- const filter = {};
91
- if (opts.ref)
92
- filter.reference = { contains: opts.ref };
93
- if (opts.status)
94
- filter.status = { eq: opts.status };
95
- if (opts.contact)
96
- filter.contactResourceId = { eq: opts.contact };
97
- const searchFilter = Object.keys(filter).length > 0 ? filter : undefined;
91
+ const searchFilter = buildCnFilter({ reference: opts.ref, status: opts.status, contactResourceId: opts.contact });
98
92
  const sort = { sortBy: [opts.sort ?? 'valueDate'], order: (opts.order ?? 'DESC') };
99
93
  const result = await paginatedFetch(opts, ({ limit, offset }) => searchSupplierCreditNotes(client, { filter: searchFilter, limit, offset, sort }), { label: 'Searching supplier credit notes', defaultLimit: 20 });
100
94
  outputList(result, SCN_COLUMNS, opts, 'Supplier Credit Notes'); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -24,18 +24,20 @@ export async function fetchAllPages(fetcher, options = {}) {
24
24
  const pageSize = Math.min(Math.max(options.pageSize ?? DEFAULT_PAGE_SIZE, 1), 1000);
25
25
  const rawConcurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
26
26
  const concurrency = Math.max(Math.floor(rawConcurrency), 1);
27
+ const maxItems = options.maxItems ?? Infinity;
27
28
  const onProgress = options.onProgress;
28
29
  // ── Step 1: First page ──
29
30
  const firstPage = await fetcher(0, pageSize);
30
31
  const total = firstPage.totalElements;
31
32
  let requestCount = 1;
32
- // Short-circuit: single page or empty
33
- if (total <= pageSize) {
34
- onProgress?.(firstPage.data.length, total);
33
+ // Short-circuit: single page, empty, or maxItems satisfied by first page
34
+ if (total <= pageSize || firstPage.data.length >= maxItems) {
35
+ const data = maxItems < Infinity ? firstPage.data.slice(0, maxItems) : firstPage.data;
36
+ onProgress?.(data.length, total);
35
37
  return {
36
- data: firstPage.data,
38
+ data,
37
39
  totalElements: total,
38
- truncated: false,
40
+ truncated: data.length < total,
39
41
  requestCount,
40
42
  };
41
43
  }
@@ -59,11 +61,14 @@ export async function fetchAllPages(fetcher, options = {}) {
59
61
  allData.push(...page.data);
60
62
  }
61
63
  onProgress?.(allData.length, total);
64
+ if (allData.length >= maxItems)
65
+ break;
62
66
  }
67
+ const cappedData = maxItems < Infinity ? allData.slice(0, maxItems) : allData;
63
68
  return {
64
- data: allData,
69
+ data: cappedData,
65
70
  totalElements: total,
66
- truncated,
71
+ truncated: truncated || cappedData.length < allData.length,
67
72
  requestCount,
68
73
  };
69
74
  }
@@ -1,3 +1,3 @@
1
1
  export { TOOL_DEFINITIONS } from './tools.js';
2
2
  export { getToolByName, getAllTools, getToolsByGroup, getReadOnlyTools, getWriteTools, getToolCount, } from './lookup.js';
3
- export { handlePaginationMode, buildCnFilter } from './pagination.js';
3
+ export { handlePagination, buildCnFilter, buildBankRecordFilter, buildInvoiceBillFilter, buildJournalFilter, buildContactFilter, buildCashflowFilter } from './pagination.js';
@@ -1,45 +1,6 @@
1
- /**
2
- * Pagination mode helpers for list/search tools.
3
- * Moved from agent/tools.ts shared by all list/search tool execute functions.
4
- */
5
- import { fetchAllPages } from '../api/pagination.js';
6
- /**
7
- * Handle pagination mode for list/search tools.
8
- * DRY helper — all list/search tools route through this.
9
- *
10
- * Modes:
11
- * - default (omit mode): caller sets limit/offset (offset = page number, 0-indexed)
12
- * - 'sample': returns 20 items + totalElements metadata
13
- * - 'all': fetches all pages concurrently via fetchAllPages
14
- */
15
- export async function handlePaginationMode(mode, fetcher, limit, offset, defaultLimit) {
16
- switch (mode) {
17
- case 'all': {
18
- const pageSize = Math.min(Math.max(limit ?? 200, 1), 1000);
19
- const result = await fetchAllPages(fetcher, { pageSize, concurrency: 3 });
20
- return {
21
- _mode: 'all',
22
- _note: result.truncated
23
- ? `Fetched ${result.data.length} of ${result.totalElements} items (offset cap: 65,536). ${result.requestCount} API calls.`
24
- : `Fetched all ${result.totalElements} items. ${result.requestCount} API calls.`,
25
- data: result.data,
26
- totalElements: result.totalElements,
27
- truncated: result.truncated,
28
- };
29
- }
30
- case 'sample': {
31
- const result = await fetcher(0, 20);
32
- return {
33
- _mode: 'sample',
34
- _note: `Showing ${result.data.length} of ${result.totalElements} items. Use limit/offset to page, or mode 'all' to fetch everything.`,
35
- data: result.data,
36
- totalElements: result.totalElements,
37
- totalPages: result.totalPages,
38
- };
39
- }
40
- default:
41
- return fetcher(offset ?? 0, limit ?? defaultLimit);
42
- }
1
+ /** Paginate a tool call with sensible defaults. */
2
+ export async function handlePagination(fetcher, limit, offset, defaultLimit) {
3
+ return fetcher(offset ?? 0, limit ?? defaultLimit);
43
4
  }
44
5
  /** Build a credit note search filter from agent input (shared by customer + supplier CN search). */
45
6
  export function buildCnFilter(input) {
@@ -52,6 +13,106 @@ export function buildCnFilter(input) {
52
13
  f.contactResourceId = { eq: input.contactResourceId };
53
14
  return Object.keys(f).length > 0 ? f : undefined;
54
15
  }
16
+ /** Build invoice/bill search filter (shared by CLI + tool + MCP). */
17
+ export function buildInvoiceBillFilter(input) {
18
+ const f = {};
19
+ if (input.reference)
20
+ f.reference = { contains: input.reference };
21
+ if (input.status)
22
+ f.status = { eq: input.status };
23
+ if (input.contactResourceId)
24
+ f.contactResourceId = { eq: input.contactResourceId };
25
+ if (input.contactName)
26
+ f.contact = { name: { contains: input.contactName } };
27
+ if (input.tag)
28
+ f.tags = { name: { eq: input.tag } };
29
+ if (input.currencyCode)
30
+ f.currencyCode = { eq: input.currencyCode };
31
+ const minAmt = typeof input.minAmount === 'number' && Number.isFinite(input.minAmount) ? input.minAmount : undefined;
32
+ const maxAmt = typeof input.maxAmount === 'number' && Number.isFinite(input.maxAmount) ? input.maxAmount : undefined;
33
+ if (minAmt != null || maxAmt != null) {
34
+ const a = {};
35
+ if (minAmt != null)
36
+ a.gte = minAmt;
37
+ if (maxAmt != null)
38
+ a.lte = maxAmt;
39
+ f.totalAmount = a;
40
+ }
41
+ if (input.startDate || input.endDate) {
42
+ const d = {};
43
+ if (input.startDate)
44
+ d.gte = input.startDate;
45
+ if (input.endDate)
46
+ d.lte = input.endDate;
47
+ f.valueDate = d;
48
+ }
49
+ if (input.dueDateFrom || input.dueDateTo) {
50
+ const d = {};
51
+ if (input.dueDateFrom)
52
+ d.gte = input.dueDateFrom;
53
+ if (input.dueDateTo)
54
+ d.lte = input.dueDateTo;
55
+ f.dueDate = d;
56
+ }
57
+ return Object.keys(f).length > 0 ? f : undefined;
58
+ }
59
+ /** Build journal search filter (shared by CLI + tool + MCP). */
60
+ export function buildJournalFilter(input) {
61
+ const f = {};
62
+ if (input.reference)
63
+ f.reference = { contains: input.reference };
64
+ if (input.status)
65
+ f.status = { eq: input.status };
66
+ if (input.tag)
67
+ f.tags = { name: { eq: input.tag } };
68
+ if (input.type)
69
+ f.type = { eq: input.type };
70
+ if (input.startDate || input.endDate) {
71
+ const d = {};
72
+ if (input.startDate)
73
+ d.gte = input.startDate;
74
+ if (input.endDate)
75
+ d.lte = input.endDate;
76
+ f.valueDate = d;
77
+ }
78
+ return Object.keys(f).length > 0 ? f : undefined;
79
+ }
80
+ /** Build contact search filter (shared by CLI + tool + MCP). */
81
+ export function buildContactFilter(input) {
82
+ const f = {};
83
+ if (input.query)
84
+ f.or = { billingName: { contains: input.query }, name: { contains: input.query } };
85
+ if (input.isCustomer !== undefined)
86
+ f.customer = { eq: input.isCustomer };
87
+ if (input.isSupplier !== undefined)
88
+ f.supplier = { eq: input.isSupplier };
89
+ if (input.status)
90
+ f.status = { eq: input.status };
91
+ if (input.email)
92
+ f.email = { contains: input.email };
93
+ return Object.keys(f).length > 0 ? f : undefined;
94
+ }
95
+ /** Build cashflow transaction search filter (shared by CLI + tool + MCP). */
96
+ export function buildCashflowFilter(input) {
97
+ const f = {};
98
+ if (input.businessTransactionType)
99
+ f.businessTransactionType = { eq: input.businessTransactionType };
100
+ if (input.direction)
101
+ f.direction = { eq: input.direction };
102
+ if (input.status)
103
+ f.businessTransactionStatus = { eq: input.status };
104
+ if (input.reference)
105
+ f.transactionReference = { contains: input.reference };
106
+ if (input.startDate || input.endDate) {
107
+ const d = {};
108
+ if (input.startDate)
109
+ d.gte = input.startDate;
110
+ if (input.endDate)
111
+ d.lte = input.endDate;
112
+ f.valueDate = d;
113
+ }
114
+ return Object.keys(f).length > 0 ? f : undefined;
115
+ }
55
116
  /** Build a bank record search filter (shared by CLI + MCP tool). */
56
117
  export function buildBankRecordFilter(input) {
57
118
  const f = {};
@@ -29,7 +29,7 @@ import { downloadExport } from '../api/data-exports.js';
29
29
  import { listAttachments, addAttachment, fetchAttachmentTable } from '../api/attachments.js';
30
30
  import { createFromAttachment, searchMagicWorkflows } from '../api/magic.js';
31
31
  import { messageToPdf } from '../api/message-pdf.js';
32
- import { handlePaginationMode, buildCnFilter, buildBankRecordFilter } from './pagination.js';
32
+ import { handlePagination, buildCnFilter, buildBankRecordFilter, buildInvoiceBillFilter, buildJournalFilter, buildContactFilter, buildCashflowFilter } from './pagination.js';
33
33
  // New API modules (200-tools expansion)
34
34
  import { listBankRules, getBankRule, searchBankRules, createBankRule, updateBankRule, deleteBankRule, } from '../api/bank-rules.js';
35
35
  import { listFixedAssets, getFixedAsset, searchFixedAssets, createFixedAsset, updateFixedAsset, deleteFixedAsset, discardFixedAsset, markFixedAssetSold, transferFixedAsset, undoFixedAssetDisposal, } from '../api/fixed-assets.js';
@@ -55,13 +55,12 @@ import { generateStatutoryFilingBlueprint } from '../jobs/statutory-filing/bluep
55
55
  import { buildDraftReport, INVOICE_REQUIRED_FIELDS, BILL_REQUIRED_FIELDS, CREDIT_NOTE_REQUIRED_FIELDS, JOURNAL_REQUIRED_FIELDS, } from '../drafts/index.js';
56
56
  // ── Shared param snippets (DRY) ─────────────────────────────────
57
57
  const PAGINATION_PARAMS = {
58
- limit: { type: 'number', description: 'Max results per page (default 100, max 1000)' },
59
- offset: { type: 'number', description: 'Pagination offset (use with limit to page through results)' },
60
- mode: { type: 'string', enum: ['sample', 'all'], description: "Pagination mode: 'sample' (20 items + total count), 'all' (fetch everything — may be slow for large datasets). Omit for default limit/offset behavior." },
58
+ limit: { type: 'number', description: 'Max results per page (default 20, max 1000).' },
59
+ offset: { type: 'number', description: 'Page offset (0-indexed). Use with limit to paginate.' },
61
60
  };
62
61
  const SEARCH_PARAMS = {
63
62
  ...PAGINATION_PARAMS,
64
- sortBy: { type: 'string', description: 'Sort field (overrides default)' },
63
+ sortBy: { type: 'string', description: 'Sort field (always sent with queries). Common: valueDate, reference, status, name. Default: valueDate' },
65
64
  order: { type: 'string', enum: ['ASC', 'DESC'], description: 'Sort direction' },
66
65
  };
67
66
  const CURRENCY_PARAM = {
@@ -122,13 +121,12 @@ const LINE_ITEM_PARAM = {
122
121
  };
123
122
  // ── Helper to extract common input fields ────────────────────────
124
123
  function extractPaginationInput(input) {
125
- const mode = input.mode;
126
124
  const limit = input.limit;
127
125
  const offset = input.offset;
128
126
  const sortBy = input.sortBy;
129
127
  const rawOrder = input.order;
130
128
  const sortOrder = rawOrder === 'ASC' || rawOrder === 'DESC' ? rawOrder : undefined;
131
- return { mode, limit, offset, sortBy, sortOrder };
129
+ return { limit, offset, sortBy, sortOrder };
132
130
  }
133
131
  // ── List tool factory (DRY — all 10 list tools share this) ───────
134
132
  function listTool(name, description, group, fetcher) {
@@ -140,8 +138,8 @@ function listTool(name, description, group, fetcher) {
140
138
  group,
141
139
  readOnly: true,
142
140
  execute: async (ctx, input) => {
143
- const { mode, limit, offset } = extractPaginationInput(input);
144
- return handlePaginationMode(mode, (off, lim) => fetcher(ctx.client, off, lim), limit, offset, 100);
141
+ const { limit, offset } = extractPaginationInput(input);
142
+ return handlePagination((off, lim) => fetcher(ctx.client, off, lim), limit, offset, 20);
145
143
  },
146
144
  };
147
145
  }
@@ -197,13 +195,13 @@ export const TOOL_DEFINITIONS = [
197
195
  group: 'accounts',
198
196
  readOnly: true,
199
197
  execute: async (ctx, input) => {
200
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
198
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
201
199
  const query = input.query;
202
- return handlePaginationMode(mode, (off, lim) => searchAccounts(ctx.client, {
200
+ return handlePagination((off, lim) => searchAccounts(ctx.client, {
203
201
  filter: { or: { name: { contains: query }, code: { contains: query } } },
204
202
  limit: lim, offset: off,
205
203
  sort: { sortBy: [sortBy ?? 'code'], order: (sortOrder ?? 'ASC') },
206
- }), limit, offset, 100);
204
+ }), limit, offset, 20);
207
205
  },
208
206
  },
209
207
  {
@@ -263,22 +261,25 @@ export const TOOL_DEFINITIONS = [
263
261
  listTool('list_contacts', 'List contacts (customers/suppliers). Returns billingName, name, emails, status. Paginated — response includes totalElements. Use limit/offset to page.', 'contacts', (client, off, lim) => listContacts(client, { limit: lim, offset: off })),
264
262
  {
265
263
  name: 'search_contacts',
266
- description: 'Search contacts by name. Searches both billingName and name fields. Returns up to 100 by default. Use limit/offset to page through large result sets.',
264
+ description: 'Search contacts with filters. Searches both billingName and name fields when query is provided.',
267
265
  params: {
268
- query: { type: 'string', description: 'Search term' },
266
+ query: { type: 'string', description: 'Search by name or billing name' },
267
+ isCustomer: { type: 'boolean', description: 'Filter to customers only' },
268
+ isSupplier: { type: 'boolean', description: 'Filter to suppliers only' },
269
+ status: { type: 'string', enum: ['ACTIVE', 'INACTIVE'], description: 'Filter by status' },
270
+ email: { type: 'string', description: 'Filter by email (contains)' },
269
271
  ...SEARCH_PARAMS,
270
272
  },
271
- required: ['query'],
273
+ required: [],
272
274
  group: 'contacts',
273
275
  readOnly: true,
274
276
  execute: async (ctx, input) => {
275
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
276
- const query = input.query;
277
- return handlePaginationMode(mode, (off, lim) => searchContacts(ctx.client, {
278
- filter: { or: { billingName: { contains: query }, name: { contains: query } } },
277
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
278
+ return handlePagination((off, lim) => searchContacts(ctx.client, {
279
+ filter: buildContactFilter(input),
279
280
  limit: lim, offset: off,
280
281
  sort: { sortBy: [sortBy ?? 'billingName'], order: (sortOrder ?? 'ASC') },
281
- }), limit, offset, 100);
282
+ }), limit, offset, 20);
282
283
  },
283
284
  },
284
285
  {
@@ -334,42 +335,32 @@ export const TOOL_DEFINITIONS = [
334
335
  listTool('list_invoices', 'List invoices. Returns reference, date, status, contact, totalAmount. Paginated — response includes totalElements. Use limit/offset to page.', 'invoices', (client, off, lim) => listInvoices(client, { limit: lim, offset: off })),
335
336
  {
336
337
  name: 'search_invoices',
337
- description: 'Search invoices by reference, contact name, and/or date range. Provide any combination of filters.',
338
+ description: 'Search invoices with filters. Provide any combination: reference, contact, status, date range, due date range, amount range, tag, currency.',
338
339
  params: {
339
340
  query: { type: 'string', description: 'Search by invoice reference number' },
340
341
  contactName: { type: 'string', description: 'Search by contact name' },
342
+ status: { type: 'string', enum: ['DRAFT', 'UNPAID', 'PARTIALLY_PAID', 'PAID', 'OVERDUE', 'VOID'], description: 'Filter by status' },
343
+ tag: { type: 'string', description: 'Filter by tag name' },
341
344
  startDate: { type: 'string', description: 'Filter invoices on or after this date (YYYY-MM-DD)' },
342
345
  endDate: { type: 'string', description: 'Filter invoices on or before this date (YYYY-MM-DD)' },
346
+ dueDateFrom: { type: 'string', description: 'Filter by due date from (YYYY-MM-DD)' },
347
+ dueDateTo: { type: 'string', description: 'Filter by due date to (YYYY-MM-DD)' },
348
+ minAmount: { type: 'number', description: 'Minimum total amount' },
349
+ maxAmount: { type: 'number', description: 'Maximum total amount' },
350
+ currencyCode: { type: 'string', description: 'Filter by currency code (e.g. USD, SGD)' },
343
351
  ...SEARCH_PARAMS,
344
352
  },
345
353
  required: [],
346
354
  group: 'invoices',
347
355
  readOnly: true,
348
356
  execute: async (ctx, input) => {
349
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
350
- const query = input.query;
351
- const contactName = input.contactName;
352
- const startDate = input.startDate;
353
- const endDate = input.endDate;
354
- // Build filter — avoid nested `contact` inside `or` (causes 400)
355
- const filter = {};
356
- if (query)
357
- filter.reference = { contains: query };
358
- if (contactName)
359
- filter.contact = { name: { contains: contactName } };
360
- if (startDate || endDate) {
361
- const dateFilter = {};
362
- if (startDate)
363
- dateFilter.gte = startDate;
364
- if (endDate)
365
- dateFilter.lte = endDate;
366
- filter.valueDate = dateFilter;
367
- }
368
- return handlePaginationMode(mode, (off, lim) => searchInvoices(ctx.client, {
369
- filter: Object.keys(filter).length > 0 ? filter : undefined,
357
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
358
+ const filterInput = { ...input, reference: input.reference ?? input.query };
359
+ return handlePagination((off, lim) => searchInvoices(ctx.client, {
360
+ filter: buildInvoiceBillFilter(filterInput),
370
361
  limit: lim, offset: off,
371
362
  sort: { sortBy: [sortBy ?? 'valueDate'], order: (sortOrder ?? 'DESC') },
372
- }), limit, offset, 100);
363
+ }), limit, offset, 20);
373
364
  },
374
365
  },
375
366
  {
@@ -493,6 +484,11 @@ export const TOOL_DEFINITIONS = [
493
484
  readOnly: false,
494
485
  execute: async (ctx, input) => {
495
486
  const { resourceId: rid, ...data } = input;
487
+ // API requires lineItems on PUT even for status-only changes — fetch if not provided
488
+ if (!data.lineItems) {
489
+ const current = await getInvoice(ctx.client, rid);
490
+ data.lineItems = current.lineItems;
491
+ }
496
492
  return finalizeInvoice(ctx.client, rid, data);
497
493
  },
498
494
  },
@@ -534,41 +530,32 @@ export const TOOL_DEFINITIONS = [
534
530
  listTool('list_bills', 'List bills (purchase invoices). Returns reference, date, status, contact, amounts. Paginated — response includes totalElements. Use limit/offset to page.', 'bills', (client, off, lim) => listBills(client, { limit: lim, offset: off })),
535
531
  {
536
532
  name: 'search_bills',
537
- description: 'Search bills by reference, contact name, and/or date range. Provide any combination of filters.',
533
+ description: 'Search bills with filters. Provide any combination: reference, contact, status, date range, due date range, amount range, tag, currency.',
538
534
  params: {
539
535
  query: { type: 'string', description: 'Search by bill reference number' },
540
536
  contactName: { type: 'string', description: 'Search by contact name' },
537
+ status: { type: 'string', enum: ['DRAFT', 'UNPAID', 'PARTIALLY_PAID', 'PAID', 'OVERDUE', 'VOID'], description: 'Filter by status' },
538
+ tag: { type: 'string', description: 'Filter by tag name' },
541
539
  startDate: { type: 'string', description: 'Filter bills on or after this date (YYYY-MM-DD)' },
542
540
  endDate: { type: 'string', description: 'Filter bills on or before this date (YYYY-MM-DD)' },
541
+ dueDateFrom: { type: 'string', description: 'Filter by due date from (YYYY-MM-DD)' },
542
+ dueDateTo: { type: 'string', description: 'Filter by due date to (YYYY-MM-DD)' },
543
+ minAmount: { type: 'number', description: 'Minimum total amount' },
544
+ maxAmount: { type: 'number', description: 'Maximum total amount' },
545
+ currencyCode: { type: 'string', description: 'Filter by currency code (e.g. USD, SGD)' },
543
546
  ...SEARCH_PARAMS,
544
547
  },
545
548
  required: [],
546
549
  group: 'bills',
547
550
  readOnly: true,
548
551
  execute: async (ctx, input) => {
549
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
550
- const query = input.query;
551
- const contactName = input.contactName;
552
- const startDate = input.startDate;
553
- const endDate = input.endDate;
554
- const filter = {};
555
- if (query)
556
- filter.reference = { contains: query };
557
- if (contactName)
558
- filter.contact = { name: { contains: contactName } };
559
- if (startDate || endDate) {
560
- const dateFilter = {};
561
- if (startDate)
562
- dateFilter.gte = startDate;
563
- if (endDate)
564
- dateFilter.lte = endDate;
565
- filter.valueDate = dateFilter;
566
- }
567
- return handlePaginationMode(mode, (off, lim) => searchBills(ctx.client, {
568
- filter: Object.keys(filter).length > 0 ? filter : undefined,
552
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
553
+ const filterInput = { ...input, reference: input.reference ?? input.query };
554
+ return handlePagination((off, lim) => searchBills(ctx.client, {
555
+ filter: buildInvoiceBillFilter(filterInput),
569
556
  limit: lim, offset: off,
570
557
  sort: { sortBy: [sortBy ?? 'valueDate'], order: (sortOrder ?? 'DESC') },
571
- }), limit, offset, 100);
558
+ }), limit, offset, 20);
572
559
  },
573
560
  },
574
561
  {
@@ -644,7 +631,7 @@ export const TOOL_DEFINITIONS = [
644
631
  },
645
632
  {
646
633
  name: 'pay_bill',
647
- description: 'Record a payment against a bill.',
634
+ description: 'Record a payment against a bill. Bill must be APPROVED before payment — draft or voided bills cannot be paid.',
648
635
  params: {
649
636
  resourceId: { type: 'string' },
650
637
  paymentAmount: { type: 'number' },
@@ -695,6 +682,11 @@ export const TOOL_DEFINITIONS = [
695
682
  readOnly: false,
696
683
  execute: async (ctx, input) => {
697
684
  const { resourceId: rid, ...data } = input;
685
+ // API requires lineItems on PUT even for status-only changes — fetch if not provided
686
+ if (!data.lineItems) {
687
+ const current = await getBill(ctx.client, rid);
688
+ data.lineItems = current.lineItems;
689
+ }
698
690
  return finalizeBill(ctx.client, rid, data);
699
691
  },
700
692
  },
@@ -725,22 +717,27 @@ export const TOOL_DEFINITIONS = [
725
717
  listTool('list_journals', 'List journal entries. Paginated — response includes totalElements. Use limit/offset to page.', 'journals', (client, off, lim) => listJournals(client, { limit: lim, offset: off })),
726
718
  {
727
719
  name: 'search_journals',
728
- description: 'Search journals by reference. Returns up to 100 by default. Use limit/offset to page through large result sets.',
729
- params: {
730
- query: { type: 'string' },
720
+ description: 'Search journals with filters. Provide any combination: reference, status, tag, type, date range.',
721
+ params: {
722
+ query: { type: 'string', description: 'Search by reference (contains)' },
723
+ status: { type: 'string', enum: ['DRAFT', 'FINALIZED', 'VOID'], description: 'Filter by status' },
724
+ tag: { type: 'string', description: 'Filter by tag name' },
725
+ type: { type: 'string', description: 'Filter by type (e.g. JOURNAL_MANUAL, JOURNAL_DIRECT_CASH_IN, JOURNAL_DIRECT_CASH_OUT)' },
726
+ startDate: { type: 'string', description: 'Filter journals on or after this date (YYYY-MM-DD)' },
727
+ endDate: { type: 'string', description: 'Filter journals on or before this date (YYYY-MM-DD)' },
731
728
  ...SEARCH_PARAMS,
732
729
  },
733
- required: ['query'],
730
+ required: [],
734
731
  group: 'journals',
735
732
  readOnly: true,
736
733
  execute: async (ctx, input) => {
737
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
738
- const query = input.query;
739
- return handlePaginationMode(mode, (off, lim) => searchJournals(ctx.client, {
740
- filter: { reference: { contains: query } },
734
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
735
+ const filterInput = { ...input, reference: input.reference ?? input.query };
736
+ return handlePagination((off, lim) => searchJournals(ctx.client, {
737
+ filter: buildJournalFilter(filterInput),
741
738
  limit: lim, offset: off,
742
739
  sort: { sortBy: [sortBy ?? 'valueDate'], order: (sortOrder ?? 'DESC') },
743
- }), limit, offset, 100);
740
+ }), limit, offset, 20);
744
741
  },
745
742
  },
746
743
  {
@@ -802,13 +799,16 @@ export const TOOL_DEFINITIONS = [
802
799
  name: 'generate_balance_sheet',
803
800
  description: 'Generate a balance sheet report.',
804
801
  params: {
805
- snapshotDate: { type: 'string', description: 'Snapshot date (YYYY-MM-DD)' },
802
+ snapshotDate: { type: 'string', description: 'Snapshot date (YYYY-MM-DD). Defaults to today if omitted.' },
806
803
  currencyCode: { type: 'string' },
807
804
  },
808
- required: ['snapshotDate'],
805
+ required: [],
809
806
  group: 'reports',
810
807
  readOnly: true,
811
- execute: async (ctx, input) => generateBalanceSheet(ctx.client, input),
808
+ execute: async (ctx, input) => {
809
+ const date = input.snapshotDate ?? new Date().toISOString().slice(0, 10);
810
+ return generateBalanceSheet(ctx.client, { primarySnapshotDate: date, currencyCode: input.currencyCode });
811
+ },
812
812
  },
813
813
  {
814
814
  name: 'generate_profit_and_loss',
@@ -913,7 +913,7 @@ export const TOOL_DEFINITIONS = [
913
913
  group: 'items',
914
914
  readOnly: true,
915
915
  execute: async (ctx, input) => {
916
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
916
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
917
917
  const filter = {};
918
918
  if (input.appliesToSale !== undefined)
919
919
  filter.appliesToSale = { eq: input.appliesToSale };
@@ -923,11 +923,11 @@ export const TOOL_DEFINITIONS = [
923
923
  filter.status = { eq: input.status };
924
924
  if (input.itemCategory)
925
925
  filter.itemCategory = { eq: input.itemCategory };
926
- return handlePaginationMode(mode, (off, lim) => searchItems(ctx.client, {
926
+ return handlePagination((off, lim) => searchItems(ctx.client, {
927
927
  filter: Object.keys(filter).length > 0 ? filter : undefined,
928
928
  limit: lim, offset: off,
929
929
  sort: { sortBy: [sortBy ?? 'internalName'], order: (sortOrder ?? 'ASC') },
930
- }), limit, offset, 100);
930
+ }), limit, offset, 20);
931
931
  },
932
932
  },
933
933
  {
@@ -1006,13 +1006,13 @@ export const TOOL_DEFINITIONS = [
1006
1006
  group: 'tags',
1007
1007
  readOnly: true,
1008
1008
  execute: async (ctx, input) => {
1009
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1009
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1010
1010
  const query = input.query;
1011
- return handlePaginationMode(mode, (off, lim) => searchTags(ctx.client, {
1011
+ return handlePagination((off, lim) => searchTags(ctx.client, {
1012
1012
  filter: { tagName: { contains: query } },
1013
1013
  limit: lim, offset: off,
1014
1014
  sort: { sortBy: [sortBy ?? 'tagName'], order: (sortOrder ?? 'ASC') },
1015
- }), limit, offset, 100);
1015
+ }), limit, offset, 20);
1016
1016
  },
1017
1017
  },
1018
1018
  {
@@ -1059,13 +1059,13 @@ export const TOOL_DEFINITIONS = [
1059
1059
  group: 'capsules',
1060
1060
  readOnly: true,
1061
1061
  execute: async (ctx, input) => {
1062
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1062
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1063
1063
  const query = input.query;
1064
- return handlePaginationMode(mode, (off, lim) => searchCapsules(ctx.client, {
1064
+ return handlePagination((off, lim) => searchCapsules(ctx.client, {
1065
1065
  filter: { title: { contains: query } },
1066
1066
  limit: lim, offset: off,
1067
1067
  sort: { sortBy: [sortBy ?? 'title'], order: (sortOrder ?? 'ASC') },
1068
- }), limit, offset, 100);
1068
+ }), limit, offset, 20);
1069
1069
  },
1070
1070
  },
1071
1071
  {
@@ -1138,12 +1138,12 @@ export const TOOL_DEFINITIONS = [
1138
1138
  group: 'customer_credit_notes',
1139
1139
  readOnly: true,
1140
1140
  execute: async (ctx, input) => {
1141
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1142
- return handlePaginationMode(mode, (off, lim) => searchCustomerCreditNotes(ctx.client, {
1141
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1142
+ return handlePagination((off, lim) => searchCustomerCreditNotes(ctx.client, {
1143
1143
  filter: buildCnFilter(input),
1144
1144
  limit: lim, offset: off,
1145
1145
  sort: { sortBy: [sortBy ?? 'valueDate'], order: (sortOrder ?? 'DESC') },
1146
- }), limit, offset, 100);
1146
+ }), limit, offset, 20);
1147
1147
  },
1148
1148
  },
1149
1149
  {
@@ -1307,12 +1307,12 @@ export const TOOL_DEFINITIONS = [
1307
1307
  group: 'supplier_credit_notes',
1308
1308
  readOnly: true,
1309
1309
  execute: async (ctx, input) => {
1310
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1311
- return handlePaginationMode(mode, (off, lim) => searchSupplierCreditNotes(ctx.client, {
1310
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1311
+ return handlePagination((off, lim) => searchSupplierCreditNotes(ctx.client, {
1312
1312
  filter: buildCnFilter(input),
1313
1313
  limit: lim, offset: off,
1314
1314
  sort: { sortBy: [sortBy ?? 'valueDate'], order: (sortOrder ?? 'DESC') },
1315
- }), limit, offset, 100);
1315
+ }), limit, offset, 20);
1316
1316
  },
1317
1317
  },
1318
1318
  {
@@ -1493,9 +1493,9 @@ export const TOOL_DEFINITIONS = [
1493
1493
  group: 'currencies',
1494
1494
  readOnly: true,
1495
1495
  execute: async (ctx, input) => {
1496
- const { mode, limit, offset } = extractPaginationInput(input);
1496
+ const { limit, offset } = extractPaginationInput(input);
1497
1497
  const cc = input.currencyCode;
1498
- return handlePaginationMode(mode, (off, lim) => listCurrencyRates(ctx.client, cc, { limit: lim, offset: off }), limit, offset, 100);
1498
+ return handlePagination((off, lim) => listCurrencyRates(ctx.client, cc, { limit: lim, offset: off }), limit, offset, 100);
1499
1499
  },
1500
1500
  },
1501
1501
  {
@@ -1815,12 +1815,12 @@ export const TOOL_DEFINITIONS = [
1815
1815
  group: 'bank',
1816
1816
  readOnly: true,
1817
1817
  execute: async (ctx, input) => {
1818
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1819
- return handlePaginationMode(mode, (off, lim) => searchBankRecords(ctx.client, input.accountResourceId, {
1818
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1819
+ return handlePagination((off, lim) => searchBankRecords(ctx.client, input.accountResourceId, {
1820
1820
  filter: buildBankRecordFilter(input),
1821
1821
  limit: lim, offset: off,
1822
1822
  sort: { sortBy: [sortBy ?? 'valueDate'], order: (sortOrder ?? 'DESC') },
1823
- }), limit, offset, 100);
1823
+ }), limit, offset, 20);
1824
1824
  },
1825
1825
  },
1826
1826
  // ── Cashflow Transactions ──────────────────────────────────────
@@ -1828,25 +1828,24 @@ export const TOOL_DEFINITIONS = [
1828
1828
  name: 'search_cashflow_transactions',
1829
1829
  description: 'Search cashflow transactions (unified ledger: invoices, bills, credit notes, journals, payments). Useful for reconciliation.',
1830
1830
  params: {
1831
- ...SEARCH_PARAMS,
1832
1831
  businessTransactionType: { type: 'string', description: 'Filter by type (e.g., INVOICE, BILL, JOURNAL)' },
1833
1832
  direction: { type: 'string', enum: ['IN', 'OUT'], description: 'Filter by direction' },
1833
+ startDate: { type: 'string', description: 'Filter from date (YYYY-MM-DD)' },
1834
+ endDate: { type: 'string', description: 'Filter to date (YYYY-MM-DD)' },
1835
+ status: { type: 'string', description: 'Filter by transaction status' },
1836
+ reference: { type: 'string', description: 'Filter by reference (contains)' },
1837
+ ...SEARCH_PARAMS,
1834
1838
  },
1835
1839
  required: [],
1836
1840
  group: 'cashflow',
1837
1841
  readOnly: true,
1838
1842
  execute: async (ctx, input) => {
1839
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1840
- const filter = {};
1841
- if (input.businessTransactionType)
1842
- filter.businessTransactionType = { eq: input.businessTransactionType };
1843
- if (input.direction)
1844
- filter.direction = { eq: input.direction };
1845
- return handlePaginationMode(mode, (off, lim) => searchCashflowTransactions(ctx.client, {
1846
- filter: Object.keys(filter).length > 0 ? filter : undefined,
1843
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1844
+ return handlePagination((off, lim) => searchCashflowTransactions(ctx.client, {
1845
+ filter: buildCashflowFilter(input),
1847
1846
  limit: lim, offset: off,
1848
1847
  sort: { sortBy: [sortBy ?? 'valueDate'], order: (sortOrder ?? 'DESC') },
1849
- }), limit, offset, 100);
1848
+ }), limit, offset, 20);
1850
1849
  },
1851
1850
  },
1852
1851
  // ── Bookmarks ──────────────────────────────────────────────────
@@ -1916,13 +1915,13 @@ export const TOOL_DEFINITIONS = [
1916
1915
  group: 'org_users',
1917
1916
  readOnly: true,
1918
1917
  execute: async (ctx, input) => {
1919
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1918
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
1920
1919
  const query = input.query;
1921
- return handlePaginationMode(mode, (off, lim) => searchOrgUsers(ctx.client, {
1920
+ return handlePagination((off, lim) => searchOrgUsers(ctx.client, {
1922
1921
  filter: { or: { firstName: { contains: query }, lastName: { contains: query }, email: { contains: query } } },
1923
1922
  limit: lim, offset: off,
1924
1923
  sort: { sortBy: [sortBy ?? 'firstName'], order: (sortOrder ?? 'ASC') },
1925
- }), limit, offset, 100);
1924
+ }), limit, offset, 20);
1926
1925
  },
1927
1926
  },
1928
1927
  {
@@ -2017,7 +2016,7 @@ export const TOOL_DEFINITIONS = [
2017
2016
  group: 'payments',
2018
2017
  readOnly: true,
2019
2018
  execute: async (ctx, input) => {
2020
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
2019
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
2021
2020
  const filter = {};
2022
2021
  if (input.businessTransactionType)
2023
2022
  filter.businessTransactionType = { eq: input.businessTransactionType };
@@ -2035,11 +2034,11 @@ export const TOOL_DEFINITIONS = [
2035
2034
  df.lte = input.toDate;
2036
2035
  filter.valueDate = df;
2037
2036
  }
2038
- return handlePaginationMode(mode, (off, lim) => searchPayments(ctx.client, {
2037
+ return handlePagination((off, lim) => searchPayments(ctx.client, {
2039
2038
  filter: Object.keys(filter).length > 0 ? filter : undefined,
2040
2039
  limit: lim, offset: off,
2041
2040
  sort: { sortBy: [sortBy ?? 'valueDate'], order: (sortOrder ?? 'DESC') },
2042
- }), limit, offset, 100);
2041
+ }), limit, offset, 20);
2043
2042
  },
2044
2043
  },
2045
2044
  // ── Data Exports ───────────────────────────────────────────────
@@ -2482,17 +2481,17 @@ Auto-resolves accounts from chart of accounts. Provide bankAccountName for recip
2482
2481
  group: 'magic',
2483
2482
  readOnly: true,
2484
2483
  execute: async (ctx, input) => {
2485
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
2484
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
2486
2485
  const filter = {};
2487
2486
  if (input.status)
2488
2487
  filter.status = input.status;
2489
2488
  if (input.documentType)
2490
2489
  filter.documentType = input.documentType;
2491
- return handlePaginationMode(mode, (off, lim) => searchMagicWorkflows(ctx.client, {
2490
+ return handlePagination((off, lim) => searchMagicWorkflows(ctx.client, {
2492
2491
  filter: Object.keys(filter).length > 0 ? filter : undefined,
2493
2492
  limit: lim, offset: off,
2494
2493
  sort: { sortBy: [sortBy ?? 'createdAt'], order: (sortOrder ?? 'DESC') },
2495
- }), limit, offset, 100);
2494
+ }), limit, offset, 20);
2496
2495
  },
2497
2496
  },
2498
2497
  {
@@ -2878,13 +2877,13 @@ Auto-resolves accounts from chart of accounts. Provide bankAccountName for recip
2878
2877
  group: 'bank_rules',
2879
2878
  readOnly: true,
2880
2879
  execute: async (ctx, input) => {
2881
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
2880
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
2882
2881
  const query = input.query;
2883
- return handlePaginationMode(mode, (off, lim) => searchBankRules(ctx.client, {
2882
+ return handlePagination((off, lim) => searchBankRules(ctx.client, {
2884
2883
  filter: { name: { contains: query } },
2885
2884
  limit: lim, offset: off,
2886
- sort: sortBy ? { sortBy: [sortBy], order: (sortOrder ?? 'ASC') } : undefined,
2887
- }), limit, offset, 100);
2885
+ sort: { sortBy: [sortBy ?? 'name'], order: (sortOrder ?? 'ASC') },
2886
+ }), limit, offset, 20);
2888
2887
  },
2889
2888
  },
2890
2889
  {
@@ -2981,13 +2980,14 @@ Auto-resolves accounts from chart of accounts. Provide bankAccountName for recip
2981
2980
  query: { type: 'string', description: 'Search term (name or reference)' },
2982
2981
  status: { type: 'string', enum: ['ONGOING', 'COMPLETED', 'DISPOSED', 'DRAFT'], description: 'Filter by status' },
2983
2982
  category: { type: 'string', enum: ['TANGIBLE', 'INTANGIBLE'], description: 'Filter by category' },
2983
+ tag: { type: 'string', description: 'Filter by tag name' },
2984
2984
  ...SEARCH_PARAMS,
2985
2985
  },
2986
2986
  required: [],
2987
2987
  group: 'fixed_assets',
2988
2988
  readOnly: true,
2989
2989
  execute: async (ctx, input) => {
2990
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
2990
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
2991
2991
  const filter = {};
2992
2992
  if (input.query)
2993
2993
  filter.name = { contains: input.query };
@@ -2995,11 +2995,13 @@ Auto-resolves accounts from chart of accounts. Provide bankAccountName for recip
2995
2995
  filter.status = { eq: input.status };
2996
2996
  if (input.category)
2997
2997
  filter.category = { eq: input.category };
2998
- return handlePaginationMode(mode, (off, lim) => searchFixedAssets(ctx.client, {
2998
+ if (input.tag)
2999
+ filter.tags = { name: { eq: input.tag } };
3000
+ return handlePagination((off, lim) => searchFixedAssets(ctx.client, {
2999
3001
  filter: Object.keys(filter).length > 0 ? filter : undefined,
3000
3002
  limit: lim, offset: off,
3001
- sort: sortBy ? { sortBy: [sortBy], order: (sortOrder ?? 'DESC') } : undefined,
3002
- }), limit, offset, 100);
3003
+ sort: { sortBy: [sortBy ?? 'name'], order: (sortOrder ?? 'DESC') },
3004
+ }), limit, offset, 20);
3003
3005
  },
3004
3006
  },
3005
3007
  {
@@ -3172,17 +3174,17 @@ Auto-resolves accounts from chart of accounts. Provide bankAccountName for recip
3172
3174
  group: 'subscriptions',
3173
3175
  readOnly: true,
3174
3176
  execute: async (ctx, input) => {
3175
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
3177
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
3176
3178
  const filter = {};
3177
3179
  if (input.businessTransactionType)
3178
3180
  filter.businessTransactionType = { eq: input.businessTransactionType };
3179
3181
  if (input.status)
3180
3182
  filter.status = { eq: input.status };
3181
- return handlePaginationMode(mode, (off, lim) => searchScheduledTransactions(ctx.client, {
3183
+ return handlePagination((off, lim) => searchScheduledTransactions(ctx.client, {
3182
3184
  filter: Object.keys(filter).length > 0 ? filter : undefined,
3183
3185
  limit: lim, offset: off,
3184
- sort: sortBy ? { sortBy: [sortBy], order: (sortOrder ?? 'DESC') } : undefined,
3185
- }), limit, offset, 100);
3186
+ sort: { sortBy: [sortBy ?? 'valueDate'], order: (sortOrder ?? 'DESC') },
3187
+ }), limit, offset, 20);
3186
3188
  },
3187
3189
  },
3188
3190
  // ══════════════════════════════════════════════════════════════
@@ -3318,13 +3320,13 @@ Auto-resolves accounts from chart of accounts. Provide bankAccountName for recip
3318
3320
  group: 'contact_groups',
3319
3321
  readOnly: true,
3320
3322
  execute: async (ctx, input) => {
3321
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
3323
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
3322
3324
  const query = input.query;
3323
- return handlePaginationMode(mode, (off, lim) => searchContactGroups(ctx.client, {
3325
+ return handlePagination((off, lim) => searchContactGroups(ctx.client, {
3324
3326
  filter: { name: { contains: query } },
3325
3327
  limit: lim, offset: off,
3326
- sort: sortBy ? { sortBy: [sortBy], order: (sortOrder ?? 'ASC') } : undefined,
3327
- }), limit, offset, 100);
3328
+ sort: { sortBy: [sortBy ?? 'name'], order: (sortOrder ?? 'ASC') },
3329
+ }), limit, offset, 20);
3328
3330
  },
3329
3331
  },
3330
3332
  {
@@ -3369,17 +3371,19 @@ Auto-resolves accounts from chart of accounts. Provide bankAccountName for recip
3369
3371
  group: 'custom_fields',
3370
3372
  readOnly: true,
3371
3373
  execute: async (ctx, input) => {
3374
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
3372
3375
  const filter = {};
3373
3376
  if (input.customFieldName)
3374
3377
  filter.customFieldName = { contains: input.customFieldName };
3375
3378
  if (input.datatypeCode)
3376
3379
  filter.datatypeCode = { eq: input.datatypeCode };
3377
3380
  const searchFilter = Object.keys(filter).length > 0 ? filter : undefined;
3378
- return searchCustomFields(ctx.client, {
3381
+ return handlePagination((off, lim) => searchCustomFields(ctx.client, {
3379
3382
  filter: searchFilter,
3380
- limit: Number(input.limit) || 20,
3381
- offset: Number(input.offset) || 0,
3382
- });
3383
+ limit: lim,
3384
+ offset: off,
3385
+ sort: { sortBy: [sortBy ?? 'customFieldName'], order: (sortOrder ?? 'ASC') },
3386
+ }), limit, offset, 20);
3383
3387
  },
3384
3388
  },
3385
3389
  {
@@ -3443,18 +3447,19 @@ Auto-resolves accounts from chart of accounts. Provide bankAccountName for recip
3443
3447
  params: {
3444
3448
  query: { type: 'string', description: 'Search term (profile name or tax type code)' },
3445
3449
  ...SEARCH_PARAMS,
3450
+ sortBy: { type: 'string', enum: ['name', 'taxTypeCode', 'status', 'vatValue'], description: 'Sort field for tax profiles' },
3446
3451
  },
3447
3452
  required: ['query'],
3448
3453
  group: 'tax_profiles',
3449
3454
  readOnly: true,
3450
3455
  execute: async (ctx, input) => {
3451
- const { mode, limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
3456
+ const { limit, offset, sortBy, sortOrder } = extractPaginationInput(input);
3452
3457
  const query = input.query;
3453
- return handlePaginationMode(mode, (off, lim) => searchTaxProfiles(ctx.client, {
3458
+ return handlePagination((off, lim) => searchTaxProfiles(ctx.client, {
3454
3459
  filter: { or: { name: { contains: query }, taxTypeCode: { contains: query } } },
3455
3460
  limit: lim, offset: off,
3456
- sort: sortBy ? { sortBy: [sortBy], order: (sortOrder ?? 'ASC') } : undefined,
3457
- }), limit, offset, 100);
3461
+ sort: { sortBy: [sortBy ?? 'name'], order: (sortOrder ?? 'ASC') },
3462
+ }), limit, offset, 20);
3458
3463
  },
3459
3464
  },
3460
3465
  getTool('get_tax_profile', 'Get full tax profile details including tax rate, type code, and status.', 'tax_profiles', (client, id) => getTaxProfile(client, id)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jaz-clio",
3
- "version": "4.28.0",
3
+ "version": "4.29.0",
4
4
  "description": "Clio — Command Line Interface Orchestrator for Jaz AI.",
5
5
  "type": "module",
6
6
  "bin": {