jaz-clio 4.24.0 → 4.25.1
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/assets/skills/api/SKILL.md +2 -2
- package/assets/skills/conversion/SKILL.md +1 -1
- package/assets/skills/jobs/SKILL.md +1 -1
- package/assets/skills/transaction-recipes/SKILL.md +1 -1
- package/dist/commands/bills.js +4 -0
- package/dist/commands/help-examples.js +127 -0
- package/dist/commands/invoices.js +4 -0
- package/dist/commands/items.js +7 -1
- package/dist/commands/schema.js +240 -0
- package/dist/core/registry/tools.js +6 -0
- package/dist/index.js +4 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: jaz-api
|
|
3
|
-
version: 4.
|
|
3
|
+
version: 4.25.1
|
|
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.
|
|
@@ -85,7 +85,7 @@ You are working with the **Jaz REST API** — the accounting platform backend. A
|
|
|
85
85
|
|
|
86
86
|
### Custom Fields
|
|
87
87
|
35. **Do NOT send `appliesTo` on custom field POST** — causes "Invalid request body". Only send `name`, `type`, `printOnDocuments`.
|
|
88
|
-
35a. **Custom field values on transactions**: Set via `customFields: [{ customFieldName: "PO Number", actualValue: "PO-123" }]` on invoice/bill/customer-CN/supplier-CN create/update. NOT on journals, cash entries, or cash transfers. Read from GET responses in the same shape.
|
|
88
|
+
35a. **Custom field values on transactions**: Set via `customFields: [{ customFieldName: "PO Number", actualValue: "PO-123" }]` on invoice/bill/customer-CN/supplier-CN/payment/item create/update. NOT on journals, cash entries, or cash transfers. Read from GET responses in the same shape.
|
|
89
89
|
35b. **Custom field search**: `POST /custom-fields/search` with filter/sort/limit/offset. Filter by `customFieldName` (StringExpression), `datatypeCode` (StringExpression: TEXT, DATE, DROPDOWN).
|
|
90
90
|
35c. **Custom field GET**: `GET /custom-fields/:resourceId` returns full definition including `applyToSales`, `applyToPurchase`, `applyToCreditNote`, `applyToPayment`, `printOnDocuments`, `listOptions`.
|
|
91
91
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: jaz-conversion
|
|
3
|
-
version: 4.
|
|
3
|
+
version: 4.25.1
|
|
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.
|
|
3
|
+
version: 4.25.1
|
|
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.
|
|
3
|
+
version: 4.25.1
|
|
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.
|
package/dist/commands/bills.js
CHANGED
|
@@ -254,17 +254,20 @@ export function registerBillsCommand(program) {
|
|
|
254
254
|
.option('--method <method>', 'Payment method (BANK_TRANSFER, CASH, CHEQUE, etc.)', 'BANK_TRANSFER')
|
|
255
255
|
.option('--ref <reference>', 'Payment reference')
|
|
256
256
|
.option('--draft', 'Save as draft instead of finalizing')
|
|
257
|
+
.option('--custom-fields <json>', 'Custom field values as JSON array')
|
|
257
258
|
.option('--input <file>', 'Read full payment body from JSON file (or pipe via stdin)')
|
|
258
259
|
.option('--api-key <key>', 'API key (overrides stored/env)')
|
|
259
260
|
.option('--json', 'Output as JSON')
|
|
260
261
|
.action((resourceId, opts) => apiAction(async (client) => {
|
|
261
262
|
const body = readBodyInput(opts);
|
|
263
|
+
const customFields = opts.customFields ? parseCustomFields(opts.customFields) : undefined;
|
|
262
264
|
let res;
|
|
263
265
|
if (body) {
|
|
264
266
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- user-provided JSON, API validates
|
|
265
267
|
res = await createBillPayment(client, resourceId, {
|
|
266
268
|
...body,
|
|
267
269
|
saveAsDraft: body.saveAsDraft ?? (opts.draft ?? false),
|
|
270
|
+
customFields: body.customFields ?? customFields,
|
|
268
271
|
});
|
|
269
272
|
}
|
|
270
273
|
else {
|
|
@@ -287,6 +290,7 @@ export function registerBillsCommand(program) {
|
|
|
287
290
|
paymentMethod: opts.method,
|
|
288
291
|
reference: opts.ref ?? '',
|
|
289
292
|
saveAsDraft: opts.draft ?? false,
|
|
293
|
+
customFields,
|
|
290
294
|
});
|
|
291
295
|
}
|
|
292
296
|
if (opts.json) {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized help examples for CLI commands.
|
|
3
|
+
* All examples in ONE file — zero changes to existing command files.
|
|
4
|
+
* After all register*Command() calls, one call to applyAllExamples() walks the
|
|
5
|
+
* command tree and attaches examples via Commander.js .addHelpText().
|
|
6
|
+
*/
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
const EXAMPLES = {
|
|
9
|
+
'invoices list': [
|
|
10
|
+
{ description: 'List first 100 invoices', command: 'clio invoices list' },
|
|
11
|
+
{ description: 'Export all invoices as CSV', command: 'clio invoices list --all --format csv' },
|
|
12
|
+
{ description: 'Get JSON for piping to other tools', command: 'clio invoices list --limit 50 --json' },
|
|
13
|
+
],
|
|
14
|
+
'invoices search': [
|
|
15
|
+
{ description: 'Search by reference', command: 'clio invoices search --reference INV-001' },
|
|
16
|
+
{ description: 'Find overdue invoices', command: 'clio invoices search --status APPROVED --dueDateTo 2025-12-31' },
|
|
17
|
+
{ description: 'Search by contact and export', command: 'clio invoices search --contactName "Acme" --format json' },
|
|
18
|
+
],
|
|
19
|
+
'invoices create': [
|
|
20
|
+
{ description: 'Create a simple invoice', command: 'clio invoices create --contact "Acme Corp" --date 2025-06-01 --due 2025-07-01 --line "Consulting,10,150"' },
|
|
21
|
+
{ description: 'Create with tax profile', command: 'clio invoices create --contact "Acme Corp" --date 2025-06-01 --due 2025-07-01 --line "Service,1,1000,,,tp-sr" --status APPROVED' },
|
|
22
|
+
{ description: 'Create from JSON file', command: 'clio invoices create --body invoice.json' },
|
|
23
|
+
],
|
|
24
|
+
'invoices pay': [
|
|
25
|
+
{ description: 'Record a full payment', command: 'clio invoices pay <resourceId> --amount 1000 --date 2025-06-15 --account "Cash"' },
|
|
26
|
+
],
|
|
27
|
+
'bills list': [
|
|
28
|
+
{ description: 'List all bills', command: 'clio bills list' },
|
|
29
|
+
{ description: 'Export as YAML', command: 'clio bills list --format yaml' },
|
|
30
|
+
{ description: 'Fetch every page as JSON', command: 'clio bills list --all --json' },
|
|
31
|
+
],
|
|
32
|
+
'bills search': [
|
|
33
|
+
{ description: 'Search by supplier name', command: 'clio bills search --contactName "Supplier Co"' },
|
|
34
|
+
{ description: 'Find draft bills', command: 'clio bills search --status DRAFT' },
|
|
35
|
+
],
|
|
36
|
+
'bills create': [
|
|
37
|
+
{ description: 'Create a bill', command: 'clio bills create --contact "Supplier Co" --date 2025-06-01 --due 2025-07-01 --line "Office supplies,5,20"' },
|
|
38
|
+
{ description: 'Create from JSON body', command: 'clio bills create --body bill.json' },
|
|
39
|
+
],
|
|
40
|
+
'contacts list': [
|
|
41
|
+
{ description: 'List contacts', command: 'clio contacts list' },
|
|
42
|
+
{ description: 'Export all as CSV', command: 'clio contacts list --all --format csv' },
|
|
43
|
+
],
|
|
44
|
+
'contacts search': [
|
|
45
|
+
{ description: 'Find customers', command: 'clio contacts search --customer true' },
|
|
46
|
+
{ description: 'Search by name', command: 'clio contacts search --name "Acme"' },
|
|
47
|
+
],
|
|
48
|
+
'contacts create': [
|
|
49
|
+
{ description: 'Create a customer', command: 'clio contacts create --name "New Client" --customer' },
|
|
50
|
+
{ description: 'Create a supplier', command: 'clio contacts create --name "Vendor Inc" --supplier' },
|
|
51
|
+
],
|
|
52
|
+
'journals create': [
|
|
53
|
+
{ description: 'Create a journal entry', command: 'clio journals create --date 2025-06-01 --ref "ADJ-001" --debit "Office Expenses,500" --credit "Cash,500"' },
|
|
54
|
+
{ description: 'Create with description', command: 'clio journals create --date 2025-06-01 --ref "ADJ-002" --debit "Rent,2000" --credit "Bank,2000" --description "Monthly rent"' },
|
|
55
|
+
],
|
|
56
|
+
'reports generate': [
|
|
57
|
+
{ description: 'Generate trial balance', command: 'clio reports generate trial-balance --date 2025-06-30' },
|
|
58
|
+
{ description: 'Profit & Loss for a period', command: 'clio reports generate profit-and-loss --from 2025-01-01 --to 2025-06-30' },
|
|
59
|
+
{ description: 'Balance sheet as JSON', command: 'clio reports generate balance-sheet --date 2025-06-30 --json' },
|
|
60
|
+
{ description: 'Export AR summary as CSV', command: 'clio reports generate ar-summary --date 2025-06-30 --format csv' },
|
|
61
|
+
],
|
|
62
|
+
'accounts list': [
|
|
63
|
+
{ description: 'List chart of accounts', command: 'clio accounts list' },
|
|
64
|
+
{ description: 'Export as JSON', command: 'clio accounts list --json' },
|
|
65
|
+
],
|
|
66
|
+
'items search': [
|
|
67
|
+
{ description: 'Search items by name', command: 'clio items search --name "Widget"' },
|
|
68
|
+
{ description: 'Find sale items', command: 'clio items search --saleItem true' },
|
|
69
|
+
],
|
|
70
|
+
'bank import': [
|
|
71
|
+
{ description: 'Import a bank statement', command: 'clio bank import --file statement.csv --account "DBS Current"' },
|
|
72
|
+
],
|
|
73
|
+
search: [
|
|
74
|
+
{ description: 'Search across all entities', command: 'clio search "Acme Corp"' },
|
|
75
|
+
{ description: 'Search with limit', command: 'clio search "INV-001" --limit 5' },
|
|
76
|
+
],
|
|
77
|
+
'capsules create': [
|
|
78
|
+
{ description: 'Create a capsule', command: 'clio capsules create --name "Q2 Prepaid" --type PREPAID_EXPENSES --class ASSET' },
|
|
79
|
+
],
|
|
80
|
+
'customer-credit-notes create': [
|
|
81
|
+
{ description: 'Create a credit note', command: 'clio customer-credit-notes create --contact "Acme" --date 2025-06-01 --line "Refund,1,500"' },
|
|
82
|
+
],
|
|
83
|
+
'magic create': [
|
|
84
|
+
{ description: 'Extract data from a document', command: 'clio magic create --file invoice.pdf' },
|
|
85
|
+
{ description: 'Check extraction status', command: 'clio magic status <jobId>' },
|
|
86
|
+
],
|
|
87
|
+
schema: [
|
|
88
|
+
{ description: 'List all API groups', command: 'clio schema' },
|
|
89
|
+
{ description: 'Show tools in a group', command: 'clio schema invoices' },
|
|
90
|
+
{ description: 'Inspect a specific tool\'s params', command: 'clio schema invoices create' },
|
|
91
|
+
{ description: 'Get full schema as JSON', command: 'clio schema invoices create --json' },
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
/** Format examples block for Commander help text. */
|
|
95
|
+
function formatExamples(examples) {
|
|
96
|
+
const lines = ['\nEXAMPLES'];
|
|
97
|
+
for (const ex of examples) {
|
|
98
|
+
if (ex.description)
|
|
99
|
+
lines.push(` ${chalk.dim(ex.description)}`);
|
|
100
|
+
lines.push(` $ ${ex.command}`);
|
|
101
|
+
lines.push('');
|
|
102
|
+
}
|
|
103
|
+
return lines.join('\n');
|
|
104
|
+
}
|
|
105
|
+
/** Recursively find a subcommand by dot-separated path (e.g., "invoices.list"). */
|
|
106
|
+
function findCommand(root, path) {
|
|
107
|
+
const parts = path.split(' ');
|
|
108
|
+
let cmd = root;
|
|
109
|
+
for (const part of parts) {
|
|
110
|
+
cmd = cmd?.commands.find((c) => c.name() === part);
|
|
111
|
+
if (!cmd)
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
return cmd;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Walk the Commander tree and attach examples to matching commands.
|
|
118
|
+
* Call once after all register*Command() calls.
|
|
119
|
+
*/
|
|
120
|
+
export function applyAllExamples(program) {
|
|
121
|
+
for (const [path, examples] of Object.entries(EXAMPLES)) {
|
|
122
|
+
const cmd = findCommand(program, path);
|
|
123
|
+
if (cmd) {
|
|
124
|
+
cmd.addHelpText('after', formatExamples(examples));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -256,17 +256,20 @@ export function registerInvoicesCommand(program) {
|
|
|
256
256
|
.option('--method <method>', 'Payment method (BANK_TRANSFER, CASH, CHEQUE, etc.)', 'BANK_TRANSFER')
|
|
257
257
|
.option('--ref <reference>', 'Payment reference')
|
|
258
258
|
.option('--draft', 'Save as draft instead of finalizing')
|
|
259
|
+
.option('--custom-fields <json>', 'Custom field values as JSON array')
|
|
259
260
|
.option('--input <file>', 'Read full payment body from JSON file (or pipe via stdin)')
|
|
260
261
|
.option('--api-key <key>', 'API key (overrides stored/env)')
|
|
261
262
|
.option('--json', 'Output as JSON')
|
|
262
263
|
.action((resourceId, opts) => apiAction(async (client) => {
|
|
263
264
|
const body = readBodyInput(opts);
|
|
265
|
+
const customFields = opts.customFields ? parseCustomFields(opts.customFields) : undefined;
|
|
264
266
|
let res;
|
|
265
267
|
if (body) {
|
|
266
268
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- user-provided JSON, API validates
|
|
267
269
|
res = await createInvoicePayment(client, resourceId, {
|
|
268
270
|
...body,
|
|
269
271
|
saveAsDraft: body.saveAsDraft ?? (opts.draft ?? false),
|
|
272
|
+
customFields: body.customFields ?? customFields,
|
|
270
273
|
});
|
|
271
274
|
}
|
|
272
275
|
else {
|
|
@@ -289,6 +292,7 @@ export function registerInvoicesCommand(program) {
|
|
|
289
292
|
paymentMethod: opts.method,
|
|
290
293
|
reference: opts.ref ?? '',
|
|
291
294
|
saveAsDraft: opts.draft ?? false,
|
|
295
|
+
customFields,
|
|
292
296
|
});
|
|
293
297
|
}
|
|
294
298
|
if (opts.json) {
|
package/dist/commands/items.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { listItems, getItem, searchItems, createItem, updateItem, deleteItem, } from '../core/api/items.js';
|
|
3
3
|
import { apiAction } from './api-action.js';
|
|
4
4
|
import { outputList } from './output.js';
|
|
5
|
-
import { parsePositiveInt, parseNonNegativeInt, parseMoney, readBodyInput, requireFields } from './parsers.js';
|
|
5
|
+
import { parsePositiveInt, parseNonNegativeInt, parseMoney, readBodyInput, requireFields, parseCustomFields } from './parsers.js';
|
|
6
6
|
import { paginatedFetch } from './pagination.js';
|
|
7
7
|
import { formatId } from './format-helpers.js';
|
|
8
8
|
const ITEMS_COLUMNS = [
|
|
@@ -101,6 +101,7 @@ export function registerItemsCommand(program) {
|
|
|
101
101
|
.option('--purchase-account <resourceId>', 'Purchase account resourceId')
|
|
102
102
|
.option('--sale-tax <resourceId>', 'Sale tax profile resourceId')
|
|
103
103
|
.option('--purchase-tax <resourceId>', 'Purchase tax profile resourceId')
|
|
104
|
+
.option('--custom-fields <json>', 'Custom field values as JSON array: [{"customFieldName":"PO Number","actualValue":"PO-123"}]')
|
|
104
105
|
.option('--input <file>', 'Read full request body from JSON file (or pipe via stdin)')
|
|
105
106
|
.option('--api-key <key>', 'API key (overrides stored/env)')
|
|
106
107
|
.option('--format <type>', 'Output format: table, json, csv, yaml')
|
|
@@ -117,6 +118,7 @@ export function registerItemsCommand(program) {
|
|
|
117
118
|
{ flag: '--name', key: 'name' },
|
|
118
119
|
{ flag: '--code', key: 'code' },
|
|
119
120
|
]);
|
|
121
|
+
const customFields = opts.customFields ? parseCustomFields(opts.customFields) : undefined;
|
|
120
122
|
res = await createItem(client, {
|
|
121
123
|
internalName: opts.name,
|
|
122
124
|
itemCode: opts.code,
|
|
@@ -128,6 +130,7 @@ export function registerItemsCommand(program) {
|
|
|
128
130
|
purchaseAccountResourceId: opts.purchaseAccount,
|
|
129
131
|
saleTaxProfileResourceId: opts.saleTax,
|
|
130
132
|
purchaseTaxProfileResourceId: opts.purchaseTax,
|
|
133
|
+
customFields,
|
|
131
134
|
});
|
|
132
135
|
}
|
|
133
136
|
if (opts.json) {
|
|
@@ -147,6 +150,7 @@ export function registerItemsCommand(program) {
|
|
|
147
150
|
.option('--sale-price <n>', 'New sale price', parseMoney)
|
|
148
151
|
.option('--purchase-price <n>', 'New purchase price', parseMoney)
|
|
149
152
|
.option('--status <status>', 'Status (ACTIVE/INACTIVE)')
|
|
153
|
+
.option('--custom-fields <json>', 'Custom field values as JSON array')
|
|
150
154
|
.option('--input <file>', 'Read full update body from JSON file (or pipe via stdin)')
|
|
151
155
|
.option('--api-key <key>', 'API key (overrides stored/env)')
|
|
152
156
|
.option('--format <type>', 'Output format: table, json, csv, yaml')
|
|
@@ -169,6 +173,8 @@ export function registerItemsCommand(program) {
|
|
|
169
173
|
data.purchasePrice = opts.purchasePrice;
|
|
170
174
|
if (opts.status !== undefined)
|
|
171
175
|
data.status = opts.status;
|
|
176
|
+
if (opts.customFields)
|
|
177
|
+
data.customFields = parseCustomFields(opts.customFields);
|
|
172
178
|
}
|
|
173
179
|
const res = await updateItem(client, resourceId, data);
|
|
174
180
|
if (opts.json) {
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `clio schema` — offline API schema introspection.
|
|
3
|
+
* No auth required — reads from compiled tool registry.
|
|
4
|
+
*
|
|
5
|
+
* Three levels:
|
|
6
|
+
* clio schema → list all groups with tool counts
|
|
7
|
+
* clio schema <group> → show tools in a group
|
|
8
|
+
* clio schema <group> <action> → show detailed params for a tool
|
|
9
|
+
*/
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { getAllTools, getToolsByGroup } from '../core/registry/lookup.js';
|
|
12
|
+
import { formatTable } from './table-formatter.js';
|
|
13
|
+
import { resolveFormat, outputRecord } from './output.js';
|
|
14
|
+
import { formatId } from './format-helpers.js';
|
|
15
|
+
// ── Group → CLI command mapping ─────────────────────────────────
|
|
16
|
+
const GROUP_CLI_MAP = {
|
|
17
|
+
cash_entries: 'cash-in / cash-out',
|
|
18
|
+
cash_transfers: 'cash-transfer',
|
|
19
|
+
customer_credit_notes: 'customer-credit-notes',
|
|
20
|
+
supplier_credit_notes: 'supplier-credit-notes',
|
|
21
|
+
bank_rules: 'bank-rules',
|
|
22
|
+
org_users: 'org-users',
|
|
23
|
+
tax_profiles: 'tax-profiles',
|
|
24
|
+
contact_groups: 'contact-groups',
|
|
25
|
+
custom_fields: 'custom-fields',
|
|
26
|
+
fixed_assets: 'fixed-assets',
|
|
27
|
+
};
|
|
28
|
+
function groupToCliCommand(group) {
|
|
29
|
+
return GROUP_CLI_MAP[group] ?? group.replace(/_/g, '-');
|
|
30
|
+
}
|
|
31
|
+
// ── Singularize helper ──────────────────────────────────────────
|
|
32
|
+
function singularize(word) {
|
|
33
|
+
if (word.endsWith('ies'))
|
|
34
|
+
return word.slice(0, -3) + 'y';
|
|
35
|
+
if (word.endsWith('ses'))
|
|
36
|
+
return word.slice(0, -2);
|
|
37
|
+
if (word.endsWith('s') && !word.endsWith('ss'))
|
|
38
|
+
return word.slice(0, -1);
|
|
39
|
+
return word;
|
|
40
|
+
}
|
|
41
|
+
// ── Tool name resolution ────────────────────────────────────────
|
|
42
|
+
function resolveToolName(group, action, tools) {
|
|
43
|
+
// Try exact: action_group (e.g., list_invoices)
|
|
44
|
+
const exactPlural = `${action}_${group}`;
|
|
45
|
+
const found1 = tools.find((t) => t.name === exactPlural);
|
|
46
|
+
if (found1)
|
|
47
|
+
return found1;
|
|
48
|
+
// Try singular: action_singular (e.g., create_invoice)
|
|
49
|
+
const singular = singularize(group);
|
|
50
|
+
const exactSingular = `${action}_${singular}`;
|
|
51
|
+
const found2 = tools.find((t) => t.name === exactSingular);
|
|
52
|
+
if (found2)
|
|
53
|
+
return found2;
|
|
54
|
+
// Try with group prefix removed for compound groups (e.g., customer_credit_notes → credit_note)
|
|
55
|
+
// and also try action_group_singular for multi-word groups
|
|
56
|
+
const found3 = tools.find((t) => t.name === `${action}_${group.replace(/_/g, '_')}`);
|
|
57
|
+
if (found3)
|
|
58
|
+
return found3;
|
|
59
|
+
// Fuzzy: find any tool in group whose name contains the action
|
|
60
|
+
return tools.find((t) => t.name.includes(action));
|
|
61
|
+
}
|
|
62
|
+
function flattenParams(params, required, prefix = '', depth = 0) {
|
|
63
|
+
if (depth > 3)
|
|
64
|
+
return [];
|
|
65
|
+
const result = [];
|
|
66
|
+
for (const [key, def] of Object.entries(params)) {
|
|
67
|
+
const fullName = prefix ? `${prefix}.${key}` : key;
|
|
68
|
+
const isRequired = required.includes(key) && !prefix;
|
|
69
|
+
result.push({
|
|
70
|
+
name: fullName,
|
|
71
|
+
type: def.type,
|
|
72
|
+
required: isRequired,
|
|
73
|
+
values: def.enum?.join(',') ?? '-',
|
|
74
|
+
description: def.description ?? '-',
|
|
75
|
+
});
|
|
76
|
+
// Recurse into object properties
|
|
77
|
+
if (def.type === 'object' && def.properties) {
|
|
78
|
+
result.push(...flattenParams(def.properties, def.required ?? [], fullName, depth + 1));
|
|
79
|
+
}
|
|
80
|
+
// Recurse into array items
|
|
81
|
+
if (def.type === 'array' && def.items) {
|
|
82
|
+
if (def.items.type === 'object' && def.items.properties) {
|
|
83
|
+
result.push(...flattenParams(def.items.properties, def.items.required ?? [], `${fullName}[]`, depth + 1));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
// ── Output helpers ──────────────────────────────────────────────
|
|
90
|
+
function outputGroupList(opts) {
|
|
91
|
+
const all = getAllTools();
|
|
92
|
+
const groupMap = new Map();
|
|
93
|
+
for (const tool of all) {
|
|
94
|
+
const list = groupMap.get(tool.group) ?? [];
|
|
95
|
+
list.push(tool);
|
|
96
|
+
groupMap.set(tool.group, list);
|
|
97
|
+
}
|
|
98
|
+
const groups = Array.from(groupMap.entries())
|
|
99
|
+
.sort(([, a], [, b]) => b.length - a.length)
|
|
100
|
+
.map(([group, tools]) => ({
|
|
101
|
+
group,
|
|
102
|
+
tools: tools.length,
|
|
103
|
+
read: tools.filter((t) => t.readOnly).length,
|
|
104
|
+
write: tools.filter((t) => !t.readOnly).length,
|
|
105
|
+
cliCommand: `clio ${groupToCliCommand(group)}`,
|
|
106
|
+
}));
|
|
107
|
+
const fmt = resolveFormat(opts);
|
|
108
|
+
if (fmt !== 'table') {
|
|
109
|
+
outputRecord({ totalTools: all.length, totalGroups: groups.length, groups }, opts);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(chalk.bold(`Schema — ${all.length} tools across ${groups.length} groups\n`));
|
|
113
|
+
const columns = [
|
|
114
|
+
{ key: 'group', header: 'Group', format: (v) => chalk.cyan(String(v)) },
|
|
115
|
+
{ key: 'tools', header: 'Tools', align: 'right' },
|
|
116
|
+
{ key: 'read', header: 'Read', align: 'right' },
|
|
117
|
+
{ key: 'write', header: 'Write', align: 'right' },
|
|
118
|
+
{ key: 'cliCommand', header: 'CLI Command', format: (v) => chalk.dim(String(v)) },
|
|
119
|
+
];
|
|
120
|
+
console.log(formatTable(groups, columns));
|
|
121
|
+
}
|
|
122
|
+
function outputGroupDetail(group, tools, opts) {
|
|
123
|
+
const readCount = tools.filter((t) => t.readOnly).length;
|
|
124
|
+
const writeCount = tools.filter((t) => !t.readOnly).length;
|
|
125
|
+
const fmt = resolveFormat(opts);
|
|
126
|
+
if (fmt !== 'table') {
|
|
127
|
+
outputRecord({
|
|
128
|
+
group,
|
|
129
|
+
cliCommand: `clio ${groupToCliCommand(group)}`,
|
|
130
|
+
toolCount: tools.length,
|
|
131
|
+
read: readCount,
|
|
132
|
+
write: writeCount,
|
|
133
|
+
tools: tools.map((t) => ({
|
|
134
|
+
name: t.name,
|
|
135
|
+
type: t.readOnly ? 'read' : 'write',
|
|
136
|
+
params: Object.keys(t.params).length,
|
|
137
|
+
description: t.description.slice(0, 120),
|
|
138
|
+
})),
|
|
139
|
+
}, opts);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
console.log(chalk.bold(`${group} — ${tools.length} tools (${readCount} read, ${writeCount} write)\n`));
|
|
143
|
+
const columns = [
|
|
144
|
+
{ key: 'name', header: 'Tool', format: formatId },
|
|
145
|
+
{ key: 'type', header: 'Type' },
|
|
146
|
+
{ key: 'params', header: 'Params', align: 'right' },
|
|
147
|
+
{ key: 'description', header: 'Description' },
|
|
148
|
+
];
|
|
149
|
+
const rows = tools.map((t) => ({
|
|
150
|
+
name: t.name,
|
|
151
|
+
type: t.readOnly ? 'read' : 'write',
|
|
152
|
+
params: Object.keys(t.params).length,
|
|
153
|
+
description: t.description.length > 80 ? t.description.slice(0, 79) + '\u2026' : t.description,
|
|
154
|
+
}));
|
|
155
|
+
console.log(formatTable(rows, columns));
|
|
156
|
+
console.log(chalk.dim(`\n Use: clio schema ${groupToCliCommand(group)} <action>`));
|
|
157
|
+
}
|
|
158
|
+
function outputToolDetail(tool, opts) {
|
|
159
|
+
const flat = flattenParams(tool.params, tool.required);
|
|
160
|
+
const fmt = resolveFormat(opts);
|
|
161
|
+
if (fmt !== 'table') {
|
|
162
|
+
outputRecord({
|
|
163
|
+
tool: tool.name,
|
|
164
|
+
group: tool.group,
|
|
165
|
+
description: tool.description,
|
|
166
|
+
readOnly: tool.readOnly,
|
|
167
|
+
requiredParams: tool.required,
|
|
168
|
+
params: tool.params,
|
|
169
|
+
}, opts);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.log(chalk.bold(`${tool.name}`) + chalk.dim(` — ${tool.description}\n`));
|
|
173
|
+
if (flat.length === 0) {
|
|
174
|
+
console.log(' No parameters.');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const columns = [
|
|
178
|
+
{ key: 'name', header: 'Parameter', format: (v) => chalk.cyan(String(v)) },
|
|
179
|
+
{ key: 'type', header: 'Type' },
|
|
180
|
+
{ key: 'required', header: 'Required', format: (v) => (v === true || v === 'true') ? chalk.green('Yes') : 'No' },
|
|
181
|
+
{ key: 'values', header: 'Values' },
|
|
182
|
+
{ key: 'description', header: 'Description' },
|
|
183
|
+
];
|
|
184
|
+
console.log(formatTable(flat, columns));
|
|
185
|
+
}
|
|
186
|
+
// ── Command registration ────────────────────────────────────────
|
|
187
|
+
export function registerSchemaCommand(program) {
|
|
188
|
+
program
|
|
189
|
+
.command('schema')
|
|
190
|
+
.description('Introspect API schema — discover fields, types, and constraints')
|
|
191
|
+
.argument('[group]', 'Entity group (e.g., invoices, contacts)')
|
|
192
|
+
.argument('[action]', 'Action (e.g., create, search, list)')
|
|
193
|
+
.option('--format <type>', 'Output format: table, json, csv, yaml')
|
|
194
|
+
.option('--json', 'Output as JSON')
|
|
195
|
+
.action((group, action, opts) => {
|
|
196
|
+
// Level 1: list all groups
|
|
197
|
+
if (!group) {
|
|
198
|
+
outputGroupList(opts);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Normalize group: hyphens → underscores to match ToolGroup
|
|
202
|
+
const normalizedGroup = group.replace(/-/g, '_');
|
|
203
|
+
const tools = getToolsByGroup(normalizedGroup);
|
|
204
|
+
if (tools.length === 0) {
|
|
205
|
+
// Try as-is in case it's already correct
|
|
206
|
+
const directTools = getToolsByGroup(group);
|
|
207
|
+
if (directTools.length === 0) {
|
|
208
|
+
console.error(chalk.red(`Unknown group: ${group}`));
|
|
209
|
+
console.error(chalk.dim('Run `clio schema` to see available groups.'));
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
// Level 2: show group tools
|
|
213
|
+
if (!action) {
|
|
214
|
+
outputGroupDetail(group, directTools, opts);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const tool = resolveToolName(group, action, directTools);
|
|
218
|
+
if (!tool) {
|
|
219
|
+
console.error(chalk.red(`Unknown action: ${action} in group ${group}`));
|
|
220
|
+
console.error(chalk.dim(`Available: ${directTools.map((t) => t.name).join(', ')}`));
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
outputToolDetail(tool, opts);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Level 2: show tools in group
|
|
227
|
+
if (!action) {
|
|
228
|
+
outputGroupDetail(normalizedGroup, tools, opts);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
// Level 3: show tool detail
|
|
232
|
+
const tool = resolveToolName(normalizedGroup, action, tools);
|
|
233
|
+
if (!tool) {
|
|
234
|
+
console.error(chalk.red(`Unknown action: ${action} in group ${group}`));
|
|
235
|
+
console.error(chalk.dim(`Available: ${tools.map((t) => t.name).join(', ')}`));
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
outputToolDetail(tool, opts);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
@@ -417,6 +417,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
417
417
|
enum: ['CASH', 'BANK_TRANSFER', 'CREDIT_CARD', 'CHEQUE', 'E_WALLET', 'OTHER'],
|
|
418
418
|
description: 'Payment method (default BANK_TRANSFER)',
|
|
419
419
|
},
|
|
420
|
+
customFields: CUSTOM_FIELDS_PARAM,
|
|
420
421
|
},
|
|
421
422
|
required: ['resourceId', 'paymentAmount', 'accountResourceId', 'valueDate'],
|
|
422
423
|
group: 'invoices',
|
|
@@ -439,6 +440,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
439
440
|
reference: input.reference ?? '',
|
|
440
441
|
paymentMethod: (input.paymentMethod ?? 'BANK_TRANSFER'),
|
|
441
442
|
saveAsDraft: false,
|
|
443
|
+
customFields: input.customFields, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
442
444
|
});
|
|
443
445
|
},
|
|
444
446
|
},
|
|
@@ -603,6 +605,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
603
605
|
valueDate: { type: 'string' },
|
|
604
606
|
reference: { type: 'string' },
|
|
605
607
|
paymentMethod: { type: 'string' },
|
|
608
|
+
customFields: CUSTOM_FIELDS_PARAM,
|
|
606
609
|
},
|
|
607
610
|
required: ['resourceId', 'paymentAmount', 'accountResourceId', 'valueDate'],
|
|
608
611
|
group: 'bills',
|
|
@@ -625,6 +628,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
625
628
|
reference: input.reference ?? '',
|
|
626
629
|
paymentMethod: (input.paymentMethod ?? 'BANK_TRANSFER'),
|
|
627
630
|
saveAsDraft: false,
|
|
631
|
+
customFields: input.customFields, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
628
632
|
});
|
|
629
633
|
},
|
|
630
634
|
},
|
|
@@ -904,6 +908,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
904
908
|
purchasePrice: { type: 'number', description: 'Default purchase price' },
|
|
905
909
|
saleAccountResourceId: { type: 'string', description: 'Revenue account for sales' },
|
|
906
910
|
purchaseAccountResourceId: { type: 'string', description: 'Expense account for purchases' },
|
|
911
|
+
customFields: CUSTOM_FIELDS_PARAM,
|
|
907
912
|
},
|
|
908
913
|
required: ['itemCode', 'internalName'],
|
|
909
914
|
group: 'items',
|
|
@@ -920,6 +925,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
920
925
|
salePrice: { type: 'number' },
|
|
921
926
|
purchasePrice: { type: 'number' },
|
|
922
927
|
status: { type: 'string', enum: ['ACTIVE', 'INACTIVE'] },
|
|
928
|
+
customFields: CUSTOM_FIELDS_PARAM,
|
|
923
929
|
},
|
|
924
930
|
required: ['resourceId'],
|
|
925
931
|
group: 'items',
|
package/dist/index.js
CHANGED
|
@@ -49,6 +49,8 @@ import { registerContactGroupsCommand } from './commands/contact-groups.js';
|
|
|
49
49
|
import { registerInventoryCommand } from './commands/inventory.js';
|
|
50
50
|
import { registerSearchCommand } from './commands/search.js';
|
|
51
51
|
import { registerCustomFieldsCommand } from './commands/custom-fields.js';
|
|
52
|
+
import { registerSchemaCommand } from './commands/schema.js';
|
|
53
|
+
import { applyAllExamples } from './commands/help-examples.js';
|
|
52
54
|
import { shouldShowPicker, showCommandPicker, attachSubcommandPickers } from './commands/picker.js';
|
|
53
55
|
import { getActiveLabel, setProfile, listProfiles } from './core/auth/credentials.js';
|
|
54
56
|
import { getOrganization } from './core/api/organization.js';
|
|
@@ -159,6 +161,8 @@ registerContactGroupsCommand(program);
|
|
|
159
161
|
registerInventoryCommand(program);
|
|
160
162
|
registerSearchCommand(program);
|
|
161
163
|
registerCustomFieldsCommand(program);
|
|
164
|
+
registerSchemaCommand(program);
|
|
165
|
+
applyAllExamples(program);
|
|
162
166
|
// Add --org to every command that has --api-key (DRY: zero changes to command files)
|
|
163
167
|
function addOrgOption(cmd) {
|
|
164
168
|
for (const sub of cmd.commands) {
|