jaz-clio 9.0.2 → 9.2.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: 9.0.2
3
+ version: 9.2.0
4
4
  description: >-
5
5
  Use this skill whenever you call, debug, or review code that touches the Jaz
6
6
  REST API. Covers field names, response shapes, 148 production gotchas, error
@@ -445,9 +445,9 @@ Bills, invoices, and credit notes share identical mandatory field specs. Adding
445
445
 
446
446
  137c. **Payment terms on contacts.** `bulk_upsert_contacts` takes `customerPaymentTerms` / `supplierPaymentTerms` as `{name, value?}`: `name` is `ON_DELIVERY`, `NET_7`, `NET_15`, `NET_30`, `NET_45`, `NET_60` or `CUSTOM` (any case). `value` is the number of days as a string: required for `CUSTOM` as a positive whole number (the client refuses anything else before the call). With any other name the name carries its days, and a `value` that is not a positive whole number fails the WHOLE batch with 422, so the client drops it. A term is stored only for a side the contact is on: a customer term on a supplier-only contact is dropped with a 200. Verified live 2026-09-23: `{name: "NET_30"}` stored `paymentDetail.customerPaymentTerms [{name: "Net 30", value: "30"}]`, the same as a single create. **Bulk UPDATE trap:** a row that omits a term makes the API back-fill it from the stored display name, which is no term name, so Net 30 is re-stored as `Custom`/"30" (measured 2026-09-24). The client re-sends each stored term the row omits under its real name (one contact search per 100 ids). The single-contact create and update take a bare name and store `CUSTOM` with no days, so a term of any other length (21 days) is reachable only through bulk upsert. `clio contacts create|update --payment-terms` / `--supplier-payment-terms` accept a name in any case (not `CUSTOM`) or days 0/7/15/30/45/60; `create` makes the contact that side, `update` refuses a term for a side the contact is not on. This replaces the earlier warning that the bulk shape silently dropped the row, which the API has since fixed. **A contact's status cannot be changed through the API either.** The update (`PUT /contacts/{id}`) has no `status` field and, per the API source, always writes ACTIVE. `bulk_upsert_contacts` with `status: "INACTIVE"` returns job SUCCESS with processedCount 1 and applies nothing: an update's other fields in that row were dropped too, and a create with it left no contact findable (measured 2026-09-24). The client refuses INACTIVE on bulk rows and `clio contacts update --status`. Change status in the Jaz app.
447
447
 
448
- 138. **`get_contact_signals`: read-only contact-history pattern lookup**. `GET /api/v1/contacts/{resourceId}/signals?btType=…` returns the contact's modal patterns (currency, payment-terms, tax-inclusion/presence, top-COA, top-item), cadence (median interval days, days-since-last, interval ratio), and outstanding-balance snapshot for one (contact × business-transaction-type) pair. `btType` is required: `SALE` | `PURCHASE` | `SALE_CREDIT_NOTE` | `PURCHASE_CREDIT_NOTE`. Returns null `data` when sampleSize < 5 or the freshness layer is unavailable. Cache key is per-(contactId, btType) pair, so repeated calls for the same pair are cheap. **Three slices are always empty/null on this endpoint** (`severitySummary`, `outlierFlags`, `revealedDivergences`) because they require a draft to compare against; those populate only on the per-result `contactSignals` object inside `validate_drafts` responses. Use this tool for "what does this contact normally look like?" questions before drafting; use `validate_drafts` for "how does this draft compare to the contact's history?" questions after drafting.
448
+ 138. **`get_contact_signals`: a contact's usual values, read-only**. `GET /api/v1/contacts/{resourceId}/signals?btType=…` (`btType` required: `SALE` | `PURCHASE` | `SALE_CREDIT_NOTE` | `PURCHASE_CREDIT_NOTE`) reads the contact's posted transactions of that type (value date in the last 12 months, most recent 50) and returns: `patterns[]`, the usual currency, payment terms, tax inclusion, tax presence, account and item, each with `matchCount` / `totalCount` (a value is usual only above half, 70% for the two tax fields; account and item counts are lines); `outstanding`, the open balance across all dates and currencies in the base currency, split past due / not yet due, up to 12 hours behind recent payments, absent when nothing is open; and `cadence` (median interval, `lastDate`, `nextExpectedDate`), present only when the contact transacts regularly (10+ distinct dates, steady interval). No draft is compared, so there are no flags. `sampleSize: 0` with `severitySummary.dominantLabel: NEW` means no history of the type; `NO_ALERTS` means it could not be computed. A user without the Sales (SALE, SALE_CREDIT_NOTE) or Purchases (PURCHASE, PURCHASE_CREDIT_NOTE) module gets 403 `NOT_HAVE_ENOUGH_PERMISSION`. Use it for "what does this contact normally look like?" before drafting; use `validate_drafts` to check a draft.
449
449
 
450
- 139. **`validate_drafts` per-result enrichment (MID7)**. Every entry in `results[]` now carries two extra slices alongside the existing `eligible` / `errors[]` / `displayData[]`: (a) **`contactSignals`**: full Mid-7 insight (cadence, outliers, severity, divergences, outstanding balance) computed against the draft's contact history. Null when the draft has no contact, the draft is ineligible, or the contact has no qualifying history in the 12-month window. Same shape as `get_contact_signals` but populated WITH the always-empty-on-GET slices (severitySummary, outlierFlags, revealedDivergences); those compare the draft against the contact's modal pattern. (b) **`breakdown`**: full Balance-panel payload (`items[]` + `meta` with subtotal / tax / total / paymentRecorded / balance / exchangeRate). Use breakdown to surface the trx-level metadata an agent needs for "show me what this draft looks like" questions without a separate `get_invoice` / `get_bill` call. Top-level: `eligibleCount`, `ineligibleCount`, `columns` / `errorColumns` (table render hints), and `contactSignalsMeta.unavailable=true` when the freshness layer was offline for the whole batch (per-result `contactSignals` will all be null). Wire response uses legacy field names `contactInsight` (per-result) and `contactInsightsMeta` (top-level); motherboard's API client renames both to `contactSignals` / `contactSignalsMeta` for consistency with `get_contact_signals`.
450
+ 139. **`validate_drafts` per-result signals and breakdown**. Every `results[]` entry carries, beside `eligible` / `errors[]` / `displayData[]`: (a) **`contactSignals`**, the draft compared with the contact's recent posted transactions of the same type. `severitySummary` holds the check result: `dominantLabel` (ranked RED before YELLOW, then POSSIBLE_DUPLICATE, NEW_CURRENCY, ABOVE_USUAL, BELOW_USUAL, NEW_TERMS, TAX_TYPE, NEW_GST, NEW_COA, NEW_ITEM; nothing found = NEW under 5 compared, else NORMAL; NO_ALERTS = could not check), `tone` (RED / YELLOW / BLUE / GRAY), `high` / `medium` counts, `checkedFields` + `amountChecked` (what a clean result checked). The evidence: `duplicateFlags[]` (at most one: the twin's id, reference, status, date, total, `inBatch`; RED for the same supplier reference on bills and supplier credit notes, YELLOW for the same amount, currency and date), `outlierFlags[]` (total outside 1.5x the contact's range: `previousMin`, `previousMax`, `comparedCount`; RED in the direction that costs money), `revealedDivergences[]` (new currency, terms, tax, or a line on an account or item the contact has not used: `lineNumber`, `draftLabel`, `modalLabel`). A contact with no history gets `sampleSize: 0` and NEW, never null. `contactSignals` is null only for an ineligible draft, a draft with no contact, or when `contactSignalsMeta.unavailable` is true. (b) **`breakdown`**: `items[]` in document order with `lineNumber`, and `meta` with subtotal (net of line discounts), shipping (invoices), tax, total, paymentRecorded, balance, exchangeRate and `totalInFunctional`. Wire names are `contactInsight` (per result) and `contactInsightsMeta`; the client renames them `contactSignals` / `contactSignalsMeta`.
451
451
 
452
452
  140. **IFRS 18 accountType values (effective 2027)**. `create_account` / `update_account` / `bulk_upsert_chart_of_accounts` accept the 9 IFRS 18 classification types alongside the 14 pre-IFRS-18 types (23 in total, listed in rule 21): **Discontinued Expense**, **Discontinued Income**, **Finance Cost**, **Financing Income**, **Goodwill**, **Income Tax Expense**, **Investing Expense**, **Investing Income**, **Investment**. `normalizeAccountType` (in `core/api/guards.ts`) maps unambiguous variants client-side: "income tax" / "tax expense" → "Income Tax Expense", "finance costs" → "Finance Cost", "investments" → "Investment". **Ambiguous variants are intentionally NOT auto-mapped**: under IFRS 18, "interest expense" can land in EITHER Finance Cost (financing activities) OR Operating / Investing Expense depending on the entity's main business activity, and "interest income" can land in EITHER Financing Income OR Investing Income. The agent must pick the explicit canonical string for those cases instead of relying on a guess that could misclassify the account. Pass any value to `accountType` (POST/PUT will receive it as `classificationType` per rule 21). The classic types still work; IFRS 18 is purely additive.
453
453
 
@@ -483,6 +483,27 @@ Create exchange rates in bulk (max 500). **Auto-enables currencies not yet enabl
483
483
  { "data": { "resourceId": "uuid", ... } }
484
484
  ```
485
485
 
486
+ ### GET /api/v1/contacts/{resourceId}/signals?btType=PURCHASE
487
+
488
+ The contact's usual values for one transaction type (`SALE`, `PURCHASE`, `SALE_CREDIT_NOTE`, `PURCHASE_CREDIT_NOTE`; required), from its posted transactions of the last 12 months (most recent 50). SKILL.md rule 138.
489
+
490
+ ```json
491
+ // Response (cadence only for a regular contact; outstanding absent when nothing is open):
492
+ { "data": {
493
+ "sampleSize": 24, "signalQuality": "STABLE",
494
+ "patterns": [
495
+ {"field": "PAYMENT_TERMS", "value": "Net 30", "rawValue": "30", "matchCount": 23, "totalCount": 24, "confidence": 0.958},
496
+ {"field": "TOP_COA", "value": "Office Supplies", "rawValue": "uuid", "matchCount": 30, "totalCount": 33, "confidence": 0.909}
497
+ ],
498
+ "cadence": {"medianIntervalDays": 30, "daysSinceLast": 34, "intervalRatio": 1.13, "lastDate": "2026-09-09", "nextExpectedDate": "2026-10-09"},
499
+ "outstanding": {"totalAmount": "5400.00", "currencyCode": "SGD", "pastDueAmount": "0.00", "pastDueCount": 0,
500
+ "notYetDueAmount": "5400.00", "notYetDueCount": 2, "activeBillCount": 2}
501
+ } }
502
+ // No history of the type: sampleSize 0, patterns [], "severitySummary": {"dominantLabel": "NEW", "tone": "BLUE"}.
503
+ // Could not compute: "severitySummary": {"dominantLabel": "NO_ALERTS", "tone": "GRAY"}.
504
+ // No access to the Sales / Purchases module: 403 NOT_HAVE_ENOUGH_PERMISSION.
505
+ ```
506
+
486
507
  ---
487
508
 
488
509
  ## 6. Items
@@ -218,7 +218,7 @@ The 6 sync endpoints are NOT idempotent on the same `bankStatementEntryResourceI
218
218
 
219
219
  | Method | Path | Description |
220
220
  |--------|------|-------------|
221
- | POST | `/drafts/validate` | **Sync**. Validate up to 500 drafts for conversion eligibility in ONE call. Returns per-item errors + display data inline. No state change. Safe to call repeatedly. |
221
+ | POST | `/drafts/validate` | **Sync**. Check up to 500 drafts in ONE call: per-item errors, line items and totals, and signals (possible duplicate, total outside the contact's range, new currency / terms / tax, a line on an account or item the contact has not used). No state change. Safe to call repeatedly. |
222
222
  | POST | `/drafts/convert-to-active` | **Async**, returns `{ jobId }`. Promote up to 500 drafts to ACTIVE in ONE call. NOT idempotent on already-promoted drafts. |
223
223
  | POST | `/drafts/submit-for-approval` | **Async**, returns `{ jobId }`. Route up to 500 drafts into approval workflow in ONE call. NOT idempotent on drafts with in-flight approval. |
224
224
 
@@ -237,6 +237,7 @@ Body for all three: `{ items: [{btResourceId: "<uuid>", btType: "SALE|PURCHASE|S
237
237
  | POST | `/contacts/search` | Advanced search |
238
238
  | PUT | `/contacts/:resourceId` | Update |
239
239
  | DELETE | `/contacts/:resourceId` | Delete |
240
+ | GET | `/contacts/:resourceId/signals?btType=` | The contact's usual values for one transaction type, open balance, and billing rhythm when regular. Read-only. |
240
241
  | POST | `/contacts/bulk-upsert` | Bulk create/update contacts (max 500); **async**, returns `{ jobId }`. Poll `/background-jobs/search` with `filter.resourceId`. |
241
242
 
242
243
  ### Contact Groups
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-cli
3
- version: 9.0.2
3
+ version: 9.2.0
4
4
  description: >-
5
5
  Use this skill when running Clio CLI commands, building shell scripts with
6
6
  Clio, debugging auth issues, understanding --json output, paginating results,
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-conversion
3
- version: 9.0.2
3
+ version: 9.2.0
4
4
  description: >-
5
5
  Use this skill when migrating accounting data into Jaz, importing from Xero,
6
6
  QuickBooks, Sage, MYOB, or Excel exports. Covers the full conversion pipeline:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-kit
3
- version: 9.0.2
3
+ version: 9.2.0
4
4
  description: >-
5
5
  Use this skill when an accountant, bookkeeper, or owner is running real books
6
6
  in Jaz across one or more organizations from the terminal: setting up a
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-pseudo-sql
3
- version: 9.0.2
3
+ version: 9.2.0
4
4
  description: >-
5
5
  Use this skill when answering ad-hoc data questions that aren't covered by
6
6
  download_export (canonical reports: anomaly, audit, aging, P&L, BS, GL,
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-jobs
3
- version: 9.0.2
3
+ version: 9.2.0
4
4
  description: >-
5
5
  Use this skill for recurring accounting workflows: month/quarter/year-end
6
6
  close, bank reconciliation, GST/VAT filing, payment runs, credit control,
@@ -8,7 +8,7 @@
8
8
  - **`generate_aged_receivables(endDate: <date>)`**: step 1: AR aging report with bucket breakdown.
9
9
  - **`search_invoices(filter: {status: {eq: 'UNPAID'}, dueDate: {lt: <date>}, contactResourceId: <customer>}, sortBy: 'dueDate', sortOrder: 'ASC', limit: 200)`**: step 2: per-customer overdue detail. Paginate.
10
10
  - **`get_contact(resourceId: <customer id>)`**: step 2: pull contact info (email, phone, primary contact).
11
- - **`get_contact_signals(resourceId: <id>, btType: 'SALE')`**: step 3: pull cadence + outlier signals + outstanding balance for the customer. Mid-7 endpoint.
11
+ - **`get_contact_signals(resourceId: <id>, btType: 'SALE')`**: step 3: the customer's usual payment terms, open balance and, for a regular customer, billing rhythm.
12
12
  - **`apply_credits_to_invoice(...)`** / **`create_customer_credit_note(...)`**: step 6: write-off path A for stage-3 specific impairment.
13
13
  - **`pay_invoice(... paymentMethod: 'DEBT_WRITE_OFF' ...)`**: step 6: write-off path B (direct).
14
14
  - **`calculate(type: 'ecl', ...)`**, then `create_capsule` + `create_journal` with `capsuleResourceId`: step 7: ECL collective top-up if material change in aging.
@@ -55,34 +55,34 @@ get_contact(resourceId: <customer id>)
55
55
 
56
56
  For each customer build a chase record: `{customerName, totalOverdue, oldestDaysOverdue, invoices: [{reference, valueDate, dueDate, daysOverdue, outstanding}], primaryContact}`.
57
57
 
58
- ## Step 3: Contact-signals pull (Mid-7)
58
+ ## Step 3: Customer context
59
59
 
60
60
  ```
61
61
  get_contact_signals(resourceId: <customer id>, btType: 'SALE')
62
62
  ```
63
63
 
64
- Returns: `{ cadence, outlierFlags[], severitySummary, patternDivergenceFlags, outstandingSnapshot, revealedPatterns[] }`. High-signal data for collection prioritization:
65
- - **Cadence outliers** (this customer typically pays N days late): expected vs current overdue.
66
- - **Severity** (`LOW` / `MEDIUM` / `HIGH`): customer-specific risk index.
67
- - **Pattern divergence**: a customer who's never been overdue is now; escalate.
64
+ Returns the customer's usual values from its recent invoices and its open balance:
65
+ - **`patterns[]`** with `field: 'PAYMENT_TERMS'`: the terms this customer is usually invoiced on (e.g. Net 30, 11 of 12 invoices). An overdue invoice on unusually long terms is a negotiation point; on the usual terms it is plain lateness.
66
+ - **`outstanding`**: everything the customer owes across all invoices, dates and currencies, in the base currency: `totalAmount`, `pastDueAmount` / `pastDueCount`, `notYetDueAmount`. Use it to size the conversation (how much more is coming due), not to rank risk. It can be up to 12 hours behind recent payments, so the aging report from step 1 stays the source for what is overdue.
67
+ - **`cadence`** (only for a customer invoiced regularly): how often the customer is invoiced (median interval, last and next expected invoice date). It describes invoicing rhythm, not payment behavior.
68
68
 
69
- Keep the contact-signals output per customer.
69
+ There is no risk score or payment-lateness history in this response. Rank customers on the aging in step 1 and the chase history in step 5.
70
70
 
71
71
  ## Step 4: Categorize for action
72
72
 
73
- Per customer, classify based on aging bucket + contact-signals:
73
+ Per customer, classify on the aging bucket, the amount owed and the chase history:
74
74
 
75
- | Bucket | Signal | Action |
76
- |--------|--------|--------|
75
+ | Bucket | Condition | Action |
76
+ |--------|-----------|--------|
77
77
  | < 30d | (any) | Soft reminder email, automated tooling outside Jaz. |
78
- | 30-60d | severity LOW | Phone call OR formal email. Document in narrative. |
79
- | 30-60d | severity MEDIUM/HIGH | Phone call + escalation to AR manager. |
78
+ | 30-60d | first time overdue | Phone call OR formal email. Document in narrative. |
79
+ | 30-60d | overdue before, or large `outstanding.notYetDueAmount` still to come | Phone call + escalation to AR manager. |
80
80
  | 60-90d | (any) | Formal demand letter. Begin specific-provision review (path A/B if appropriate). |
81
81
  | 90-120d | (any) | Final demand. Suspend further credit. Begin write-off review. |
82
- | 120d+ | severity LOW | Final demand + small claims / mediation option. |
83
- | 120d+ | severity HIGH | Likely uncollectible; proceed to step 6 specific write-off. |
82
+ | 120d+ | customer still responding | Final demand + small claims / mediation option. |
83
+ | 120d+ | no response, or evidence of insolvency | Likely uncollectible; proceed to step 6 specific write-off. |
84
84
 
85
- The contact-signals `outstandingSnapshot.recoverabilityScore` (0-100) is a useful tie-breaker.
85
+ "Overdue before" comes from your chase log (step 5).
86
86
 
87
87
  ## Step 5: Document chase activities
88
88
 
@@ -167,7 +167,8 @@ Returns XLSX with high-risk customer flags (rising aging trends, recently-defaul
167
167
  | Source | Error | Recovery |
168
168
  |--------|-------|----------|
169
169
  | Step 2 | `search_invoices` returns 0 despite aging shows overdue | Aging report may include `PARTIALLY_PAID` invoices; expand filter `status: {in: ['UNPAID', 'PARTIALLY_PAID']}`. |
170
- | Step 3 | `get_contact_signals` returns `null` | The freshness layer is offline; skip the signals step and use aging alone. Don't halt the job. |
170
+ | Step 3 | `severitySummary.dominantLabel` is `NO_ALERTS` | Signals could not be computed; use aging alone. Don't halt the job. |
171
+ | Step 3 | 403 `NOT_HAVE_ENOUGH_PERMISSION` | The user has no access to Sales; ask them, or skip step 3. |
171
172
  | Step 6 Path A | `apply_credits_to_invoice` 422 `credit_exceeds_balance` | Split the credit across multiple invoices, OR reduce the credit amount to match the bill balance. |
172
173
  | Step 6 Path B | `paymentMethod: 'DEBT_WRITE_OFF'` rejected | Verify the enum value via `jaz-api/SKILL.md` (some orgs may have custom payment-method config; default supports DEBT_WRITE_OFF). |
173
174
  | Step 7 ECL top-up | Top-up causes Bad Debt Expense to spike | Expected: material aging shift = material P&L impact. Surface to practitioner; potentially split across multiple periods if it's a known one-off (rare). |
@@ -179,7 +180,7 @@ Returns XLSX with high-risk customer flags (rising aging trends, recently-defaul
179
180
  ## Tips
180
181
 
181
182
  - **Run weekly, not monthly.** Aging gets worse the longer you wait. A 30-day-overdue invoice has a 70% recovery rate; 90-day-overdue drops to 40%; 120+ days to 20%.
182
- - **Contact-signals is the differentiator.** `get_contact_signals` surfaces who's likely to pay and who's behaving abnormally. Without this, credit control is just "send reminders to everyone."
183
+ - **Context before the call.** `get_contact_signals` gives the customer's usual terms and total exposure in one read, so the chase conversation starts from facts, not just the overdue invoice.
183
184
  - **Capsule per customer write-off** = audit trail. Even small write-offs ($500+) should have a capsule with the chase history.
184
185
  - **Year-end specific impairment** is harder than monthly. Catch it monthly; auditor will sample-test path A/B write-offs as part of audit-prep.
185
186
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-recipes
3
- version: 9.0.2
3
+ version: 9.2.0
4
4
  description: >-
5
5
  Use this skill when modeling complex multi-step accounting transactions:
6
6
  anything that spans multiple periods, involves changing amounts, or requires
package/cli.mjs CHANGED
@@ -671,11 +671,9 @@ Multiple organizations are accessible: pass org_id (from list_organizations) to
671
671
 
672
672
  resourceId per row \u2192 update; omit \u2192 create. Note: dedup is by NAME (not code); duplicate name surfaces ORGANIZATION_CHART_OF_ACCOUNT_DUPLICATED per row. Accepts all 23 accountType values (see create_account); common variants normalized via normalizeAccountType.
673
673
 
674
- Use for CoA imports / IFRS 18 migration / template seeding. For one-off creates with dedup, use create_account.`,params:{accounts:{type:"array",description:"Array of chart-of-accounts entries (max 500). Mix create and update freely.",items:{type:"object",properties:{resourceId:{type:"string",description:"Account resourceId (UUID); provide to update, omit to create"},code:{type:"string",description:"Account code (unique per org)"},name:{type:"string",description:"Account name (unique per org; dedup key)"},accountType:{type:"string",description:'Any of the 23 types listed on create_account (e.g. "Operating Expense", "Direct Costs", "Finance Cost"). Variants normalized client-side ("income tax" \u2192 "Income Tax Expense"). Required for create.'},currencyCode:{type:"string",description:'Currency code (e.g. "SGD"). Defaults to org functional currency.'},description:{type:"string",description:"Optional description / memo"},status:{type:"string",enum:["ACTIVE","INACTIVE"],description:"Account status"}}}}},required:["accounts"],group:"accounts",readOnly:!1,isDestructive:!0,searchHint:"bulk create or update chart of accounts import COA migration",execute:async(e,t)=>{Ut(t.accounts,"accounts",500,"chart-of-accounts entries");let r=t.accounts.map(GQ);return(await VQ(e.client,r)).data}},yr("list_contacts","List contacts (customers/suppliers). Returns billingName, name, emails, status. Paginated; response includes totalElements. Use limit/offset to page.","contacts",(e,t,r,n)=>Qb(e,{limit:r,offset:t,view:n}),"list customers suppliers contacts with email status",{leanView:!0}),Vt({name:"search_contacts",description:"Search contacts.",group:"contacts",fields:xD,defaults:kD,fetcher:pl,searchHint:"find contacts customers suppliers vendors by name email role status tax-id tag"}),{name:"get_contact",description:"Fetch a contact by internal resourceId. To search by other fields, use `search_contacts`.",params:{resourceId:{type:"string",description:"Contact resourceId (UUID)"}},required:["resourceId"],group:"contacts",readOnly:!0,searchHint:"get contact customer supplier details by id",isConcurrencySafe:!0,maxResultSizeChars:2e4,execute:async(e,t)=>PA(e.client,t.resourceId)},{name:"get_contact_signals",description:`Read-only contact pattern intelligence (cadence, outliers, divergences, severity). USE THIS for stand-alone history lookup before drafting (e.g. "what does this supplier normally look like?"). Use validate_drafts instead when you have a specific draft to score.
674
+ Use for CoA imports / IFRS 18 migration / template seeding. For one-off creates with dedup, use create_account.`,params:{accounts:{type:"array",description:"Array of chart-of-accounts entries (max 500). Mix create and update freely.",items:{type:"object",properties:{resourceId:{type:"string",description:"Account resourceId (UUID); provide to update, omit to create"},code:{type:"string",description:"Account code (unique per org)"},name:{type:"string",description:"Account name (unique per org; dedup key)"},accountType:{type:"string",description:'Any of the 23 types listed on create_account (e.g. "Operating Expense", "Direct Costs", "Finance Cost"). Variants normalized client-side ("income tax" \u2192 "Income Tax Expense"). Required for create.'},currencyCode:{type:"string",description:'Currency code (e.g. "SGD"). Defaults to org functional currency.'},description:{type:"string",description:"Optional description / memo"},status:{type:"string",enum:["ACTIVE","INACTIVE"],description:"Account status"}}}}},required:["accounts"],group:"accounts",readOnly:!1,isDestructive:!0,searchHint:"bulk create or update chart of accounts import COA migration",execute:async(e,t)=>{Ut(t.accounts,"accounts",500,"chart-of-accounts entries");let r=t.accounts.map(GQ);return(await VQ(e.client,r)).data}},yr("list_contacts","List contacts (customers/suppliers). Returns billingName, name, emails, status. Paginated; response includes totalElements. Use limit/offset to page.","contacts",(e,t,r,n)=>Qb(e,{limit:r,offset:t,view:n}),"list customers suppliers contacts with email status",{leanView:!0}),Vt({name:"search_contacts",description:"Search contacts.",group:"contacts",fields:xD,defaults:kD,fetcher:pl,searchHint:"find contacts customers suppliers vendors by name email role status tax-id tag"}),{name:"get_contact",description:"Fetch a contact by internal resourceId. To search by other fields, use `search_contacts`.",params:{resourceId:{type:"string",description:"Contact resourceId (UUID)"}},required:["resourceId"],group:"contacts",readOnly:!0,searchHint:"get contact customer supplier details by id",isConcurrencySafe:!0,maxResultSizeChars:2e4,execute:async(e,t)=>PA(e.client,t.resourceId)},{name:"get_contact_signals",description:`A contact's usual values for one transaction type, from its recent posted transactions (last 12 months, up to 50): currency, payment terms, tax, account and item, each with counts. Also returns its open balance (all dates and currencies, in the base currency, up to 12h behind payments) and, only for a contact that transacts regularly, its billing rhythm (median interval, last and next expected date). USE THIS before drafting ("what does this supplier normally look like?"). To check a specific draft against this history use validate_drafts.
675
675
 
676
- Returns null when the contact has no qualifying history yet (sampleSize < 5) or the freshness layer is unavailable. Per-pair cache (contactId \xD7 btType); duplicate calls for the same pair return the same result.
677
-
678
- Three response slices (severitySummary, outlierFlags, revealedDivergences) populate ONLY on the validate_drafts path; empty/null here.`,params:{resourceId:{type:"string",description:"Contact resourceId (UUID)"},btType:{type:"string",description:"Business-transaction type to scope the signal sample to.",enum:["SALE","PURCHASE","SALE_CREDIT_NOTE","PURCHASE_CREDIT_NOTE"]}},required:["resourceId","btType"],group:"contacts",readOnly:!0,searchHint:"contact signals patterns cadence outliers severity history",isConcurrencySafe:!0,maxResultSizeChars:2e4,execute:async(e,t)=>{let r=t.resourceId;return Co(r,"resourceId","Call search_contacts to get a valid contact resourceId."),LRe(e.client,r,t.btType)}},{name:"create_contact",description:"Create a new contact. Auto-checks for duplicates by name; returns existing contact if found instead of creating a duplicate.",params:{billingName:{type:"string",description:"Official billing name"},name:{type:"string",description:"Display name (usually same as billingName)"},email:{type:"string",description:"Primary email address"},customer:{type:"boolean",description:"Is a customer (default true)"},supplier:{type:"boolean",description:"Is a supplier (default false)"},returnFullEntity:Vy},required:["billingName"],group:"contacts",readOnly:!1,searchHint:"create new customer or supplier contact",execute:async(e,t)=>{let r=t.name??t.billingName,n=t.returnFullEntity===!0,i=await lS(e.client,r);if(i)return{_guard:"duplicate_skipped",message:`Contact "${r}" already exists.`,existing:i};let o=await Lb(e.client,{billingName:t.billingName,name:r,emails:t.email?[t.email]:void 0,customer:t.customer??!0,supplier:t.supplier??!1});return Wu(o,n,s=>PA(e.client,s))}},{name:"update_contact",description:"Update an existing contact. Fetches current data first so you only need to send changed fields.",params:{resourceId:{type:"string",description:"Contact resourceId"},billingName:{type:"string"},name:Epe,email:{type:"string"}},required:["resourceId"],group:"contacts",readOnly:!1,isDestructive:!0,searchHint:"update existing contact billing name email details",execute:async(e,t)=>{let{resourceId:r,...n}=t,{data:i}=await PA(e.client,r),o=["billingName","name","email","customer","supplier","taxId","phone","status","currencyCode","customerPaymentTerms"],s=i,a=Object.fromEntries(o.filter(c=>s[c]!==void 0&&s[c]!==null).map(c=>[c,s[c]]));return Object.assign(a,n),TQ(e.client,r,a)}},yr("list_invoices","List invoices. Returns reference, date, status, contact, totalAmount. Paginated; response includes totalElements. Use limit/offset to page.","invoices",(e,t,r,n)=>RU(e,{limit:r,offset:t,view:n}),"list sales invoices with pagination and status",{leanView:!0}),Vt({name:"search_invoices",description:'Search invoices. Supports comma-separated status (e.g., "UNPAID,PARTIALLY_PAID"). Use filter param for advanced queries (nested paths, OR groups).',group:"invoices",fields:FI,defaults:QI,fetcher:UI,searchHint:"find sales invoices by status date contact amount"}),{name:"get_invoice",description:"Fetch an invoice by internal resourceId. To search by other fields, use `search_invoices`.",params:{resourceId:{type:"string",description:"Invoice resourceId"}},required:["resourceId"],group:"invoices",readOnly:!0,searchHint:"get invoice details line items payments totals",isConcurrencySafe:!0,maxResultSizeChars:2e4,execute:async(e,t)=>Ls(e.client,t.resourceId)},{name:"create_invoice",description:'Create invoice. lineItem text field = `name` (not "description"). saveAsDraft defaults true; false only on "finalize".\n\naccountResourceId per lineItem required only when finalizing. lineItem minimum: `{ name, unitPrice, quantity, accountResourceId }`. Currency: `{ sourceCurrency: "USD" }`; see the exchangeRate param for rate direction. Dates YYYY-MM-DD.\n\nreference MUST be unique per org. Never invent one; set autoReference:true to take the organization\'s next number. Omitting both is an error. Duplicate \u2192 422.',params:{reference:{type:"string",description:"Invoice reference number"},autoReference:ed,valueDate:{type:"string",description:"Invoice date (YYYY-MM-DD)"},dueDate:{type:"string",description:"Due date (YYYY-MM-DD)"},contactResourceId:{type:"string",description:"Contact resourceId"},lineItems:rd,currency:zs,saveAsDraft:{type:"boolean",description:"Save as draft (default true)"},notes:di,tag:td,customFields:Ni,saleOrderResourceId:{type:"string",description:"Optional: link this invoice to a source Sale Order (records the order\u2192invoice relationship)."},saleQuoteResourceId:{type:"string",description:"Optional: link this invoice to a source Sale Quote."},returnFullEntity:Vy,capsuleResourceId:Wy},required:["valueDate","dueDate","lineItems"],group:"invoices",readOnly:!1,searchHint:"create new sales invoice draft with line items deferred revenue subscription",execute:async(e,t)=>{let r=hl(t);if(r)return r;let{notes:n,tag:i,returnFullEntity:o,autoReference:s,...a}=t,c=await wi(e.client,"SALE",{...a,...n!==void 0&&{invoiceNotes:n},...i!==void 0&&{tags:[i]}},s),u=await NR(e.client,c),d=uS(u.appliedRate,t.currency);return Pf(await Wu(u,o===!0,f=>Ls(e.client,f)),d)}},{name:"update_invoice",description:'Update an existing draft invoice (change reference, dates, line items, notes, custom fields). Use when the user says "update", "change", "fix", or "correct" a draft invoice. Line items CAN be fully replaced; pass the complete updated lineItems array.',params:{resourceId:{type:"string",description:"Invoice resourceId"},reference:xo,valueDate:ho,dueDate:zl,lineItems:rd,notes:di,customFields:Ni},required:["resourceId"],group:"invoices",readOnly:!1,isDestructive:!0,searchHint:"update draft invoice reference dates line items",execute:async(e,t)=>{let r=Zy(e,"update_invoice");if(r)return r;let{resourceId:n,notes:i,tag:o,...s}=t,a={...s,...i!==void 0&&{invoiceNotes:i},...o!==void 0&&{tags:[o]}};return TU(e.client,n,a)}},en("delete_invoice",PB("invoice"),"invoices",xU,"delete draft or void active invoice",{paramDescription:"Invoice resourceId",voidable:!0}),{name:"pay_invoice",description:"Record a payment against an invoice. Invoice must be UNPAID/PARTIALLY_PAID before payment; draft or voided invoices cannot be paid. If the invoice is still a DRAFT, call finalize_invoice first.",params:{resourceId:{type:"string",description:"Invoice resourceId"},paymentAmount:{type:"number",description:"Amount to pay (in bank currency)"},transactionAmount:{type:"number",description:"Amount in invoice currency (defaults to paymentAmount for same-currency)"},accountResourceId:FB,valueDate:{type:"string",description:"Payment date (YYYY-MM-DD)"},reference:{type:"string",description:"Payment reference"},paymentMethod:$f,adjustment:I_,customFields:Ni},required:["resourceId","paymentAmount","accountResourceId","valueDate"],group:"invoices",readOnly:!1,searchHint:"record payment against an approved invoice",isDestructive:!0,execute:async(e,t)=>{let r=t.resourceId,n=Number(t.paymentAmount);if(!Number.isFinite(n)||n<=0)throw new Error("paymentAmount must be a positive number");let i=Number(t.transactionAmount??n);if(!Number.isFinite(i)||i<=0)throw new Error("transactionAmount must be a positive number");return await rit(Ls,e.client,r,"invoice"),OR(e.client,r,{paymentAmount:n,transactionAmount:i,accountResourceId:t.accountResourceId,valueDate:t.valueDate,dueDate:t.valueDate,reference:t.reference||Ro("PMT"),paymentMethod:t.paymentMethod??"BANK_TRANSFER",saveAsDraft:!1,adjustment:t.adjustment,customFields:t.customFields})}},{name:"finalize_invoice",description:"Finalize a draft invoice (set saveAsDraft=false). Can optionally update fields in the same call. IMPORTANT: Every lineItem MUST have accountResourceId. If the draft was created without it, pass lineItems with accountResourceId added (search accounts first, e.g. Operating Revenue for sales).",params:{resourceId:{type:"string",description:"Invoice resourceId"},reference:xo,valueDate:ho,dueDate:zl,lineItems:rd,notes:di},required:["resourceId"],group:"invoices",readOnly:!1,searchHint:"approve finalize draft invoice for sending",isDestructive:!0,execute:async(e,t)=>{let{resourceId:r,notes:n,...i}=t,o={...i,...n!==void 0&&{invoiceNotes:n}},s=await qB(e.client,"invoice",r,o),a=hl(s,{assumeFinalize:!0});if(a)return a;if(s.reference){let c=s.reference;((await UI(e.client,{filter:{reference:{eq:c}},limit:1,offset:0,sort:{sortBy:["valueDate"],order:"DESC"}})).data??[]).filter(f=>f.resourceId!==r).length>0&&(s.reference=Ro(c))}return QR(e.client,r,s)}},{name:"apply_credits_to_invoice",description:`Apply customer credit note(s) to an invoice. You MUST call this tool to actually apply credits; do not just describe the steps.
676
+ sampleSize 0 with severitySummary.dominantLabel NEW = no history of this type. NO_ALERTS = could not be computed; carry on without it. 403 NOT_HAVE_ENOUGH_PERMISSION = the user lacks Sales (SALE, SALE_CREDIT_NOTE) or Purchases (PURCHASE, PURCHASE_CREDIT_NOTE).`,params:{resourceId:{type:"string",description:"Contact resourceId (UUID)"},btType:{type:"string",description:"Transaction type whose history to read: SALE = invoices, PURCHASE = bills, SALE_CREDIT_NOTE = customer credit notes, PURCHASE_CREDIT_NOTE = supplier credit notes.",enum:["SALE","PURCHASE","SALE_CREDIT_NOTE","PURCHASE_CREDIT_NOTE"]}},required:["resourceId","btType"],group:"contacts",readOnly:!0,searchHint:"contact signals usual patterns currency payment terms tax account item cadence billing rhythm open balance supplier customer history",isConcurrencySafe:!0,maxResultSizeChars:2e4,execute:async(e,t)=>{let r=t.resourceId;return Co(r,"resourceId","Call search_contacts to get a valid contact resourceId."),LRe(e.client,r,t.btType)}},{name:"create_contact",description:"Create a new contact. Auto-checks for duplicates by name; returns existing contact if found instead of creating a duplicate.",params:{billingName:{type:"string",description:"Official billing name"},name:{type:"string",description:"Display name (usually same as billingName)"},email:{type:"string",description:"Primary email address"},customer:{type:"boolean",description:"Is a customer (default true)"},supplier:{type:"boolean",description:"Is a supplier (default false)"},returnFullEntity:Vy},required:["billingName"],group:"contacts",readOnly:!1,searchHint:"create new customer or supplier contact",execute:async(e,t)=>{let r=t.name??t.billingName,n=t.returnFullEntity===!0,i=await lS(e.client,r);if(i)return{_guard:"duplicate_skipped",message:`Contact "${r}" already exists.`,existing:i};let o=await Lb(e.client,{billingName:t.billingName,name:r,emails:t.email?[t.email]:void 0,customer:t.customer??!0,supplier:t.supplier??!1});return Wu(o,n,s=>PA(e.client,s))}},{name:"update_contact",description:"Update an existing contact. Fetches current data first so you only need to send changed fields.",params:{resourceId:{type:"string",description:"Contact resourceId"},billingName:{type:"string"},name:Epe,email:{type:"string"}},required:["resourceId"],group:"contacts",readOnly:!1,isDestructive:!0,searchHint:"update existing contact billing name email details",execute:async(e,t)=>{let{resourceId:r,...n}=t,{data:i}=await PA(e.client,r),o=["billingName","name","email","customer","supplier","taxId","phone","status","currencyCode","customerPaymentTerms"],s=i,a=Object.fromEntries(o.filter(c=>s[c]!==void 0&&s[c]!==null).map(c=>[c,s[c]]));return Object.assign(a,n),TQ(e.client,r,a)}},yr("list_invoices","List invoices. Returns reference, date, status, contact, totalAmount. Paginated; response includes totalElements. Use limit/offset to page.","invoices",(e,t,r,n)=>RU(e,{limit:r,offset:t,view:n}),"list sales invoices with pagination and status",{leanView:!0}),Vt({name:"search_invoices",description:'Search invoices. Supports comma-separated status (e.g., "UNPAID,PARTIALLY_PAID"). Use filter param for advanced queries (nested paths, OR groups).',group:"invoices",fields:FI,defaults:QI,fetcher:UI,searchHint:"find sales invoices by status date contact amount"}),{name:"get_invoice",description:"Fetch an invoice by internal resourceId. To search by other fields, use `search_invoices`.",params:{resourceId:{type:"string",description:"Invoice resourceId"}},required:["resourceId"],group:"invoices",readOnly:!0,searchHint:"get invoice details line items payments totals",isConcurrencySafe:!0,maxResultSizeChars:2e4,execute:async(e,t)=>Ls(e.client,t.resourceId)},{name:"create_invoice",description:'Create invoice. lineItem text field = `name` (not "description"). saveAsDraft defaults true; false only on "finalize".\n\naccountResourceId per lineItem required only when finalizing. lineItem minimum: `{ name, unitPrice, quantity, accountResourceId }`. Currency: `{ sourceCurrency: "USD" }`; see the exchangeRate param for rate direction. Dates YYYY-MM-DD.\n\nreference MUST be unique per org. Never invent one; set autoReference:true to take the organization\'s next number. Omitting both is an error. Duplicate \u2192 422.',params:{reference:{type:"string",description:"Invoice reference number"},autoReference:ed,valueDate:{type:"string",description:"Invoice date (YYYY-MM-DD)"},dueDate:{type:"string",description:"Due date (YYYY-MM-DD)"},contactResourceId:{type:"string",description:"Contact resourceId"},lineItems:rd,currency:zs,saveAsDraft:{type:"boolean",description:"Save as draft (default true)"},notes:di,tag:td,customFields:Ni,saleOrderResourceId:{type:"string",description:"Optional: link this invoice to a source Sale Order (records the order\u2192invoice relationship)."},saleQuoteResourceId:{type:"string",description:"Optional: link this invoice to a source Sale Quote."},returnFullEntity:Vy,capsuleResourceId:Wy},required:["valueDate","dueDate","lineItems"],group:"invoices",readOnly:!1,searchHint:"create new sales invoice draft with line items deferred revenue subscription",execute:async(e,t)=>{let r=hl(t);if(r)return r;let{notes:n,tag:i,returnFullEntity:o,autoReference:s,...a}=t,c=await wi(e.client,"SALE",{...a,...n!==void 0&&{invoiceNotes:n},...i!==void 0&&{tags:[i]}},s),u=await NR(e.client,c),d=uS(u.appliedRate,t.currency);return Pf(await Wu(u,o===!0,f=>Ls(e.client,f)),d)}},{name:"update_invoice",description:'Update an existing draft invoice (change reference, dates, line items, notes, custom fields). Use when the user says "update", "change", "fix", or "correct" a draft invoice. Line items CAN be fully replaced; pass the complete updated lineItems array.',params:{resourceId:{type:"string",description:"Invoice resourceId"},reference:xo,valueDate:ho,dueDate:zl,lineItems:rd,notes:di,customFields:Ni},required:["resourceId"],group:"invoices",readOnly:!1,isDestructive:!0,searchHint:"update draft invoice reference dates line items",execute:async(e,t)=>{let r=Zy(e,"update_invoice");if(r)return r;let{resourceId:n,notes:i,tag:o,...s}=t,a={...s,...i!==void 0&&{invoiceNotes:i},...o!==void 0&&{tags:[o]}};return TU(e.client,n,a)}},en("delete_invoice",PB("invoice"),"invoices",xU,"delete draft or void active invoice",{paramDescription:"Invoice resourceId",voidable:!0}),{name:"pay_invoice",description:"Record a payment against an invoice. Invoice must be UNPAID/PARTIALLY_PAID before payment; draft or voided invoices cannot be paid. If the invoice is still a DRAFT, call finalize_invoice first.",params:{resourceId:{type:"string",description:"Invoice resourceId"},paymentAmount:{type:"number",description:"Amount to pay (in bank currency)"},transactionAmount:{type:"number",description:"Amount in invoice currency (defaults to paymentAmount for same-currency)"},accountResourceId:FB,valueDate:{type:"string",description:"Payment date (YYYY-MM-DD)"},reference:{type:"string",description:"Payment reference"},paymentMethod:$f,adjustment:I_,customFields:Ni},required:["resourceId","paymentAmount","accountResourceId","valueDate"],group:"invoices",readOnly:!1,searchHint:"record payment against an approved invoice",isDestructive:!0,execute:async(e,t)=>{let r=t.resourceId,n=Number(t.paymentAmount);if(!Number.isFinite(n)||n<=0)throw new Error("paymentAmount must be a positive number");let i=Number(t.transactionAmount??n);if(!Number.isFinite(i)||i<=0)throw new Error("transactionAmount must be a positive number");return await rit(Ls,e.client,r,"invoice"),OR(e.client,r,{paymentAmount:n,transactionAmount:i,accountResourceId:t.accountResourceId,valueDate:t.valueDate,dueDate:t.valueDate,reference:t.reference||Ro("PMT"),paymentMethod:t.paymentMethod??"BANK_TRANSFER",saveAsDraft:!1,adjustment:t.adjustment,customFields:t.customFields})}},{name:"finalize_invoice",description:"Finalize a draft invoice (set saveAsDraft=false). Can optionally update fields in the same call. IMPORTANT: Every lineItem MUST have accountResourceId. If the draft was created without it, pass lineItems with accountResourceId added (search accounts first, e.g. Operating Revenue for sales).",params:{resourceId:{type:"string",description:"Invoice resourceId"},reference:xo,valueDate:ho,dueDate:zl,lineItems:rd,notes:di},required:["resourceId"],group:"invoices",readOnly:!1,searchHint:"approve finalize draft invoice for sending",isDestructive:!0,execute:async(e,t)=>{let{resourceId:r,notes:n,...i}=t,o={...i,...n!==void 0&&{invoiceNotes:n}},s=await qB(e.client,"invoice",r,o),a=hl(s,{assumeFinalize:!0});if(a)return a;if(s.reference){let c=s.reference;((await UI(e.client,{filter:{reference:{eq:c}},limit:1,offset:0,sort:{sortBy:["valueDate"],order:"DESC"}})).data??[]).filter(f=>f.resourceId!==r).length>0&&(s.reference=Ro(c))}return QR(e.client,r,s)}},{name:"apply_credits_to_invoice",description:`Apply customer credit note(s) to an invoice. You MUST call this tool to actually apply credits; do not just describe the steps.
679
677
  Steps: 1) search_customer_credit_notes with status UNAPPLIED for the same contact. 2) If CN is still DRAFT, call finalize_customer_credit_note first. 3) Call this tool with creditNoteResourceId and amountApplied for each CN.`,params:{resourceId:{type:"string",description:"Invoice resourceId"},credits:{type:"array",items:{type:"object",properties:{creditNoteResourceId:{type:"string",description:"Customer credit note resourceId"},amountApplied:{type:"number",description:"Amount to apply"}},required:["creditNoteResourceId","amountApplied"]},description:"Credits to apply"}},required:["resourceId","credits"],group:"invoices",readOnly:!1,searchHint:"apply customer credit note to invoice balance",isDestructive:!0,execute:async(e,t)=>kU(e.client,t.resourceId,t.credits)},$B("download_invoice_pdf","invoice","invoices",(e,t)=>BU(e,t)),yr("list_bills","List bills (purchase invoices). Returns reference, date, status, contact, amounts. Paginated; response includes totalElements. Use limit/offset to page.","bills",(e,t,r,n)=>NQ(e,{limit:r,offset:t,view:n}),"list purchase bills with pagination and status",{leanView:!0}),Vt({name:"search_bills",description:'Search bills. Supports comma-separated status (e.g., "UNPAID,PARTIALLY_PAID"). Use filter param for advanced queries.',group:"bills",fields:CD,defaults:wD,fetcher:Md,searchHint:"find purchase bills by status date supplier amount"}),{name:"get_bill",description:"Fetch a bill by internal resourceId. To search by other fields, use `search_bills`.",params:{resourceId:za},required:["resourceId"],group:"bills",readOnly:!0,searchHint:"get bill details line items payments totals",isConcurrencySafe:!0,maxResultSizeChars:2e4,execute:async(e,t)=>Ns(e.client,t.resourceId)},{name:"create_bill",description:"Create a new bill. Same rules as create_invoice: name not description, saveAsDraft defaults true, accountResourceId REQUIRED on lineItems for finalized bills (search accounts first, e.g. Operating Expense). When selecting a tax profile, use search_tax_profiles with appliesTo \"purchase\"; sales-only profiles cause 422. reference MUST be unique; use the SUPPLIER'S OWN invoice number. If the document genuinely has none, set autoReference:true for the org's next number. Never invent one. A duplicate guard (supplier + date + currency + amount) returns `_guard: duplicate_suspected` with the existing bill instead of creating; show it, retry with allowDuplicate only once the user confirms it is separate.",params:{reference:xo,autoReference:ed,allowDuplicate:Rbr,valueDate:ho,dueDate:zl,contactResourceId:gpe,lineItems:LB,currency:zs,saveAsDraft:HB,notes:di,tag:td,customFields:Ni,purchaseOrderResourceId:{type:"string",description:"Optional: link this bill to a source Purchase Order (records the order\u2192bill relationship)."},purchaseRequestResourceId:{type:"string",description:"Optional: link this bill to a source Purchase Request."},returnFullEntity:Vy,capsuleResourceId:Wy},required:["valueDate","dueDate","lineItems"],group:"bills",readOnly:!1,searchHint:"create new purchase bill draft with line items prepaid amortize accrual",execute:async(e,t)=>{let r=hl(t);if(r)return r;let{notes:n,tag:i,returnFullEntity:o,allowDuplicate:s,autoReference:a,...c}=t;if(s!==!0){let p=await XL(e.client,{contactResourceId:c.contactResourceId,valueDate:c.valueDate,currencyCode:c.currency?.sourceCurrency,lineSubtotal:KL(c.lineItems)});if(p)return{_guard:"duplicate_suspected",message:eP(p,c.valueDate,"allowDuplicate: true"),existing:p}}let u=await wi(e.client,"PURCHASE",{...c,...n!==void 0&&{invoiceNotes:n},...i!==void 0&&{tags:[i]}},a),d=await P0(e.client,u),f=uS(d.appliedRate,t.currency);return Pf(await Wu(d,o===!0,p=>Ns(e.client,p)),f)}},{name:"update_bill",description:'Update an existing draft bill (change reference, dates, line items, notes, custom fields). Use when the user says "update", "change", "fix", or "correct" a draft bill. Line items CAN be fully replaced; pass the complete updated lineItems array.',params:{resourceId:{type:"string",description:"Bill resourceId"},reference:xo,valueDate:ho,dueDate:zl,lineItems:LB,notes:di,customFields:Ni},required:["resourceId"],group:"bills",readOnly:!1,isDestructive:!0,searchHint:"update draft bill reference dates line items",execute:async(e,t)=>{let r=Zy(e,"update_bill");if(r)return r;let{resourceId:n,notes:i,tag:o,...s}=t,a={...s,...i!==void 0&&{invoiceNotes:i},...o!==void 0&&{tags:[o]}};return OQ(e.client,n,a)}},en("delete_bill",PB("bill"),"bills",FQ,"delete draft or void active bill",{paramDescription:za.description,voidable:!0}),$B("download_bill_pdf","bill","bills",(e,t)=>QQ(e,t)),{name:"pay_bill",description:"Record a payment against a bill. Bill must be UNPAID/PARTIALLY_PAID before payment; draft or voided bills cannot be paid.",params:{resourceId:za,paymentAmount:Tbr,transactionAmount:{type:"number",description:"Amount in bill currency (defaults to paymentAmount for same-currency)"},accountResourceId:FB,valueDate:ho,reference:xo,paymentMethod:$f,adjustment:I_,customFields:Ni},required:["resourceId","paymentAmount","accountResourceId","valueDate"],group:"bills",readOnly:!1,searchHint:"record payment against an approved bill",isDestructive:!0,execute:async(e,t)=>{let r=t.resourceId,n=Number(t.paymentAmount);if(!Number.isFinite(n)||n<=0)throw new Error("paymentAmount must be a positive number");let i=Number(t.transactionAmount??n);if(!Number.isFinite(i)||i<=0)throw new Error("transactionAmount must be a positive number");return await rit(Ns,e.client,r,"bill"),U0(e.client,r,{paymentAmount:n,transactionAmount:i,accountResourceId:t.accountResourceId,valueDate:t.valueDate,dueDate:t.valueDate,reference:t.reference||Ro("PMT"),paymentMethod:t.paymentMethod??"BANK_TRANSFER",saveAsDraft:!1,adjustment:t.adjustment,customFields:t.customFields})}},{name:"finalize_bill",description:"Finalize a draft bill (set saveAsDraft=false). Can optionally update fields in the same call. IMPORTANT: Every lineItem MUST have accountResourceId. If the draft was created without it, pass lineItems with accountResourceId added (search accounts first, e.g. Operating Expense for purchases).",params:{resourceId:{type:"string",description:"Bill resourceId"},reference:xo,valueDate:ho,dueDate:zl,lineItems:LB,notes:di},required:["resourceId"],group:"bills",readOnly:!1,searchHint:"approve finalize draft bill for processing",isDestructive:!0,execute:async(e,t)=>{let{resourceId:r,notes:n,...i}=t,o={...i,...n!==void 0&&{invoiceNotes:n}},s=await qB(e.client,"bill",r,o),a=hl(s,{assumeFinalize:!0});return a||M0(e.client,r,s)}},{name:"apply_credits_to_bill",description:"Apply supplier credit note(s) to a bill. IMPORTANT: The credit note must be FINALIZED first (status UNAPPLIED, not DRAFT). If it is still a draft, call finalize_supplier_credit_note first. Then search_supplier_credit_notes with status UNAPPLIED to find available credits for the contact, and pass creditNoteResourceId and amountApplied for each.",params:{resourceId:{type:"string",description:"Bill resourceId"},credits:{type:"array",items:{type:"object",properties:{creditNoteResourceId:{type:"string",description:"Supplier credit note resourceId"},amountApplied:{type:"number",description:"Amount to apply"}},required:["creditNoteResourceId","amountApplied"]},description:"Credits to apply"}},required:["resourceId","credits"],group:"bills",readOnly:!1,searchHint:"apply supplier credit note to bill balance",isDestructive:!0,execute:async(e,t)=>LQ(e.client,t.resourceId,t.credits)},{name:"create_sale_order",description:`Create a Sale Quote or Sale Order (pick via documentType). lineItem text field = \`name\`. Dates YYYY-MM-DD. \`reference\` unique per org; set autoReference:true for the org's next number.
680
678
 
681
679
  SALE_QUOTE: DRAFT by default. Lifecycle DRAFT \u2192 (issue) \u2192 CREATED \u2192 (accept_sale_quote) \u2192 ACCEPTED. A DRAFT quote can't be linked or accepted: issue it via update_sale_order isDraftToActiveSaleQuote:true, never a second quote.
@@ -822,13 +820,14 @@ ASYNC: returns jobId \u2192 poll search_background_jobs(filter:{resourceId:{eq:j
822
820
 
823
821
  Required per row: reference, registrationType. NEW also needs purchaseBusinessTransactionType + purchaseBusinessTransactionResourceId (the purchase LINE ITEM, not the bill/journal id); TRANSFER takes accumulatedDepreciationAtTransferDate + bookValueAtTransferDate. Recommended: typeCode, typeName, category, purchaseAmount, purchaseDate, effectiveLife, depreciationMethod + the three account UUIDv4s.
824
822
 
825
- ASYNC: returns jobId \u2192 poll search_background_jobs(filter:{resourceId:{eq:jobId}}); on PARTIAL_SUCCESS read data[0].errorDetails.`,params:{fixedAssets:{type:"array",description:"Fixed assets to create/update (max 500).",items:{type:"object",properties:{rowIndex:{type:"string"},reference:{type:"string",description:"Natural key"},registrationType:{type:"string",enum:["NEW","TRANSFER"],description:"NEW = newly acquired, TRANSFER = inherited; required"},typeCode:{type:"string",description:"Asset class code (e.g. FURNITURE_AND_FIXTURE)"},typeName:{type:"string",description:"Human label for typeCode"},category:{type:"string",enum:["TANGIBLE","INTANGIBLE"],description:"TANGIBLE | INTANGIBLE"},name:{type:"string",description:"Asset name"},purchaseAmount:{type:"number",description:"Acquisition cost."},purchaseDate:{type:"number",description:"Acquisition date, epoch ms (NOT YYYY-MM-DD on this endpoint)."},depreciationStartDate:{type:"number",description:"Epoch ms. Omit to default to purchaseDate."},effectiveLife:{type:"number",description:"Useful life months."},depreciationMethod:{type:"string",enum:["STRAIGHT_LINE","NO_DEPRECIATION"],description:"Bulk accepts only STRAIGHT_LINE or NO_DEPRECIATION (422 otherwise); use create_fixed_asset for declining-balance methods."},purchaseAssetAccountResourceId:{type:"string",description:"Asset (PPE) account UUIDv4."},depreciationExpenseAccountResourceId:{type:"string",description:"Depreciation expense account UUIDv4."},accumulatedDepreciationAccountResourceId:{type:"string",description:"Accumulated depreciation account UUIDv4."},purchaseBusinessTransactionType:{type:"string",enum:[...jy],description:"Required for NEW."},purchaseBusinessTransactionResourceId:{type:"string",description:"Purchase LINE ITEM id, unlinked. Required for NEW."},accumulatedDepreciationAtTransferDate:{type:"number",description:"TRANSFER only."},bookValueAtTransferDate:{type:"number",description:"TRANSFER only."},internalNotes:{type:"string"}},required:["reference"]}}},required:["fixedAssets"],group:"fixed_assets",readOnly:!1,isDestructive:!0,searchHint:"bulk fixed assets ppe register upsert import migrate",execute:async(e,t)=>{Ut(t.fixedAssets,"fixedAssets",500,"fixed assets");let r=t.fixedAssets;return ORe(r,["purchaseDate","depreciationStartDate"]),iG(e.client,r)}},{name:"quick_reconcile",description:`Bulk-reconcile bank statement entries against a list of journals (max 500). Reconciliation mode is hardcoded to QUICK_RECON. ${ql} Caller provides bankAccountResourceId + journalsForReconciliation[] (each row: bankStatementEntryResourceId, journalReference, journalAccountResourceId).`,params:{bankAccountResourceId:{type:"string",description:"Bank account UUID (the entries belong to this account)"},journalsForReconciliation:{type:"array",description:"Per-row reconciliation instructions (max 500).",items:{type:"object",properties:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},journalReference:{type:"string",description:"Journal reference text"},journalAccountResourceId:{type:"string",description:"Offsetting account UUID (revenue for cash-in, expense for cash-out)"},contactResourceId:{type:"string",description:"Contact UUID"},capsuleResourceId:{type:"string",description:"Capsule UUID"},taxProfileResourceId:{type:"string",description:"Tax profile UUID"},internalNotes:{type:"string"},journalEntryDescription:{type:"string"},tags:{type:"array",items:{type:"string"}},rowIndex:{type:"string",description:"Optional caller-supplied index for error correlation"}},required:["bankStatementEntryResourceId","journalAccountResourceId","journalReference"]}}},required:["bankAccountResourceId","journalsForReconciliation"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"bulk reconcile bank statement journals match",execute:async(e,t)=>(Ut(t.journalsForReconciliation,"journalsForReconciliation",500,"journals"),iV(e.client,t))},{name:"apply_bank_rule",description:"Reconcile bank statement entries (max 500) with one saved bank rule: each entry gets the cash-in or cash-out entry the rule defines (its accounts and amount split). Async: returns a jobId; poll search_background_jobs filtered by resourceId.",params:{actionShortcutResourceId:{type:"string",description:"Saved bank rule resourceId"},businessTransactionResourceIds:{type:"array",description:"Bank statement entry resourceIds (max 500).",items:{type:"string"}}},required:["actionShortcutResourceId","businessTransactionResourceIds"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"bank rule apply action shortcut bulk reconcile",execute:async(e,t)=>(Ut(t.businessTransactionResourceIds,"businessTransactionResourceIds",500,"entries"),oV(e.client,t))},{name:"reconcile_direct_cash_entry",description:"Reconcile a bank statement entry with a single cash-in / cash-out line. Direction (cash-in vs cash-out) is INFERRED from the bank entry sign. Sync: returns the reconciled entry status. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.",params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID (used to look up the entry)"},reference:{type:"string",description:"Reference for the cash entry"},accountResourceId:{type:"string",description:"Offsetting account UUID (revenue for cash-in, expense for cash-out)"},amount:{type:"number",description:"Amount in source currency; defaults to and must EQUAL the entry amount; a partial allocation 422s (Total journal entries amount mismatch). Split an entry via reconcile_cash_journal instead."},description:{type:"string"},contactResourceId:{type:"string"},taxProfileResourceId:{type:"string"},internalNotes:{type:"string"},bankAccountJournalEntryDescription:{type:"string",description:"Description for the bank-side journal entry"},tags:{type:"array",items:{type:"string"}},taxVatApplicable:{type:"boolean"},taxInclusion:{type:"boolean"},capsuleResourceId:{type:"string"}},required:["bankStatementEntryResourceId","bankAccountResourceId","reference","accountResourceId"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry direct cash in out single line",execute:async(e,t)=>sV(e.client,t)},{name:"reconcile_cash_journal",description:"Reconcile a bank statement entry with a multi-line cashflow journal (multiple cash splits, max 200 lines). Sync: returns the reconciled entry status. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.",params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID"},reference:{type:"string",description:"Journal reference"},journalEntries:{type:"array",description:"Cash journal lines (max 200). Each row is one debit OR credit against an account.",items:{type:"object",properties:{accountResourceId:{type:"string",description:"Account UUID"},amount:{type:"number",description:"Amount in source currency (>0)"},description:{type:"string"},taxProfileResourceId:{type:"string"}},required:["accountResourceId","amount"]}},contactResourceId:{type:"string"},bankAccountJournalEntryDescription:{type:"string"},internalNotes:{type:"string"},tags:{type:"array",items:{type:"string"}},taxVatApplicable:{type:"boolean"},taxInclusion:{type:"boolean"},capsuleResourceId:{type:"string"}},required:["bankStatementEntryResourceId","bankAccountResourceId","reference","journalEntries"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry multi line cash journal splits",execute:async(e,t)=>(Ut(t.journalEntries,"journalEntries",200,"entries"),aV(e.client,t))},{name:"reconcile_manual_journal",description:'\u26A0\uFE0F CALLER PROVIDES ONLY THE OFFSET LEG(S); backend AUTO-ADDS the bank-side leg from the statement entry. Sending both debit AND credit legs \u2192 422 "sum of debit and credit amounts are not equal" (your sides duplicate after the auto-add). Reconcile a bank statement entry with a double-entry manual journal. Sync: returns the reconciled entry status. valueDate prefills from bank entry if omitted. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.',params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID"},status:{type:"string",enum:["DRAFT","ACTIVE"],description:"Journal status"},journalEntries:{type:"array",description:"Offset-side journal entries only (max 200). The bank-side leg is auto-added by the API.",items:{type:"object",properties:{accountResourceId:{type:"string",description:"Account UUID"},type:{type:"string",enum:["DEBIT","CREDIT"]},amount:{type:"number",description:"Amount, required"},description:{type:"string"},taxProfileResourceId:{type:"string"},exchangeRate:{type:"number",description:"Line rate, account currency to base; applied as sent."}},required:["accountResourceId","type"]}},reference:{type:"string"},valueDate:{type:"string",description:"YYYY-MM-DD. Defaults to bank entry value date."},contactResourceId:{type:"string"},internalNotes:{type:"string"},bankAccountJournalEntryDescription:{type:"string"},taxVatApplicable:{type:"boolean"},taxInclusion:{type:"boolean"},tags:{type:"array",items:{type:"string"}},capsuleResourceId:{type:"string"}},required:["bankStatementEntryResourceId","bankAccountResourceId","status","journalEntries"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry manual journal double entry",execute:async(e,t)=>(Ut(t.journalEntries,"journalEntries",200,"entries"),cV(e.client,t))},{name:"reconcile_cash_transfer",description:"Reconcile a bank statement entry with an inter-account transfer. Sync: returns the reconciled entry status. amount is required only when the counterparty account is in a non-functional currency. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.",params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID"},accountResourceId:{type:"string",description:"Counterparty cash account UUID"},reference:{type:"string",description:"Transfer reference"},amount:{type:"number",description:"Amount in source currency. Required only for cross-currency transfers."},contactResourceId:{type:"string"},internalNotes:{type:"string"},tags:{type:"array",items:{type:"string"}},capsuleResourceId:{type:"string"}},required:["bankStatementEntryResourceId","bankAccountResourceId","accountResourceId","reference"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry transfer between accounts",execute:async(e,t)=>lV(e.client,t)},{name:"reconcile_invoice_receipt",description:'CREATES A NEW invoice and auto-reconciles it to a bank statement entry (AR). To match an EXISTING open invoice instead, use reconcile_with_payments (do not create a duplicate). \u26A0\uFE0F BSE must have `paymentDirection: PAYIN` (money in, AR). Produce via `bank add-records` with a POSITIVE amount (creates `credit_amount > 0` \u2192 PAYIN). Statement-imported BSEs from `bank import` also work. The error code "Invalid business transaction type" (422) is misleadingly named; the actual gate is direction, not entry type. Sync: returns the reconciled entry status. valueDate / dueDate / payment amount prefill from the bank entry if omitted. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.',params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID"},invoiceDetails:Jnt("Invoice","Customer","Revenue")},required:["bankStatementEntryResourceId","bankAccountResourceId","invoiceDetails"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry invoice receipt AR sale",execute:async(e,t)=>(ipe(t.invoiceDetails,"invoiceDetails"),uV(e.client,t))},{name:"reconcile_bill_receipt",description:'CREATES A NEW bill and auto-reconciles it to a bank statement entry (AP). To match an EXISTING open bill instead, use reconcile_with_payments (do not create a duplicate). \u26A0\uFE0F BSE must have `paymentDirection: PAYOUT` (money out, AP). Produce via `bank add-records` with a NEGATIVE amount (creates `debit_amount > 0` \u2192 PAYOUT). Statement-imported BSEs from `bank import` also work. The error code "Invalid business transaction type" (422) is misleadingly named; the actual gate is direction, not entry type. Sync: returns the reconciled entry status. valueDate / dueDate / payment amount prefill from the bank entry if omitted. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.',params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID"},billDetails:Jnt("Bill","Supplier","Expense")},required:["bankStatementEntryResourceId","bankAccountResourceId","billDetails"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry bill receipt AP purchase",execute:async(e,t)=>(ipe(t.billDetails,"billDetails"),dV(e.client,t))},{name:"reconcile_with_payments",description:"USE THIS to match a bank statement entry to an EXISTING open bill / invoice / payment (NOT reconcile_bill_receipt or reconcile_invoice_receipt, which CREATE a new bill/invoice). Pass the open transaction's cashflowTransactionResourceId (from search_cashflow_transactions or a view_auto_reconciliation suggestion) in businessTransactionPayments[]: this CREATES the payment AND reconciles in one call; no pay_bill / pay_invoice first. FX is resolved server-side; pass NO rate for the common case (only supply paymentAmount + currencySettings for the rare bill-currency \u2260 bank-currency case). Sync: returns the reconciled entry status. NOT idempotent: a blind retry double-creates a payment; confirm reconciled-state via search_bank_records(status: RECONCILED) before retrying.",params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},businessTransactionPayments:{type:"array",description:"Open business transactions (bills/invoices) to reconcile by creating a payment against each.",items:{type:"object",properties:{cashflowTransactionResourceId:{type:"string",description:"Open BT cashflow UUID (the bill/invoice)"},transactionAmount:{type:"number",description:"Payment amount in the BT currency (>0)"},paymentAmount:{type:"number",description:"Actual amount in bank-statement currency. Required ONLY when bank currency \u2260 BT currency."},currencySettings:{type:"object",description:"FX settings: supply ONLY for cross-currency (bank ccy \u2260 BT ccy). Omit otherwise (server resolves).",properties:{sourceCurrencyCode:{type:"string",description:"BT currency (ISO 4217)"},rateFunctionalToSource:{type:"number"},exchangeToken:{type:"string"}},required:["sourceCurrencyCode"]}},required:["cashflowTransactionResourceId","transactionAmount"]}},matchedPayments:{type:"array",items:{type:"string"},description:"Existing payment UUIDs to reconcile against the entry"},matchedBatchPayments:{type:"array",items:{type:"string"},description:"Existing batch payment UUIDs to reconcile against the entry"},adjustment:{type:"object",description:"Optional adjustment leg to balance payments vs the statement amount (over/under-payment, rounding, or FX gain/loss write-off; the platform does NOT auto-post FX gain/loss).",properties:{reference:{type:"string"},contactResourceId:{type:"string"},tags:{type:"array",items:{type:"string"}},cashAdjustmentEntries:{type:"array",items:{type:"object",properties:{organizationAccountResourceId:{type:"string",description:"e.g. the FX gain/loss account UUID"},amount:{type:"number"},description:{type:"string"},taxProfileResourceId:{type:"string"},rateSourceToFunctional:{type:"number",description:"Required when this account is in a non-functional currency."}},required:["amount"]}},bankAccountJournalEntryRateFunctionalToSource:{type:"number",description:"Required when the bank account is in a non-functional currency."}}}},required:["bankStatementEntryResourceId"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"match bank entry to existing open bill invoice payment reconcile with payments pay outstanding",execute:async(e,t)=>(lnt(t),fV(e.client,t))},{name:"reconcile_magic_match",description:"Bulk-accept MAGIC_MATCH suggestions: reconcile many bank entries against EXISTING open business transactions and/or payments in one call (max 500 entries). Feed entries from view_auto_reconciliation suggestions. Returns a per-entry split { reconciled[], failed[] }: a 200 with a non-empty failed[] is a PARTIAL success; surface failed entries (with their errorCode) and loop. Entry-level idempotency-keyed server-side. Use reconcile_with_payments for a single precise match; use this to accept a batch of suggestions at once.",params:{bankAccountResourceId:{type:"string",description:"Bank account UUID the entries belong to"},autoReconcileRunResourceId:{type:"string",description:"Auto-reconciliation run UUID for batch tracking"},entries:{type:"array",description:"One row per bank entry to reconcile (max 500).",items:{type:"object",properties:{workflowType:{type:"string",enum:["MAGIC_MATCH"],description:"Only MAGIC_MATCH (match to existing) is supported here."},bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},matchedBusinessTransactions:{type:"array",items:{type:"string"},description:"Open BT cashflow UUIDs to reconcile by creating payments"},matchedPayments:{type:"array",items:{type:"string"},description:"Existing payment UUIDs"},matchedBatchResourceId:{type:"string",description:"Batch payment UUID"}},required:["bankStatementEntryResourceId","workflowType"]}}},required:["bankAccountResourceId","entries"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"bulk accept magic match suggestions reconcile many bank entries to existing",execute:async(e,t)=>(Ut(t.entries,"entries",500,"entries"),hV(e.client,t))},{name:"reconcile_learned_prediction",description:"Accept an ML learned-prediction suggestion to reconcile a bank entry. predictedPayload + predictedPayloadSchemaVersion are passed through VERBATIM from a view_auto_reconciliation suggestion (recommendationType MAGIC_RECONCILE_WITH_CASH_IN_OUT); do not construct them by hand. Pass retryToken to force a fresh journal on an edit-then-retry; omit it for an idempotent replay. Sync: returns the reconciled entry status. On failure (stale/invalid payload), fall back to reconcile_with_payments; do NOT retry the same opaque payload.",params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},learnedPredictionResourceId:{type:"string",description:"Learned-prediction UUID from the suggestion"},predictedPayload:{type:"string",description:"Opaque JSON payload from the suggestion; required, passed verbatim"},predictedPayloadSchemaVersion:{type:"string",description:"Schema version from the suggestion"},retryToken:{type:"string",description:"Optional idempotency retry token"},autoReconcileRunResourceId:{type:"string",description:"Auto-reconciliation run UUID for batch tracking"}},required:["bankStatementEntryResourceId","learnedPredictionResourceId","predictedPayload","predictedPayloadSchemaVersion"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry learned prediction ML cash in out accept suggestion",execute:async(e,t)=>pV(e.client,t)},{name:"undo_reconciliations",description:"Unlink bank entries from what they were reconciled against, returning each to UNRECONCILED. Re-running reports already-unreconciled/archived entries as per-entry FAILED (INVALID_BANK_STATEMENT_ENTRY_STATUS); it never double-applies. On a non-200 nothing was applied, the batch rolls back. Ids that do not resolve are OMITTED from resetReconciliationResponse, so compare returned ids against what you sent. Does NOT delete the record it was matched to; reconcile_* tools that CREATE one leave it on the books as a bank-rec discrepancy, and re-reconciling without deleting it duplicates the record. Delete via linkedRecords by businessTransactionType: SALE=delete_invoice, PURCHASE=delete_bill, SALE_CREDIT_NOTE=delete_customer_credit_note, PURCHASE_CREDIT_NOTE=delete_supplier_credit_note, PAYMENT_*=delete_payment, JOURNAL_MANUAL=delete_journal (voids an ACTIVE); all using businessTransactionResourceId. JOURNAL_DIRECT_CASH_IN/OUT and JOURNAL_CASH_TRANSFER use delete_cash_in/out/transfer with parentEntityResourceId instead. Any other type has no delete tool: locate it with search_cashflow_transactions first.",params:{bankStatementEntryResourceIds:{type:"array",items:{type:"string"},description:"Bank entry UUIDs to unlink. 1-500, unique."}},required:["bankStatementEntryResourceIds"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"undo unreconcile reset reverse reconciliation unlink bank entry mistake wrong match",execute:async(e,t)=>{Ut(t.bankStatementEntryResourceIds,"bankStatementEntryResourceIds",500,"bank entries");let r=[...new Set(t.bankStatementEntryResourceIds)];return mV(e.client,{...t,bankStatementEntryResourceIds:r})}},{name:"validate_drafts",description:`BULK validate up to 500 draft business transactions in ONE call, mixing any btType. SYNC: returns per-item errors + display data; no state change. Run before convert_drafts_to_active.
826
-
827
- btType enum: SALE = invoice, PURCHASE = bill, SALE_CREDIT_NOTE = customer CN, PURCHASE_CREDIT_NOTE = supplier CN. Journals NOT in this enum.
823
+ ASYNC: returns jobId \u2192 poll search_background_jobs(filter:{resourceId:{eq:jobId}}); on PARTIAL_SUCCESS read data[0].errorDetails.`,params:{fixedAssets:{type:"array",description:"Fixed assets to create/update (max 500).",items:{type:"object",properties:{rowIndex:{type:"string"},reference:{type:"string",description:"Natural key"},registrationType:{type:"string",enum:["NEW","TRANSFER"],description:"NEW = newly acquired, TRANSFER = inherited; required"},typeCode:{type:"string",description:"Asset class code (e.g. FURNITURE_AND_FIXTURE)"},typeName:{type:"string",description:"Human label for typeCode"},category:{type:"string",enum:["TANGIBLE","INTANGIBLE"],description:"TANGIBLE | INTANGIBLE"},name:{type:"string",description:"Asset name"},purchaseAmount:{type:"number",description:"Acquisition cost."},purchaseDate:{type:"number",description:"Acquisition date, epoch ms (NOT YYYY-MM-DD on this endpoint)."},depreciationStartDate:{type:"number",description:"Epoch ms. Omit to default to purchaseDate."},effectiveLife:{type:"number",description:"Useful life months."},depreciationMethod:{type:"string",enum:["STRAIGHT_LINE","NO_DEPRECIATION"],description:"Bulk accepts only STRAIGHT_LINE or NO_DEPRECIATION (422 otherwise); use create_fixed_asset for declining-balance methods."},purchaseAssetAccountResourceId:{type:"string",description:"Asset (PPE) account UUIDv4."},depreciationExpenseAccountResourceId:{type:"string",description:"Depreciation expense account UUIDv4."},accumulatedDepreciationAccountResourceId:{type:"string",description:"Accumulated depreciation account UUIDv4."},purchaseBusinessTransactionType:{type:"string",enum:[...jy],description:"Required for NEW."},purchaseBusinessTransactionResourceId:{type:"string",description:"Purchase LINE ITEM id, unlinked. Required for NEW."},accumulatedDepreciationAtTransferDate:{type:"number",description:"TRANSFER only."},bookValueAtTransferDate:{type:"number",description:"TRANSFER only."},internalNotes:{type:"string"}},required:["reference"]}}},required:["fixedAssets"],group:"fixed_assets",readOnly:!1,isDestructive:!0,searchHint:"bulk fixed assets ppe register upsert import migrate",execute:async(e,t)=>{Ut(t.fixedAssets,"fixedAssets",500,"fixed assets");let r=t.fixedAssets;return ORe(r,["purchaseDate","depreciationStartDate"]),iG(e.client,r)}},{name:"quick_reconcile",description:`Bulk-reconcile bank statement entries against a list of journals (max 500). Reconciliation mode is hardcoded to QUICK_RECON. ${ql} Caller provides bankAccountResourceId + journalsForReconciliation[] (each row: bankStatementEntryResourceId, journalReference, journalAccountResourceId).`,params:{bankAccountResourceId:{type:"string",description:"Bank account UUID (the entries belong to this account)"},journalsForReconciliation:{type:"array",description:"Per-row reconciliation instructions (max 500).",items:{type:"object",properties:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},journalReference:{type:"string",description:"Journal reference text"},journalAccountResourceId:{type:"string",description:"Offsetting account UUID (revenue for cash-in, expense for cash-out)"},contactResourceId:{type:"string",description:"Contact UUID"},capsuleResourceId:{type:"string",description:"Capsule UUID"},taxProfileResourceId:{type:"string",description:"Tax profile UUID"},internalNotes:{type:"string"},journalEntryDescription:{type:"string"},tags:{type:"array",items:{type:"string"}},rowIndex:{type:"string",description:"Optional caller-supplied index for error correlation"}},required:["bankStatementEntryResourceId","journalAccountResourceId","journalReference"]}}},required:["bankAccountResourceId","journalsForReconciliation"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"bulk reconcile bank statement journals match",execute:async(e,t)=>(Ut(t.journalsForReconciliation,"journalsForReconciliation",500,"journals"),iV(e.client,t))},{name:"apply_bank_rule",description:"Reconcile bank statement entries (max 500) with one saved bank rule: each entry gets the cash-in or cash-out entry the rule defines (its accounts and amount split). Async: returns a jobId; poll search_background_jobs filtered by resourceId.",params:{actionShortcutResourceId:{type:"string",description:"Saved bank rule resourceId"},businessTransactionResourceIds:{type:"array",description:"Bank statement entry resourceIds (max 500).",items:{type:"string"}}},required:["actionShortcutResourceId","businessTransactionResourceIds"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"bank rule apply action shortcut bulk reconcile",execute:async(e,t)=>(Ut(t.businessTransactionResourceIds,"businessTransactionResourceIds",500,"entries"),oV(e.client,t))},{name:"reconcile_direct_cash_entry",description:"Reconcile a bank statement entry with a single cash-in / cash-out line. Direction (cash-in vs cash-out) is INFERRED from the bank entry sign. Sync: returns the reconciled entry status. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.",params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID (used to look up the entry)"},reference:{type:"string",description:"Reference for the cash entry"},accountResourceId:{type:"string",description:"Offsetting account UUID (revenue for cash-in, expense for cash-out)"},amount:{type:"number",description:"Amount in source currency; defaults to and must EQUAL the entry amount; a partial allocation 422s (Total journal entries amount mismatch). Split an entry via reconcile_cash_journal instead."},description:{type:"string"},contactResourceId:{type:"string"},taxProfileResourceId:{type:"string"},internalNotes:{type:"string"},bankAccountJournalEntryDescription:{type:"string",description:"Description for the bank-side journal entry"},tags:{type:"array",items:{type:"string"}},taxVatApplicable:{type:"boolean"},taxInclusion:{type:"boolean"},capsuleResourceId:{type:"string"}},required:["bankStatementEntryResourceId","bankAccountResourceId","reference","accountResourceId"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry direct cash in out single line",execute:async(e,t)=>sV(e.client,t)},{name:"reconcile_cash_journal",description:"Reconcile a bank statement entry with a multi-line cashflow journal (multiple cash splits, max 200 lines). Sync: returns the reconciled entry status. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.",params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID"},reference:{type:"string",description:"Journal reference"},journalEntries:{type:"array",description:"Cash journal lines (max 200). Each row is one debit OR credit against an account.",items:{type:"object",properties:{accountResourceId:{type:"string",description:"Account UUID"},amount:{type:"number",description:"Amount in source currency (>0)"},description:{type:"string"},taxProfileResourceId:{type:"string"}},required:["accountResourceId","amount"]}},contactResourceId:{type:"string"},bankAccountJournalEntryDescription:{type:"string"},internalNotes:{type:"string"},tags:{type:"array",items:{type:"string"}},taxVatApplicable:{type:"boolean"},taxInclusion:{type:"boolean"},capsuleResourceId:{type:"string"}},required:["bankStatementEntryResourceId","bankAccountResourceId","reference","journalEntries"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry multi line cash journal splits",execute:async(e,t)=>(Ut(t.journalEntries,"journalEntries",200,"entries"),aV(e.client,t))},{name:"reconcile_manual_journal",description:'\u26A0\uFE0F CALLER PROVIDES ONLY THE OFFSET LEG(S); backend AUTO-ADDS the bank-side leg from the statement entry. Sending both debit AND credit legs \u2192 422 "sum of debit and credit amounts are not equal" (your sides duplicate after the auto-add). Reconcile a bank statement entry with a double-entry manual journal. Sync: returns the reconciled entry status. valueDate prefills from bank entry if omitted. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.',params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID"},status:{type:"string",enum:["DRAFT","ACTIVE"],description:"Journal status"},journalEntries:{type:"array",description:"Offset-side journal entries only (max 200). The bank-side leg is auto-added by the API.",items:{type:"object",properties:{accountResourceId:{type:"string",description:"Account UUID"},type:{type:"string",enum:["DEBIT","CREDIT"]},amount:{type:"number",description:"Amount, required"},description:{type:"string"},taxProfileResourceId:{type:"string"},exchangeRate:{type:"number",description:"Line rate, account currency to base; applied as sent."}},required:["accountResourceId","type"]}},reference:{type:"string"},valueDate:{type:"string",description:"YYYY-MM-DD. Defaults to bank entry value date."},contactResourceId:{type:"string"},internalNotes:{type:"string"},bankAccountJournalEntryDescription:{type:"string"},taxVatApplicable:{type:"boolean"},taxInclusion:{type:"boolean"},tags:{type:"array",items:{type:"string"}},capsuleResourceId:{type:"string"}},required:["bankStatementEntryResourceId","bankAccountResourceId","status","journalEntries"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry manual journal double entry",execute:async(e,t)=>(Ut(t.journalEntries,"journalEntries",200,"entries"),cV(e.client,t))},{name:"reconcile_cash_transfer",description:"Reconcile a bank statement entry with an inter-account transfer. Sync: returns the reconciled entry status. amount is required only when the counterparty account is in a non-functional currency. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.",params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID"},accountResourceId:{type:"string",description:"Counterparty cash account UUID"},reference:{type:"string",description:"Transfer reference"},amount:{type:"number",description:"Amount in source currency. Required only for cross-currency transfers."},contactResourceId:{type:"string"},internalNotes:{type:"string"},tags:{type:"array",items:{type:"string"}},capsuleResourceId:{type:"string"}},required:["bankStatementEntryResourceId","bankAccountResourceId","accountResourceId","reference"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry transfer between accounts",execute:async(e,t)=>lV(e.client,t)},{name:"reconcile_invoice_receipt",description:'CREATES A NEW invoice and auto-reconciles it to a bank statement entry (AR). To match an EXISTING open invoice instead, use reconcile_with_payments (do not create a duplicate). \u26A0\uFE0F BSE must have `paymentDirection: PAYIN` (money in, AR). Produce via `bank add-records` with a POSITIVE amount (creates `credit_amount > 0` \u2192 PAYIN). Statement-imported BSEs from `bank import` also work. The error code "Invalid business transaction type" (422) is misleadingly named; the actual gate is direction, not entry type. Sync: returns the reconciled entry status. valueDate / dueDate / payment amount prefill from the bank entry if omitted. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.',params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID"},invoiceDetails:Jnt("Invoice","Customer","Revenue")},required:["bankStatementEntryResourceId","bankAccountResourceId","invoiceDetails"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry invoice receipt AR sale",execute:async(e,t)=>(ipe(t.invoiceDetails,"invoiceDetails"),uV(e.client,t))},{name:"reconcile_bill_receipt",description:'CREATES A NEW bill and auto-reconciles it to a bank statement entry (AP). To match an EXISTING open bill instead, use reconcile_with_payments (do not create a duplicate). \u26A0\uFE0F BSE must have `paymentDirection: PAYOUT` (money out, AP). Produce via `bank add-records` with a NEGATIVE amount (creates `debit_amount > 0` \u2192 PAYOUT). Statement-imported BSEs from `bank import` also work. The error code "Invalid business transaction type" (422) is misleadingly named; the actual gate is direction, not entry type. Sync: returns the reconciled entry status. valueDate / dueDate / payment amount prefill from the bank entry if omitted. Retrying a RECONCILED entry 422s (Invalid bank statement entry status); it does not duplicate.',params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},bankAccountResourceId:{type:"string",description:"Bank account UUID"},billDetails:Jnt("Bill","Supplier","Expense")},required:["bankStatementEntryResourceId","bankAccountResourceId","billDetails"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry bill receipt AP purchase",execute:async(e,t)=>(ipe(t.billDetails,"billDetails"),dV(e.client,t))},{name:"reconcile_with_payments",description:"USE THIS to match a bank statement entry to an EXISTING open bill / invoice / payment (NOT reconcile_bill_receipt or reconcile_invoice_receipt, which CREATE a new bill/invoice). Pass the open transaction's cashflowTransactionResourceId (from search_cashflow_transactions or a view_auto_reconciliation suggestion) in businessTransactionPayments[]: this CREATES the payment AND reconciles in one call; no pay_bill / pay_invoice first. FX is resolved server-side; pass NO rate for the common case (only supply paymentAmount + currencySettings for the rare bill-currency \u2260 bank-currency case). Sync: returns the reconciled entry status. NOT idempotent: a blind retry double-creates a payment; confirm reconciled-state via search_bank_records(status: RECONCILED) before retrying.",params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},businessTransactionPayments:{type:"array",description:"Open business transactions (bills/invoices) to reconcile by creating a payment against each.",items:{type:"object",properties:{cashflowTransactionResourceId:{type:"string",description:"Open BT cashflow UUID (the bill/invoice)"},transactionAmount:{type:"number",description:"Payment amount in the BT currency (>0)"},paymentAmount:{type:"number",description:"Actual amount in bank-statement currency. Required ONLY when bank currency \u2260 BT currency."},currencySettings:{type:"object",description:"FX settings: supply ONLY for cross-currency (bank ccy \u2260 BT ccy). Omit otherwise (server resolves).",properties:{sourceCurrencyCode:{type:"string",description:"BT currency (ISO 4217)"},rateFunctionalToSource:{type:"number"},exchangeToken:{type:"string"}},required:["sourceCurrencyCode"]}},required:["cashflowTransactionResourceId","transactionAmount"]}},matchedPayments:{type:"array",items:{type:"string"},description:"Existing payment UUIDs to reconcile against the entry"},matchedBatchPayments:{type:"array",items:{type:"string"},description:"Existing batch payment UUIDs to reconcile against the entry"},adjustment:{type:"object",description:"Optional adjustment leg to balance payments vs the statement amount (over/under-payment, rounding, or FX gain/loss write-off; the platform does NOT auto-post FX gain/loss).",properties:{reference:{type:"string"},contactResourceId:{type:"string"},tags:{type:"array",items:{type:"string"}},cashAdjustmentEntries:{type:"array",items:{type:"object",properties:{organizationAccountResourceId:{type:"string",description:"e.g. the FX gain/loss account UUID"},amount:{type:"number"},description:{type:"string"},taxProfileResourceId:{type:"string"},rateSourceToFunctional:{type:"number",description:"Required when this account is in a non-functional currency."}},required:["amount"]}},bankAccountJournalEntryRateFunctionalToSource:{type:"number",description:"Required when the bank account is in a non-functional currency."}}}},required:["bankStatementEntryResourceId"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"match bank entry to existing open bill invoice payment reconcile with payments pay outstanding",execute:async(e,t)=>(lnt(t),fV(e.client,t))},{name:"reconcile_magic_match",description:"Bulk-accept MAGIC_MATCH suggestions: reconcile many bank entries against EXISTING open business transactions and/or payments in one call (max 500 entries). Feed entries from view_auto_reconciliation suggestions. Returns a per-entry split { reconciled[], failed[] }: a 200 with a non-empty failed[] is a PARTIAL success; surface failed entries (with their errorCode) and loop. Entry-level idempotency-keyed server-side. Use reconcile_with_payments for a single precise match; use this to accept a batch of suggestions at once.",params:{bankAccountResourceId:{type:"string",description:"Bank account UUID the entries belong to"},autoReconcileRunResourceId:{type:"string",description:"Auto-reconciliation run UUID for batch tracking"},entries:{type:"array",description:"One row per bank entry to reconcile (max 500).",items:{type:"object",properties:{workflowType:{type:"string",enum:["MAGIC_MATCH"],description:"Only MAGIC_MATCH (match to existing) is supported here."},bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},matchedBusinessTransactions:{type:"array",items:{type:"string"},description:"Open BT cashflow UUIDs to reconcile by creating payments"},matchedPayments:{type:"array",items:{type:"string"},description:"Existing payment UUIDs"},matchedBatchResourceId:{type:"string",description:"Batch payment UUID"}},required:["bankStatementEntryResourceId","workflowType"]}}},required:["bankAccountResourceId","entries"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"bulk accept magic match suggestions reconcile many bank entries to existing",execute:async(e,t)=>(Ut(t.entries,"entries",500,"entries"),hV(e.client,t))},{name:"reconcile_learned_prediction",description:"Accept an ML learned-prediction suggestion to reconcile a bank entry. predictedPayload + predictedPayloadSchemaVersion are passed through VERBATIM from a view_auto_reconciliation suggestion (recommendationType MAGIC_RECONCILE_WITH_CASH_IN_OUT); do not construct them by hand. Pass retryToken to force a fresh journal on an edit-then-retry; omit it for an idempotent replay. Sync: returns the reconciled entry status. On failure (stale/invalid payload), fall back to reconcile_with_payments; do NOT retry the same opaque payload.",params:{bankStatementEntryResourceId:{type:"string",description:"Bank entry UUID"},learnedPredictionResourceId:{type:"string",description:"Learned-prediction UUID from the suggestion"},predictedPayload:{type:"string",description:"Opaque JSON payload from the suggestion; required, passed verbatim"},predictedPayloadSchemaVersion:{type:"string",description:"Schema version from the suggestion"},retryToken:{type:"string",description:"Optional idempotency retry token"},autoReconcileRunResourceId:{type:"string",description:"Auto-reconciliation run UUID for batch tracking"}},required:["bankStatementEntryResourceId","learnedPredictionResourceId","predictedPayload","predictedPayloadSchemaVersion"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"reconcile bank entry learned prediction ML cash in out accept suggestion",execute:async(e,t)=>pV(e.client,t)},{name:"undo_reconciliations",description:"Unlink bank entries from what they were reconciled against, returning each to UNRECONCILED. Re-running reports already-unreconciled/archived entries as per-entry FAILED (INVALID_BANK_STATEMENT_ENTRY_STATUS); it never double-applies. On a non-200 nothing was applied, the batch rolls back. Ids that do not resolve are OMITTED from resetReconciliationResponse, so compare returned ids against what you sent. Does NOT delete the record it was matched to; reconcile_* tools that CREATE one leave it on the books as a bank-rec discrepancy, and re-reconciling without deleting it duplicates the record. Delete via linkedRecords by businessTransactionType: SALE=delete_invoice, PURCHASE=delete_bill, SALE_CREDIT_NOTE=delete_customer_credit_note, PURCHASE_CREDIT_NOTE=delete_supplier_credit_note, PAYMENT_*=delete_payment, JOURNAL_MANUAL=delete_journal (voids an ACTIVE); all using businessTransactionResourceId. JOURNAL_DIRECT_CASH_IN/OUT and JOURNAL_CASH_TRANSFER use delete_cash_in/out/transfer with parentEntityResourceId instead. Any other type has no delete tool: locate it with search_cashflow_transactions first.",params:{bankStatementEntryResourceIds:{type:"array",items:{type:"string"},description:"Bank entry UUIDs to unlink. 1-500, unique."}},required:["bankStatementEntryResourceIds"],group:"reconciliations",readOnly:!1,isDestructive:!0,searchHint:"undo unreconcile reset reverse reconciliation unlink bank entry mistake wrong match",execute:async(e,t)=>{Ut(t.bankStatementEntryResourceIds,"bankStatementEntryResourceIds",500,"bank entries");let r=[...new Set(t.bankStatementEntryResourceIds)];return mV(e.client,{...t,bankStatementEntryResourceIds:r})}},{name:"validate_drafts",description:`BULK check up to 500 drafts in ONE call, any mix of btType (SALE = invoice, PURCHASE = bill, SALE_CREDIT_NOTE, PURCHASE_CREDIT_NOTE; not journals). SYNC, no state change. Run before convert_drafts_to_active.
828
824
 
829
- Each result carries contactSignals (Mid-7: cadence, outliers, severity, divergences from contact's modal pattern; null if no contact / no history) and breakdown (Balance-panel payload: line items + subtotal/tax/total/balance/exchange rate). Top-level contactSignalsMeta.unavailable=true \u2192 freshness layer offline for whole batch.
825
+ Each result: eligible + errors[] (what blocks conversion), breakdown (lines, subtotal, shipping, tax, total, balance, exchange rate, totalInFunctional) and contactSignals, the draft vs the contact's recent posted transactions of the same type:
826
+ - severitySummary.dominantLabel + tone (RED probably wrong, YELLOW unusual, BLUE nothing found, GRAY could not check): POSSIBLE_DUPLICATE, NEW_CURRENCY, ABOVE_USUAL / BELOW_USUAL (outside 1.5x the contact's range), NEW_TERMS, TAX_TYPE, NEW_GST, NEW_COA / NEW_ITEM (account or item the contact never used), NEW (<5 compared), NORMAL, NO_ALERTS.
827
+ - Evidence: duplicateFlags (the twin; RED same supplier reference, YELLOW same amount + date), outlierFlags (previous min / max), revealedDivergences (line number, names).
828
+ contactSignals is null when the draft is ineligible, has no contact, or contactSignalsMeta.unavailable.
830
829
 
831
- USE THIS to score a specific draft against contact history. For stand-alone "what does this contact normally look like?" without a draft \u2192 use get_contact_signals.`,params:{items:ype},required:["items"],group:"drafts",readOnly:!0,isConcurrencySafe:!0,searchHint:"validate drafts eligibility check before convert promote",execute:async(e,t)=>(h_(t.items),WG(e.client,t.items))},{name:"convert_drafts_to_active",description:`BULK promote: finalize up to 500 draft business transactions to ACTIVE in ONE call, mixing any combination of invoices, bills, and credit notes (no per-type tools needed). ${ql} NOT idempotent: a second call on already-promoted drafts returns 422. Filter the draft list by status: DRAFT before submitting. Pair with validate_drafts for a pre-flight check on large batches.`,params:{items:ype},required:["items"],group:"drafts",readOnly:!1,isDestructive:!0,searchHint:"convert drafts to active promote finalize bulk",execute:async(e,t)=>(h_(t.items),ZG(e.client,t.items))},{name:"submit_drafts_for_approval",description:`BULK submit: route up to 500 draft business transactions into the approval workflow in ONE call, mixing any combination of invoices, bills, and credit notes. ${ql} NOT idempotent on drafts that already have an in-flight approval request.`,params:{items:ype},required:["items"],group:"drafts",readOnly:!1,isDestructive:!0,searchHint:"submit drafts for approval workflow review bulk",execute:async(e,t)=>(h_(t.items),KG(e.client,t.items))},{name:"search_help_center",description:'Search the Jaz help center for how-to articles, feature guides, and accounting concepts. Use this for "how do I..." or "what is..." questions about Jaz: bank reconciliation, GST/VAT filing, multi-currency, fixed assets, custom fields, integrations, and so on. Returns top matching articles with title, section, content, and source URL.',params:{query:{type:"string",description:'Natural-language question or keywords (e.g. "how do I reconcile a bank statement", "set up GST", "multi-currency invoice").'},limit:{type:"number",description:"Maximum number of articles to return (default 3, max 10)."},section:{type:"string",description:'Optional: restrict to a single help-center section slug (e.g. "bank-reconciliations", "invoices").'}},required:["query"],group:"help_center",readOnly:!0,searchHint:"help center docs how to guide article search question",isConcurrencySafe:!0,maxResultSizeChars:2e4,execute:async(e,t)=>{let r=typeof t.limit=="number"?t.limit:void 0,n=typeof t.section=="string"?t.section:void 0;return Afe(String(t.query??""),{limit:r,section:n})}},{name:"jot",description:"Record judgment entries in the org's judgment journal. THE RULE: log a judgment when you chose among real alternatives and a write followed, or when you deliberately decided NOT to write; skip mechanical actions and calls where the platform, the data, or the user left you no choice. Jot AFTER the write succeeds, never before, and carry the written record's resourceId in refs. Kinds: CLASSIFICATION account/tax/category choice \xB7 MATCH decided which records correspond \xB7 SCOPE chose what is in or out of a set \xB7 ASSUMPTION filled a gap the data left open \xB7 RISK proceeded despite a known concern \xB7 METHOD picked a calculation or approach \xB7 RECOVERY decided how to proceed after a failure (incl. holding a write) \xB7 DEVIATION knowingly departed from a stated rule. Accepting or rejecting an inbound external document (an e-invoice a counterparty sent) is a FINALIZE act, so pin it. A decided omission (you deliberately did not post something) is RECOVERY, which pins via withheld-write, not DEVIATION. Always give why: a call without a basis is half a record. Batch related entries in one call; each entry is acked independently. "+OP+" "+qke,params:{entries:{type:"array",description:"Judgment entries to record (1-100). A bad entry never fails the batch: it acks with error while siblings persist.",items:{type:"object",properties:{kind:{type:"string",enum:["CLASSIFICATION","MATCH","SCOPE","ASSUMPTION","RISK","METHOD","RECOVERY","DEVIATION","NOTE"],description:"Judgment kind: one-liners in the tool description. "+BP},tier:{type:"string",enum:["LOW","MEDIUM","HIGH","CRITICAL"],description:"Severity you assign the call. "+xP},call:{type:"string",description:'The decision, one line (e.g. "Posted to 6420 Travel: recurring Grab vendor pattern").'},why:{type:"string",description:"The basis for the call: a call without a basis is half a record."},ruledOut:{type:"string",description:"Alternatives considered and set aside."},refs:{type:"array",description:"Entity refs this judgment touches. Include the written record's resourceId.",items:{type:"string",description:'Grammar TYPE:resourceId[#field][:RELATION], e.g. "INVOICE:41f6\u2026:CREATE". RELATION: CREATE UPDATE DELETE FINALIZE PAY RECONCILE TRIGGER SUBJECT INPUT REJECTED SUPERSEDES (default SUBJECT).'}},frame:{type:"string",description:'For set-shaped subjects: the defining query + counts (e.g. "search_bills status=UNPAID \xB7 41 in scope, 4 deferred").'},confidence:{type:"string",enum:["LOW","MEDIUM","HIGH"],description:"Routing hint for review attention. "+kP},citedRule:{type:"string",description:"Org rule, policy, threshold, or precedent applied."},workflowLabel:{type:"string",description:NP},agentLabel:{type:"string",description:"Self-reported product/model (e.g. jaz-clio/claude); informative, never authoritative."},idempotencyKey:{type:"string",description:"Retry key: a replay returns the existing entry, not a duplicate."}},required:["kind","tier","call"]}}},required:["entries"],group:"jots",readOnly:!1,alwaysLoaded:!0,searchHint:"jot judgment judgement journal record decision log rationale reasoning why chose ruled out classification match scope assumption risk method recovery deviation basis precedent note after write",execute:async(e,t)=>i_(e.client,t.entries)},{name:"recall",description:"Search the org's judgment journal (jots) for precedents, prior judgment calls with their basis. Check BEFORE repeating a judgment on the same entity, kind of decision, or workflow: a prior call with its why beats re-deriving from scratch; a FLAG or REJECT disposition on it is a warning, ENDORSE a strong precedent. Sort is pinned server-side (CRITICAL and withheld-write RECOVERY entries first, then newest). An empty page means no prior judgments match this filter, or none are recorded for this org yet.",params:{ref:{type:"string",description:'Entity ref filter, TYPE:resourceId (e.g. "INVOICE:41f626a3-\u2026").'},kind:{type:"string",enum:["CLASSIFICATION","MATCH","SCOPE","ASSUMPTION","RISK","METHOD","RECOVERY","DEVIATION","NOTE"],description:"Judgment kind filter."},tier:{type:"string",enum:["LOW","MEDIUM","HIGH","CRITICAL"],description:"Severity tier filter."},dispositionVerb:{type:"string",enum:["FLAG","REJECT","ENDORSE"],description:"Only entries carrying at least one disposition with this verb."},freetext:{type:"string",description:"Case-insensitive substring over call and why."},createdFrom:{type:"number",description:"Created-at lower bound, epoch ms (inclusive)."},createdTo:{type:"number",description:"Created-at upper bound, epoch ms (inclusive)."},firstParty:{type:"boolean",description:"true = only entries from Jaz-operated agent surfaces (server-stamped origin, never self-reported); false = only external and pre-stamp entries. Omit for all."},pinned:{type:"boolean",description:"true = only pinned entries (critical or withheld-write, the same predicate the pinned sort uses); false = only unpinned. Omit for all."},limit:{type:"number",description:"Max results per page (1-1000, default 100)."},offset:{type:"number",description:"Page number, 0-indexed."},includeStats:{type:"boolean",description:"Include per-credential disposition counts."}},required:[],group:"jots",readOnly:!0,isConcurrencySafe:!0,searchHint:"recall search jots judgment judgement journal precedent prior decisions rationale history dispositions flagged rejected endorsed first party jaz agents pinned lookup why",maxResultSizeChars:2e4,execute:async(e,t)=>{let r={};for(let n of["ref","kind","tier","dispositionVerb","freetext"])typeof t[n]=="string"&&t[n]&&(r[n]=t[n]);for(let n of["createdFrom","createdTo"])typeof t[n]=="number"&&(r[n]=t[n]);for(let n of["firstParty","pinned"])typeof t[n]=="boolean"&&(r[n]=t[n]);return uJ(e.client,{...typeof t.limit=="number"?{limit:t.limit}:{},...typeof t.offset=="number"?{offset:t.offset}:{},...Object.keys(r).length?{filter:r}:{},...t.includeStats===!0?{includeStats:!0}:{}})}},{name:"remember",description:'Save, update, or remove a standing user preference (memory). MANDATORY: whenever the user states a lasting preference ("always\u2026", "never\u2026", "from now on\u2026"), call this tool with action "set" before finalizing your reply, even if the preference matches your current default behavior; a jot never substitutes for it. action "set" creates or updates the memory at (scope, category, key); REUSE the exact key shown in the Saved preferences block when updating or reinforcing an existing entry; use replaces_key to supersede a different existing key; use confirms_key to confirm a pending candidate by its reference key. action "forget" removes one memory; "forget_all" removes ALL saved memories for this user. Set match_condition to make the preference conditional; omit it for always-on. At most 3 memory writes per turn. Returns {ok:true, outcome, resource_id} or {ok:false, reason}; NEVER tell the user a preference was saved unless the result has ok:true.',params:{action:{type:"string",enum:[...frt],description:'"set" to save/update, "forget" to remove one memory, "forget_all" to remove all.'},category:{type:"string",enum:[...drt],description:"Preference category the memory belongs to."},key:{type:"string",description:'Stable kebab-case identifier (max 120 chars), e.g. "acme-expense-account". Reuse keys shown in Saved preferences.'},directive:{type:"string",description:'The preference text to store (required for "set"; plain text, max 300 chars; no URLs, no email addresses (addresses belong in match_condition); no markup or template syntax).'},scope:{type:"string",enum:[...prt],description:'"this_org" applies in this organization only (default); "all_orgs" follows the user across all their organizations.'},match_condition:{type:"string",description:'WHEN this preference applies, in plain words (max 255 chars), e.g. "invoices from Acme" or "sent from billing@grab.com". Omit if it always applies. Nothing parses it: it comes back to you verbatim on turns it may fit, and you decide.'},replaces_key:{type:"string",description:"Existing key this memory supersedes, if any."},confirms_key:{type:"string",description:"Reference key of a pending candidate the user just confirmed, if any."},pending:{type:"boolean",description:"True to store as a candidate awaiting user confirmation instead of an active preference. Set it when the preference would make you TAKE an action you would otherwise ask about, or SKIP a confirmation you would otherwise seek; not for ones that only change HOW you act."},importance:{type:"number",description:"How much this changes your behavior, integer 0-100 (default 50). High (80+) for a preference that authorizes an action or a posting-policy change that alters what gets booked; low (20 or below) for a wording or reply-style tweak."}},required:["action","category","key"],group:"memory",readOnly:!1,searchHint:"remember memory save user preference standing directive forget conditional condition when applies sender vendor",channels:Ife()?["channel","chat"]:["channel"],alwaysLoaded:!0,disabled:!fJ(),execute:async(e,t)=>hrt(e,t)},{name:"present_questions",description:`Present the user with a bounded multiple-choice question (or a short sequence of them) as an interactive pick-list, instead of asking in prose. Use as your FINAL action when the user must choose among a KNOWN, finite set of options you ALREADY have from context (a prior search/list result, org defaults, or your own knowledge) to continue (e.g. "which bank account?", "which of these duplicate bills?"). Give 2-4 options per question (curate to the most likely, never dump a long list) and at most 4 questions; set multiSelect per question when more than one may apply. List the options and stop; the user's pick arrives as their next message and starts a new turn. Ask several questions at once when more than one thing is unspecified. Prose-ask only when there's no bounded set to offer (a free-text name or amount), to confirm destructive actions, or instead of fetching data just to populate options. Terminal: do not call further tools or re-list the options in text afterward.`,params:{questions:{type:"array",description:"Questions to ask, each rendered as one page of the pick-list (at most 4). Provide one unless you genuinely have several distinct choices to collect in sequence.",items:{type:"object",properties:{header:{type:"string",description:'Optional short eyebrow label categorizing the question (1-2 words, e.g. "Account", "Period").'},question:{type:"string",description:'The question, shown as the card title (e.g. "Which bank account should I inspect?").'},options:{type:"array",description:"2-4 concrete choices (hard cap: curate to the most likely; the user can still type a different answer if none fit).",items:{type:"object",properties:{label:{type:"string",description:"The choice shown on the row and relayed verbatim as the user's answer when picked; keep it self-contained and unambiguous."},description:{type:"string",description:'Optional one-line detail under the label (e.g. "554 unreconciled records").'}},required:["label"]}},multiSelect:{type:"boolean",description:"Set true to let the user select multiple options for this question; omit or false for a single pick."}},required:["question","options"]}}},required:["questions"],group:"interaction",alwaysLoaded:!0,interactive:!0,readOnly:!0,isConcurrencySafe:!0,searchHint:"present questions options choices pick select ask user interactive widget",maxResultSizeChars:2e4,execute:async(e,t)=>{let n=(Array.isArray(t?.questions)?t.questions:[]).filter(o=>typeof o=="object"&&o!==null).map(o=>{let s=(Array.isArray(o.options)?o.options:[]).filter(c=>typeof c=="object"&&c!==null).map(c=>{let u=String(c.label??"").trim();return c.description?{label:u,description:String(c.description)}:{label:u}}).filter(c=>c.label),a=String(o.question??"").trim();return{...o.header?{header:String(o.header)}:{},question:a,options:s,multiSelect:!!o.multiSelect}}).filter(o=>o.question&&o.options.length>=2);if(n.length===0)return{error:"present_questions needs at least one question with non-empty question text and \u22652 labeled options (a single-option pick-list is not a choice).",hint:"Provide questions: [{ question, options: [{ label }, { label }] }]. For open-ended input, ask in prose instead."};if(n.length>4)return{error:`present_questions renders at most 4 questions (got ${n.length}).`,hint:"Ask the 4 most important now; collect the rest on a later turn."};let i=n.find(o=>o.options.length>4);return i?{error:`Each question may offer at most 4 options ("${i.question}" has ${i.options.length}).`,hint:"Curate to the 4 most likely choices; the user can still type a different answer if none fit."}:{questions:n}}},{name:"describe_capabilities",description:"Offline. The capability map. No arguments: every namespace with its operation count. `namespace`: that namespace's operations with kinds and parameters. `query`: operations ranked by keyword. Call before telling a user something is unsupported, and to explain why your tool list is shorter than the operation count.",params:{namespace:{type:"string",description:'Drill into one namespace (e.g. "invoices") to list its operations and their parameters. Omit for the full map.'},query:{type:"string",description:'Rank operations across every namespace by keyword (e.g. "depreciation", "bank recon"). Omit for the full map.'}},required:[],group:"capabilities",readOnly:!0,isConcurrencySafe:!0,searchHint:"capabilities catalog overview inventory full list all operations how many operations namespaces map discover features supported unsupported",maxResultSizeChars:4e4,execute:async(e,t)=>{let{buildCapabilityMap:r}=await Promise.resolve().then(()=>(xnt(),Tnt));return r({namespace:typeof t.namespace=="string"?t.namespace:void 0,query:typeof t.query=="string"?t.query:void 0,surface:e.mcpSurface})}},{name:"navigate",description:'Dashboard navigation: discover destinations, then build deep links. Without `destination`: lists valid destinations as { key, label, kind }; filter with `query` (substring over key + label), `resource` (e.g. "sales"), or `kind` ("screen" | "modal"); results are capped, narrow if `truncated`. With `destination`: builds the URL to hand the user, a screen (e.g. "reports.profit-and-loss") or a record modal (e.g. "sales.modal.view-sale"); pass `resourceId` only for a record-specific modal (view/edit one record), never for screens or create/"new" modals. Returns { url, kind, label }; the link opens in the user\'s current org. Never write dashboard URLs or destination keys from memory; call with no destination to discover keys first.',params:{destination:{type:"string",description:'Destination key to link to, e.g. "reports.profit-and-loss" or "sales.modal.view-sale". Omit to discover valid keys instead.'},resourceId:{type:"string",description:"Jaz resourceId of the record to focus, only with a record-modal destination (view/edit a specific record). Omit for screens and create modals."},query:{type:"string",description:'Discovery only: case-insensitive substring over destination keys and labels (e.g. "invoice", "p&l", "bank").'},resource:{type:"string",description:'Discovery only: restrict to one resource namespace, e.g. "sales", "purchases", "reports", "settings".'},kind:{type:"string",description:'Discovery only: restrict to "screen" (list/report pages) or "modal" (record view/create/edit).',enum:["screen","modal"]}},required:[],group:"navigation",alwaysLoaded:!0,readOnly:!0,isConcurrencySafe:!0,aliases:["find_dashboard_destinations","get_dashboard_url"],searchHint:"dashboard url deep link navigate go to open screen record modal page share shareable list destinations keys catalog",maxResultSizeChars:16e3,execute:async(e,t)=>{let r=typeof t.destination=="string"&&t.destination?t.destination:void 0,n=typeof t.resourceId=="string"&&t.resourceId?t.resourceId:void 0;if(!r){if(n)return{error:"resourceId given without destination - pass the record-modal destination key, or omit resourceId to discover destinations."};let o=t.kind==="screen"||t.kind==="modal"?t.kind:void 0;return PP({query:typeof t.query=="string"?t.query:void 0,resource:typeof t.resource=="string"?t.resource:void 0,kind:o})}let{dashboardUrl:i}=TS(e.platform??"jaz");try{return xS(r,n,{dashboardUrl:i,orgResourceId:e.org?.resourceId})}catch(o){return{error:o instanceof Error?o.message:String(o)}}}},...Lbr,...$br,...Mbr,$B("download_journal_pdf","journal","journals",(e,t)=>SQe(e,t))];$ke(qf)});function dit(){if(!C_){C_=new Map;for(let e of qf)C_.set(e.name,e);for(let e of qf)for(let t of e.aliases??[])C_.has(t)||C_.set(t,e)}return C_}function xB(e){return dit().get(e)}function w_(e,t){let r=dit().get(e);if(!(!r||r.disabled)&&!(r.channels!==void 0&&(t===void 0||!r.channels.includes(t))))return r}function OB(){return Tpe||(Tpe=qf.filter(e=>!e.disabled&&e.channels===void 0)),Tpe}function iIr(){return xpe||(xpe=OB().filter(e=>!e.interactive)),xpe}function oIr(){return kpe||(kpe=qf.filter(e=>!e.disabled&&e.channels!==void 0)),kpe}function Fm(e){let t=e!==void 0&&nIr.has(e)?OB():iIr();if(e===void 0)return t;let r=oIr().filter(i=>i.channels.includes(e));if(r.length===0)return t;let n=uit.get(e);return n||(n=[...t,...r],uit.set(e,n)),n}function jl(e){return OB().filter(t=>t.group===e)}var C_,Tpe,nIr,xpe,kpe,uit,Yl=G(()=>{"use strict";QV();j7();C_=null,Tpe=null;nIr=new Set(["chat"]),xpe=null;kpe=null;uit=new Map});function aIr(e){return sIr.some(t=>e.startsWith(t))?!0:xB(e)?.readOnly===!0}function Jl(e){if(aIr(e.tool)&&xB(e.tool))return e}function tV(e,t,r){if(!(e instanceof Ur))return;let n=e.message.toLowerCase(),i=e.status;if(e.refusal)return e.refusal.repairTool?Jl({tool:e.refusal.repairTool,arguments:{},reason:e.refusal.hint}):void 0;if(i===404||n.includes("not found")){if(typeof r.contactResourceId=="string"&&n.includes("contact"))return Jl({tool:"search_contacts",arguments:{},reason:`Contact ${r.contactResourceId} not found. Search by name to find the correct resourceId before retrying.`});let s=/^(get|update|delete|finalize|pay)_(.+)$/.exec(t);if(s){let a=s[2],c=`search_${a}s`,u=typeof r.resourceId=="string"?r.resourceId:void 0;return Jl({tool:c,arguments:{},reason:`${a} ${u??""} not found. Search to locate the correct resourceId, then retry.`.trim()})}}if(i===422&&n.includes("user not found")&&n.includes("userresourceid"))return Jl({tool:"search_org_users",arguments:{},reason:"Employee binding needs the login user's resourceId. In search_org_users, read each member's `userResourceId` field (NOT the org-user record's own resourceId), then retry the bind with that."});if(i===422&&n.includes("claim profile is required"))return Jl({tool:"search_claim_profiles",arguments:{},reason:"An employee requires a claimProfileResourceId (it carries the employee balance account used to convert + pay claims; the org default is not auto-applied). Find one via search_claim_profiles, then set it on the employee."});if(i===422&&n.includes("expenseaccountresourceid"))return Jl({tool:"search_accounts",arguments:{},reason:'Claim type requires an expenseAccountResourceId. Search expense GL accounts via search_accounts \u2014 accountType is a display label (try "Operating Expense" or "Direct Costs", NOT "EXPENSE") \u2014 then set it on the claim type.'});if(i===422&&(n.includes("accountresourceid")||n.includes("account resource id")))return Jl({tool:"search_accounts",arguments:{},reason:"Line item missing accountResourceId. Search for the right GL account first (e.g. by name or accountType), then retry with the resourceId on each line item."});if(i===422&&(n.includes("duplicate")||n.includes("already exists"))){let s=typeof r.reference=="string"?r.reference:void 0,a=/^create_(.+)$/.exec(t);if(s&&a){let u=a[1];return Jl({tool:`search_${u}s`,arguments:{filter:{reference:{eq:s}}},reason:`Reference "${s}" already exists in ${u}s. Look up the existing record before deciding whether to update it or use a new reference.`})}let c=Qke(t,r);if(!s&&r.autoReference===!0&&c)return Jl({tool:"get_next_reference",arguments:{type:c},reason:"That number was taken by another document created at the same moment. Reading the series does not reserve a number. Read the next one and pass it as an explicit reference, then create again. Do not invent one."})}if(i===422&&n.includes("every line item must carry a tax profile"))return Jl({tool:"search_tax_profiles",arguments:{},reason:"The claim profile is INCLUSIVE \u2014 every claim line must carry a tax profile to submit. Find a purchase tax profile via search_tax_profiles, set it on the claim type (so its lines inherit tax) or on the line, then resubmit."});if(i===422&&(n.includes("taxprofileresourceid")||n.includes("tax profile")||n.includes("tax_profile"))&&(n.includes("direction")||n.includes("appliesto")||n.includes("applies to")||n.includes("sale")||n.includes("purchase")))return Jl({tool:"search_tax_profiles",arguments:{},reason:'Tax profile rejected \u2014 direction mismatch. Use search_tax_profiles with the correct appliesTo direction ("sale" for invoices, "purchase" for bills) before retrying.'})}function LV(e){let t=`clio mcp-call ${e.tool}`;if(Object.keys(e.arguments).length===0)return t;let r=JSON.stringify(e.arguments);return`${t} --args '${r.replace(/'/g,"'\\''")}'`}var sIr,rV=G(()=>{"use strict";bo();yl();Yl();sIr=["search_","list_","get_","view_","describe_"]});function Bpe(e,t,r){!t&&r?process.stderr.write(De(` ${Ko.warning} ${e.label} ${$("\xB7")} ${e.orgName} (${e.currency})`)+$(` \u2014 not pinned
830
+ USE THIS to check a specific draft; for "what does this contact normally look like?" use get_contact_signals.`,params:{items:ype},required:["items"],group:"drafts",readOnly:!0,isConcurrencySafe:!0,searchHint:"validate drafts eligibility check before convert promote duplicate unusual amount new account signals",execute:async(e,t)=>(h_(t.items),WG(e.client,t.items))},{name:"convert_drafts_to_active",description:`BULK promote: finalize up to 500 draft business transactions to ACTIVE in ONE call, mixing any combination of invoices, bills, and credit notes (no per-type tools needed). ${ql} NOT idempotent: a second call on already-promoted drafts returns 422. Filter the draft list by status: DRAFT before submitting. Pair with validate_drafts for a pre-flight check on large batches.`,params:{items:ype},required:["items"],group:"drafts",readOnly:!1,isDestructive:!0,searchHint:"convert drafts to active promote finalize bulk",execute:async(e,t)=>(h_(t.items),ZG(e.client,t.items))},{name:"submit_drafts_for_approval",description:`BULK submit: route up to 500 draft business transactions into the approval workflow in ONE call, mixing any combination of invoices, bills, and credit notes. ${ql} NOT idempotent on drafts that already have an in-flight approval request.`,params:{items:ype},required:["items"],group:"drafts",readOnly:!1,isDestructive:!0,searchHint:"submit drafts for approval workflow review bulk",execute:async(e,t)=>(h_(t.items),KG(e.client,t.items))},{name:"search_help_center",description:'Search the Jaz help center for how-to articles, feature guides, and accounting concepts. Use this for "how do I..." or "what is..." questions about Jaz: bank reconciliation, GST/VAT filing, multi-currency, fixed assets, custom fields, integrations, and so on. Returns top matching articles with title, section, content, and source URL.',params:{query:{type:"string",description:'Natural-language question or keywords (e.g. "how do I reconcile a bank statement", "set up GST", "multi-currency invoice").'},limit:{type:"number",description:"Maximum number of articles to return (default 3, max 10)."},section:{type:"string",description:'Optional: restrict to a single help-center section slug (e.g. "bank-reconciliations", "invoices").'}},required:["query"],group:"help_center",readOnly:!0,searchHint:"help center docs how to guide article search question",isConcurrencySafe:!0,maxResultSizeChars:2e4,execute:async(e,t)=>{let r=typeof t.limit=="number"?t.limit:void 0,n=typeof t.section=="string"?t.section:void 0;return Afe(String(t.query??""),{limit:r,section:n})}},{name:"jot",description:"Record judgment entries in the org's judgment journal. THE RULE: log a judgment when you chose among real alternatives and a write followed, or when you deliberately decided NOT to write; skip mechanical actions and calls where the platform, the data, or the user left you no choice. Jot AFTER the write succeeds, never before, and carry the written record's resourceId in refs. Kinds: CLASSIFICATION account/tax/category choice \xB7 MATCH decided which records correspond \xB7 SCOPE chose what is in or out of a set \xB7 ASSUMPTION filled a gap the data left open \xB7 RISK proceeded despite a known concern \xB7 METHOD picked a calculation or approach \xB7 RECOVERY decided how to proceed after a failure (incl. holding a write) \xB7 DEVIATION knowingly departed from a stated rule. Accepting or rejecting an inbound external document (an e-invoice a counterparty sent) is a FINALIZE act, so pin it. A decided omission (you deliberately did not post something) is RECOVERY, which pins via withheld-write, not DEVIATION. Always give why: a call without a basis is half a record. Batch related entries in one call; each entry is acked independently. "+OP+" "+qke,params:{entries:{type:"array",description:"Judgment entries to record (1-100). A bad entry never fails the batch: it acks with error while siblings persist.",items:{type:"object",properties:{kind:{type:"string",enum:["CLASSIFICATION","MATCH","SCOPE","ASSUMPTION","RISK","METHOD","RECOVERY","DEVIATION","NOTE"],description:"Judgment kind: one-liners in the tool description. "+BP},tier:{type:"string",enum:["LOW","MEDIUM","HIGH","CRITICAL"],description:"Severity you assign the call. "+xP},call:{type:"string",description:'The decision, one line (e.g. "Posted to 6420 Travel: recurring Grab vendor pattern").'},why:{type:"string",description:"The basis for the call: a call without a basis is half a record."},ruledOut:{type:"string",description:"Alternatives considered and set aside."},refs:{type:"array",description:"Entity refs this judgment touches. Include the written record's resourceId.",items:{type:"string",description:'Grammar TYPE:resourceId[#field][:RELATION], e.g. "INVOICE:41f6\u2026:CREATE". RELATION: CREATE UPDATE DELETE FINALIZE PAY RECONCILE TRIGGER SUBJECT INPUT REJECTED SUPERSEDES (default SUBJECT).'}},frame:{type:"string",description:'For set-shaped subjects: the defining query + counts (e.g. "search_bills status=UNPAID \xB7 41 in scope, 4 deferred").'},confidence:{type:"string",enum:["LOW","MEDIUM","HIGH"],description:"Routing hint for review attention. "+kP},citedRule:{type:"string",description:"Org rule, policy, threshold, or precedent applied."},workflowLabel:{type:"string",description:NP},agentLabel:{type:"string",description:"Self-reported product/model (e.g. jaz-clio/claude); informative, never authoritative."},idempotencyKey:{type:"string",description:"Retry key: a replay returns the existing entry, not a duplicate."}},required:["kind","tier","call"]}}},required:["entries"],group:"jots",readOnly:!1,alwaysLoaded:!0,searchHint:"jot judgment judgement journal record decision log rationale reasoning why chose ruled out classification match scope assumption risk method recovery deviation basis precedent note after write",execute:async(e,t)=>i_(e.client,t.entries)},{name:"recall",description:"Search the org's judgment journal (jots) for precedents, prior judgment calls with their basis. Check BEFORE repeating a judgment on the same entity, kind of decision, or workflow: a prior call with its why beats re-deriving from scratch; a FLAG or REJECT disposition on it is a warning, ENDORSE a strong precedent. Sort is pinned server-side (CRITICAL and withheld-write RECOVERY entries first, then newest). An empty page means no prior judgments match this filter, or none are recorded for this org yet.",params:{ref:{type:"string",description:'Entity ref filter, TYPE:resourceId (e.g. "INVOICE:41f626a3-\u2026").'},kind:{type:"string",enum:["CLASSIFICATION","MATCH","SCOPE","ASSUMPTION","RISK","METHOD","RECOVERY","DEVIATION","NOTE"],description:"Judgment kind filter."},tier:{type:"string",enum:["LOW","MEDIUM","HIGH","CRITICAL"],description:"Severity tier filter."},dispositionVerb:{type:"string",enum:["FLAG","REJECT","ENDORSE"],description:"Only entries carrying at least one disposition with this verb."},freetext:{type:"string",description:"Case-insensitive substring over call and why."},createdFrom:{type:"number",description:"Created-at lower bound, epoch ms (inclusive)."},createdTo:{type:"number",description:"Created-at upper bound, epoch ms (inclusive)."},firstParty:{type:"boolean",description:"true = only entries from Jaz-operated agent surfaces (server-stamped origin, never self-reported); false = only external and pre-stamp entries. Omit for all."},pinned:{type:"boolean",description:"true = only pinned entries (critical or withheld-write, the same predicate the pinned sort uses); false = only unpinned. Omit for all."},limit:{type:"number",description:"Max results per page (1-1000, default 100)."},offset:{type:"number",description:"Page number, 0-indexed."},includeStats:{type:"boolean",description:"Include per-credential disposition counts."}},required:[],group:"jots",readOnly:!0,isConcurrencySafe:!0,searchHint:"recall search jots judgment judgement journal precedent prior decisions rationale history dispositions flagged rejected endorsed first party jaz agents pinned lookup why",maxResultSizeChars:2e4,execute:async(e,t)=>{let r={};for(let n of["ref","kind","tier","dispositionVerb","freetext"])typeof t[n]=="string"&&t[n]&&(r[n]=t[n]);for(let n of["createdFrom","createdTo"])typeof t[n]=="number"&&(r[n]=t[n]);for(let n of["firstParty","pinned"])typeof t[n]=="boolean"&&(r[n]=t[n]);return uJ(e.client,{...typeof t.limit=="number"?{limit:t.limit}:{},...typeof t.offset=="number"?{offset:t.offset}:{},...Object.keys(r).length?{filter:r}:{},...t.includeStats===!0?{includeStats:!0}:{}})}},{name:"remember",description:'Save, update, or remove a standing user preference (memory). MANDATORY: whenever the user states a lasting preference ("always\u2026", "never\u2026", "from now on\u2026"), call this tool with action "set" before finalizing your reply, even if the preference matches your current default behavior; a jot never substitutes for it. action "set" creates or updates the memory at (scope, category, key); REUSE the exact key shown in the Saved preferences block when updating or reinforcing an existing entry; use replaces_key to supersede a different existing key; use confirms_key to confirm a pending candidate by its reference key. action "forget" removes one memory; "forget_all" removes ALL saved memories for this user. Set match_condition to make the preference conditional; omit it for always-on. At most 3 memory writes per turn. Returns {ok:true, outcome, resource_id} or {ok:false, reason}; NEVER tell the user a preference was saved unless the result has ok:true.',params:{action:{type:"string",enum:[...frt],description:'"set" to save/update, "forget" to remove one memory, "forget_all" to remove all.'},category:{type:"string",enum:[...drt],description:"Preference category the memory belongs to."},key:{type:"string",description:'Stable kebab-case identifier (max 120 chars), e.g. "acme-expense-account". Reuse keys shown in Saved preferences.'},directive:{type:"string",description:'The preference text to store (required for "set"; plain text, max 300 chars; no URLs, no email addresses (addresses belong in match_condition); no markup or template syntax).'},scope:{type:"string",enum:[...prt],description:'"this_org" applies in this organization only (default); "all_orgs" follows the user across all their organizations.'},match_condition:{type:"string",description:'WHEN this preference applies, in plain words (max 255 chars), e.g. "invoices from Acme" or "sent from billing@grab.com". Omit if it always applies. Nothing parses it: it comes back to you verbatim on turns it may fit, and you decide.'},replaces_key:{type:"string",description:"Existing key this memory supersedes, if any."},confirms_key:{type:"string",description:"Reference key of a pending candidate the user just confirmed, if any."},pending:{type:"boolean",description:"True to store as a candidate awaiting user confirmation instead of an active preference. Set it when the preference would make you TAKE an action you would otherwise ask about, or SKIP a confirmation you would otherwise seek; not for ones that only change HOW you act."},importance:{type:"number",description:"How much this changes your behavior, integer 0-100 (default 50). High (80+) for a preference that authorizes an action or a posting-policy change that alters what gets booked; low (20 or below) for a wording or reply-style tweak."}},required:["action","category","key"],group:"memory",readOnly:!1,searchHint:"remember memory save user preference standing directive forget conditional condition when applies sender vendor",channels:Ife()?["channel","chat"]:["channel"],alwaysLoaded:!0,disabled:!fJ(),execute:async(e,t)=>hrt(e,t)},{name:"present_questions",description:`Present the user with a bounded multiple-choice question (or a short sequence of them) as an interactive pick-list, instead of asking in prose. Use as your FINAL action when the user must choose among a KNOWN, finite set of options you ALREADY have from context (a prior search/list result, org defaults, or your own knowledge) to continue (e.g. "which bank account?", "which of these duplicate bills?"). Give 2-4 options per question (curate to the most likely, never dump a long list) and at most 4 questions; set multiSelect per question when more than one may apply. List the options and stop; the user's pick arrives as their next message and starts a new turn. Ask several questions at once when more than one thing is unspecified. Prose-ask only when there's no bounded set to offer (a free-text name or amount), to confirm destructive actions, or instead of fetching data just to populate options. Terminal: do not call further tools or re-list the options in text afterward.`,params:{questions:{type:"array",description:"Questions to ask, each rendered as one page of the pick-list (at most 4). Provide one unless you genuinely have several distinct choices to collect in sequence.",items:{type:"object",properties:{header:{type:"string",description:'Optional short eyebrow label categorizing the question (1-2 words, e.g. "Account", "Period").'},question:{type:"string",description:'The question, shown as the card title (e.g. "Which bank account should I inspect?").'},options:{type:"array",description:"2-4 concrete choices (hard cap: curate to the most likely; the user can still type a different answer if none fit).",items:{type:"object",properties:{label:{type:"string",description:"The choice shown on the row and relayed verbatim as the user's answer when picked; keep it self-contained and unambiguous."},description:{type:"string",description:'Optional one-line detail under the label (e.g. "554 unreconciled records").'}},required:["label"]}},multiSelect:{type:"boolean",description:"Set true to let the user select multiple options for this question; omit or false for a single pick."}},required:["question","options"]}}},required:["questions"],group:"interaction",alwaysLoaded:!0,interactive:!0,readOnly:!0,isConcurrencySafe:!0,searchHint:"present questions options choices pick select ask user interactive widget",maxResultSizeChars:2e4,execute:async(e,t)=>{let n=(Array.isArray(t?.questions)?t.questions:[]).filter(o=>typeof o=="object"&&o!==null).map(o=>{let s=(Array.isArray(o.options)?o.options:[]).filter(c=>typeof c=="object"&&c!==null).map(c=>{let u=String(c.label??"").trim();return c.description?{label:u,description:String(c.description)}:{label:u}}).filter(c=>c.label),a=String(o.question??"").trim();return{...o.header?{header:String(o.header)}:{},question:a,options:s,multiSelect:!!o.multiSelect}}).filter(o=>o.question&&o.options.length>=2);if(n.length===0)return{error:"present_questions needs at least one question with non-empty question text and \u22652 labeled options (a single-option pick-list is not a choice).",hint:"Provide questions: [{ question, options: [{ label }, { label }] }]. For open-ended input, ask in prose instead."};if(n.length>4)return{error:`present_questions renders at most 4 questions (got ${n.length}).`,hint:"Ask the 4 most important now; collect the rest on a later turn."};let i=n.find(o=>o.options.length>4);return i?{error:`Each question may offer at most 4 options ("${i.question}" has ${i.options.length}).`,hint:"Curate to the 4 most likely choices; the user can still type a different answer if none fit."}:{questions:n}}},{name:"describe_capabilities",description:"Offline. The capability map. No arguments: every namespace with its operation count. `namespace`: that namespace's operations with kinds and parameters. `query`: operations ranked by keyword. Call before telling a user something is unsupported, and to explain why your tool list is shorter than the operation count.",params:{namespace:{type:"string",description:'Drill into one namespace (e.g. "invoices") to list its operations and their parameters. Omit for the full map.'},query:{type:"string",description:'Rank operations across every namespace by keyword (e.g. "depreciation", "bank recon"). Omit for the full map.'}},required:[],group:"capabilities",readOnly:!0,isConcurrencySafe:!0,searchHint:"capabilities catalog overview inventory full list all operations how many operations namespaces map discover features supported unsupported",maxResultSizeChars:4e4,execute:async(e,t)=>{let{buildCapabilityMap:r}=await Promise.resolve().then(()=>(xnt(),Tnt));return r({namespace:typeof t.namespace=="string"?t.namespace:void 0,query:typeof t.query=="string"?t.query:void 0,surface:e.mcpSurface})}},{name:"navigate",description:'Dashboard navigation: discover destinations, then build deep links. Without `destination`: lists valid destinations as { key, label, kind }; filter with `query` (substring over key + label), `resource` (e.g. "sales"), or `kind` ("screen" | "modal"); results are capped, narrow if `truncated`. With `destination`: builds the URL to hand the user, a screen (e.g. "reports.profit-and-loss") or a record modal (e.g. "sales.modal.view-sale"); pass `resourceId` only for a record-specific modal (view/edit one record), never for screens or create/"new" modals. Returns { url, kind, label }; the link opens in the user\'s current org. Never write dashboard URLs or destination keys from memory; call with no destination to discover keys first.',params:{destination:{type:"string",description:'Destination key to link to, e.g. "reports.profit-and-loss" or "sales.modal.view-sale". Omit to discover valid keys instead.'},resourceId:{type:"string",description:"Jaz resourceId of the record to focus, only with a record-modal destination (view/edit a specific record). Omit for screens and create modals."},query:{type:"string",description:'Discovery only: case-insensitive substring over destination keys and labels (e.g. "invoice", "p&l", "bank").'},resource:{type:"string",description:'Discovery only: restrict to one resource namespace, e.g. "sales", "purchases", "reports", "settings".'},kind:{type:"string",description:'Discovery only: restrict to "screen" (list/report pages) or "modal" (record view/create/edit).',enum:["screen","modal"]}},required:[],group:"navigation",alwaysLoaded:!0,readOnly:!0,isConcurrencySafe:!0,aliases:["find_dashboard_destinations","get_dashboard_url"],searchHint:"dashboard url deep link navigate go to open screen record modal page share shareable list destinations keys catalog",maxResultSizeChars:16e3,execute:async(e,t)=>{let r=typeof t.destination=="string"&&t.destination?t.destination:void 0,n=typeof t.resourceId=="string"&&t.resourceId?t.resourceId:void 0;if(!r){if(n)return{error:"resourceId given without destination - pass the record-modal destination key, or omit resourceId to discover destinations."};let o=t.kind==="screen"||t.kind==="modal"?t.kind:void 0;return PP({query:typeof t.query=="string"?t.query:void 0,resource:typeof t.resource=="string"?t.resource:void 0,kind:o})}let{dashboardUrl:i}=TS(e.platform??"jaz");try{return xS(r,n,{dashboardUrl:i,orgResourceId:e.org?.resourceId})}catch(o){return{error:o instanceof Error?o.message:String(o)}}}},...Lbr,...$br,...Mbr,$B("download_journal_pdf","journal","journals",(e,t)=>SQe(e,t))];$ke(qf)});function dit(){if(!C_){C_=new Map;for(let e of qf)C_.set(e.name,e);for(let e of qf)for(let t of e.aliases??[])C_.has(t)||C_.set(t,e)}return C_}function xB(e){return dit().get(e)}function w_(e,t){let r=dit().get(e);if(!(!r||r.disabled)&&!(r.channels!==void 0&&(t===void 0||!r.channels.includes(t))))return r}function OB(){return Tpe||(Tpe=qf.filter(e=>!e.disabled&&e.channels===void 0)),Tpe}function iIr(){return xpe||(xpe=OB().filter(e=>!e.interactive)),xpe}function oIr(){return kpe||(kpe=qf.filter(e=>!e.disabled&&e.channels!==void 0)),kpe}function Fm(e){let t=e!==void 0&&nIr.has(e)?OB():iIr();if(e===void 0)return t;let r=oIr().filter(i=>i.channels.includes(e));if(r.length===0)return t;let n=uit.get(e);return n||(n=[...t,...r],uit.set(e,n)),n}function jl(e){return OB().filter(t=>t.group===e)}var C_,Tpe,nIr,xpe,kpe,uit,Yl=G(()=>{"use strict";QV();j7();C_=null,Tpe=null;nIr=new Set(["chat"]),xpe=null;kpe=null;uit=new Map});function aIr(e){return sIr.some(t=>e.startsWith(t))?!0:xB(e)?.readOnly===!0}function Jl(e){if(aIr(e.tool)&&xB(e.tool))return e}function tV(e,t,r){if(!(e instanceof Ur))return;let n=e.message.toLowerCase(),i=e.status;if(e.refusal)return e.refusal.repairTool?Jl({tool:e.refusal.repairTool,arguments:{},reason:e.refusal.hint}):void 0;if(i===404||n.includes("not found")){if(typeof r.contactResourceId=="string"&&n.includes("contact"))return Jl({tool:"search_contacts",arguments:{},reason:`Contact ${r.contactResourceId} not found. Search by name to find the correct resourceId before retrying.`});let s=/^(get|update|delete|finalize|pay)_(.+)$/.exec(t);if(s){let a=s[2],c=`search_${a}s`,u=typeof r.resourceId=="string"?r.resourceId:void 0;return Jl({tool:c,arguments:{},reason:`${a} ${u??""} not found. Search to locate the correct resourceId, then retry.`.trim()})}}if(i===422&&n.includes("user not found")&&n.includes("userresourceid"))return Jl({tool:"search_org_users",arguments:{},reason:"Employee binding needs the login user's resourceId. In search_org_users, read each member's `userResourceId` field (NOT the org-user record's own resourceId), then retry the bind with that."});if(i===422&&n.includes("claim profile is required"))return Jl({tool:"search_claim_profiles",arguments:{},reason:"An employee requires a claimProfileResourceId (it carries the employee balance account used to convert + pay claims; the org default is not auto-applied). Find one via search_claim_profiles, then set it on the employee."});if(i===422&&n.includes("expenseaccountresourceid"))return Jl({tool:"search_accounts",arguments:{},reason:'Claim type requires an expenseAccountResourceId. Search expense GL accounts via search_accounts \u2014 accountType is a display label (try "Operating Expense" or "Direct Costs", NOT "EXPENSE") \u2014 then set it on the claim type.'});if(i===422&&(n.includes("accountresourceid")||n.includes("account resource id")))return Jl({tool:"search_accounts",arguments:{},reason:"Line item missing accountResourceId. Search for the right GL account first (e.g. by name or accountType), then retry with the resourceId on each line item."});if(i===422&&(n.includes("duplicate")||n.includes("already exists"))){let s=typeof r.reference=="string"?r.reference:void 0,a=/^create_(.+)$/.exec(t);if(s&&a){let u=a[1];return Jl({tool:`search_${u}s`,arguments:{filter:{reference:{eq:s}}},reason:`Reference "${s}" already exists in ${u}s. Look up the existing record before deciding whether to update it or use a new reference.`})}let c=Qke(t,r);if(!s&&r.autoReference===!0&&c)return Jl({tool:"get_next_reference",arguments:{type:c},reason:"That number was taken by another document created at the same moment. Reading the series does not reserve a number. Read the next one and pass it as an explicit reference, then create again. Do not invent one."})}if(i===422&&n.includes("every line item must carry a tax profile"))return Jl({tool:"search_tax_profiles",arguments:{},reason:"The claim profile is INCLUSIVE \u2014 every claim line must carry a tax profile to submit. Find a purchase tax profile via search_tax_profiles, set it on the claim type (so its lines inherit tax) or on the line, then resubmit."});if(i===422&&(n.includes("taxprofileresourceid")||n.includes("tax profile")||n.includes("tax_profile"))&&(n.includes("direction")||n.includes("appliesto")||n.includes("applies to")||n.includes("sale")||n.includes("purchase")))return Jl({tool:"search_tax_profiles",arguments:{},reason:'Tax profile rejected \u2014 direction mismatch. Use search_tax_profiles with the correct appliesTo direction ("sale" for invoices, "purchase" for bills) before retrying.'})}function LV(e){let t=`clio mcp-call ${e.tool}`;if(Object.keys(e.arguments).length===0)return t;let r=JSON.stringify(e.arguments);return`${t} --args '${r.replace(/'/g,"'\\''")}'`}var sIr,rV=G(()=>{"use strict";bo();yl();Yl();sIr=["search_","list_","get_","view_","describe_"]});function Bpe(e,t,r){!t&&r?process.stderr.write(De(` ${Ko.warning} ${e.label} ${$("\xB7")} ${e.orgName} (${e.currency})`)+$(` \u2014 not pinned
832
831
  `)+$(` Pin: export JAZ_ORG=${e.label} or --org ${e.label}
833
832
  `)):process.stderr.write($(` ${M(Ko.bullet)} ${e.label} ${$("\xB7")} ${e.orgName} (${e.currency})
834
833
  `))}var fit=G(()=>{"use strict";Ne()});var hit={};Bo(hit,{formatApiError:()=>Npe,formatAuthError:()=>Ope,formatGenericError:()=>GB,formatWarning:()=>lIr});function cIr(e){return pit[e]?pit[e]:e>=500?"Server error. Try again shortly.":""}function Npe(e,t,r,n){let i=ne(En.vertical),o=[`${_t}${i}`,`${_t}${i} ${ne(`API Error (${e})`)}`];r&&o.push(`${_t}${i} ${$(r)}`),o.push(`${_t}${i}`);let s=n??cIr(e);s&&(o.push(`${_t}${i} ${s}`),o.push(`${_t}${i}`));let a=t.replace(/^.*?→\s*\d+:\s*/,"").trim();if(a&&a!==s){let c=r?3:2;o.splice(c,0,`${_t}${i} ${$(a)}`)}return o.join(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jaz-clio",
3
- "version": "9.0.2",
3
+ "version": "9.2.0",
4
4
  "description": "Fastest ledger for agents. Jaz accounting CLI and MCP server: invoicing, bills, payments, quotes & orders, bank reconciliation, fixed assets, inventory, expense claims, tax, reports, and period close.",
5
5
  "type": "module",
6
6
  "bin": {