@go-labs-sg/bb 2.31.0 → 2.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -244,6 +244,38 @@ Bill creation determines its required permissions from the parsed input, for bot
244
244
 
245
245
  **Contact-person estimate email:** `bb budget estimate send --payload '<json>'` calls the same `email.sendEstimateToContactPerson` procedure as the web composer. The payload requires `budgetId`, `estimateId`, optional `estimateDocNumber`, `to`, `cc`, `replyTo`, `subject`, HTML `content`, and HTML `signature`. It durably queues worker delivery of the QuickBooks PDF, standard terms, and Budget Builder budget attachments, and returns the outbound-email and operation IDs immediately. Worker retries reuse one provider idempotency key; the budget becomes `ESTIMATE_SENT` only after Resend accepts the email. The command requires interactive `CONFIRM`; inspect the budget, recipients, and HTML first.
246
246
 
247
+ ### Link an existing QuickBooks invoice
248
+
249
+ **Release status:** these CLI commands are pending release. The repository package version remains `2.31.0`; that version number alone does not establish that a published CLI contains them. Check `bb help` for `customer-invoice preview-qbo` and `customer-invoice import-qbo` after the normal CLI release. This code delivery does not publish the CLI, deploy the API, or perform a production import. The backend must expose `customerInvoice.previewExistingInvoice` and `customerInvoice.linkExistingInvoice` (implemented in PR #1067).
250
+
251
+ Authenticate and run `bb auth whoami` first. Preview and import use the same project management permission as the web flow: with user access checks enabled, Admins or the project's creator, business-development owner, inside-sales owner, or project manager can act. A finance/Lead role alone does not grant access to an unrelated project. The server applies its current access-check configuration and rechecks permission during confirmation.
252
+
253
+ Use the **BB budget ID** whose existing estimate should receive the invoice, and the numeric **QBO invoice transaction ID**, not the displayed invoice number or an estimate number. Accepted and closed estimates are supported; linking does not reopen them. Fully invoiced means all estimate coverage has been billed; it does not mean the invoices are fully paid.
254
+
255
+ ```bash
256
+ bb customer-invoice preview-qbo <budgetId> <qboInvoiceId>
257
+ bb customer-invoice import-qbo <budgetId> <qboInvoiceId> --token <previewToken>
258
+ ```
259
+
260
+ Preview performs read-only QBO access and returns JSON under `data`, including `token`, project/estimate, invoice number/identity, currency, dates, total, outstanding `balance`, allocation `percentage`, lines, `alreadyLinked`, and other unlinked invoices. Review all of it before confirming. Import requires the exact `data.token` from that preview; it does not silently generate a fresh token. In an interactive terminal, confirm the named invoice and budget when prompted. For an explicitly authorized non-interactive import:
261
+
262
+ ```bash
263
+ bb customer-invoice import-qbo <budgetId> <qboInvoiceId> \
264
+ --token <previewToken> --allow-state-change --allow-financial-write
265
+ ```
266
+
267
+ Import writes BB financial records only and needs neither `--allow-external-write` nor `--allow-email`. It calls the same server validation as the UI: project/customer/currency/estimate/allocation checks, sibling invoice coverage, and financial drift checks. It preserves the original invoice evidence and does not create, update, void, delete, send, or match anything in QBO. A successful response returns `batchId`, `invoiceId` (the new BB invoice ID), and `alreadyLinked: false`. Repeating the same link returns the original IDs with `alreadyLinked: true`, provided upstream financial evidence still agrees. A conflicting budget, ambiguous allocation, or financial mismatch fails; a stale preview requires another reviewed preview. Do not retry a conflict with a different budget to bypass it.
268
+
269
+ To refresh payments, use the returned **BB invoice ID**:
270
+
271
+ ```bash
272
+ bb customer-invoice sync <invoiceId>
273
+ ```
274
+
275
+ For `QBO_IMPORT` invoices, this existing command reads QBO and updates BB balance, payment status, and sync evidence only. Settlement becomes `PAID`; a reversal restores `APPROVED`; original invoice evidence is retained. Financial drift is reported separately as `qboFinancialDrift` and blocks further billing. The shared sync command also supports native invoices, so its generic automation permissions remain `--allow-state-change --allow-financial-write --allow-external-write`; imported records still take the server's BB-only path. Native creation continues to use `bb customer-invoice create --payload '<json>'` and needs no import token or fields.
276
+
277
+ The Seatrium example must be previewed against live QBO again before an operator imports it. Previously observed invoice numbers and paid/open balances are not authorization or current-state evidence.
278
+
247
279
  **Customer-invoice workflow parity:** `bb customer-invoice list` uses the same global/project list procedure and metrics as the web pages; omit filters for the global list or use `--projectId` for project scope. `bb customer-invoice send --payload '<json>'` uses the same protected email workflow as the web composer, sends to the payload's `to` address, saves that address as the project's configured billing email after a successful send, and marks the invoice `SENT`. `bb customer-invoice approve` and `bb customer-invoice reject` are Admin-only batch operations: callers select a batch ID, and the API resolves its internal pending Admin approval record. `bb customer-invoice sync` follows QuickBooks' paid state and zero balance, restoring `SENT` or `APPROVED` if that payment is reversed. Invoice approval refuses voided invoices and closes the estimate only when approved invoice coverage totals 100%; deletion, voiding, rejection, expiry, and QBO synchronization use the same estimate-reopening and live-payment guards as the web app.
248
280
 
249
281
  For agent-driven invoice work, use this read-before-write sequence:
@@ -285,7 +317,7 @@ Bill, claim, and quotation mutations accept only suppliers whose approval status
285
317
  | **Budgets** | `list-budgets` (full payload by default; `--summary` or `--includeDetails false` for slim list), `get-budget`, `get-budget-items`, `get-budget-details`, `get-budget-categories`, `get-budget-versions`, `rename-budget-version`, `restore-budget-version`, `update-budget-status` (`ESTIMATE_ACCEPTED` requires win proof, auto-marks `PITCH` or `LOST` projects `WON`, and auto-creates an Asana Event Ops section from Prompt 5 tasks when enabled; Prompt 5 skips quantity-zero items, combines matching non-GO-internal suppliers, keeps each GO internal item separate, removes generated work that repeats another line item or group, and schedules applicable subtasks relative to the project start date; `--projectStatusOnCommercialRejection PITCH\|LOST` is required when rejecting the only accepted/closed budget on a commercial project), `mark-budget-won` (`<budgetId>` + proof file path; `PITCH` or `LOST` projects become `WON` automatically), `create-budget` / `update-budget` (`--payload`; cloned unavailable lines require `unavailableItemReviewAcknowledged: true`), `delete-budget`, `create-budget-approval` (also sends approval request emails), `create-estimate`, `send-estimate-to-contact-person` (same contact-person email workflow as web), `add-budget-items`, `update-budget-item`, `replace-budget-item`, `approve-unavailable-item-exception` (Lead/Admin), `remove-budget-item`, `reorder-budget-items`, `update-budget-item-supplier`, `mark-budget-items-not-utilized`, `restore-budget-item`, `create-placeholder-bill` (admin recovery), `create-budget-category`, `update-budget-category`, `delete-budget-category`, `update-budget-commission`, `delete-budget-commission`, `update-budget-discount`, `delete-budget-discount` (`--payload` where noted), `upload-budget-attachment` (`<budgetId>` + local file path; uses `attachment.requestBudgetAttachmentUpload` + PUT + `attachment.confirmBudgetAttachment`) |
286
318
  | **Bills / claims** | `list-bills` (`--isClaimable false` for bills, `--isClaimable true` for claims, omit for both), `list-claims` (claims only), `validate-bill-selection` (`--payload`; validates the project and supplier separately, then checks approved quotation links for each selected budget line item independent of the bill supplier; `alreadyPaid` never bypasses the checks), `stage-bill-attachment` (securely uploads invoice/payment-proof files before creation and returns attachment JSON; ownership and one-hour expiry are enforced by a server-side staged-upload record rather than encoded in the object key), `cleanup-staged-bill-attachments`, `create-bill` (`--payload`; set `isClaimable=false` for a bill, `isClaimable=true` for a claim; supplier bills require positive `extractedAmount` and `amount`, with `amount <= extractedAmount`; an already-paid supplier bill sets `alreadyPaid=true` and requires `paymentReference` plus a staged PDF in `paymentProofAttachments`; admin creation automatically queues QBO finalization while other roles remain pending approval; non-legacy supplier bills from 1 Jul 2026 00:00 SGT require approved quotation coverage linked to every selected line item, independent of the bill supplier, even when already paid), `update-bill` (`--payload`), `update-bill-payment-evidence` (`--payload`; replaces the payment reference and payment-proof PDFs for an already-paid bill), `delete-bill`, `create-bill-approval` (also sends approval request emails), `update-bill-status` (`PAID` requires `--paymentReference`; pass `--paymentProof <receipt.pdf>` to stage and submit a PDF up to 20MB atomically, or omit it only when BB already has payment proof; moving to `PAID` runs the server's paid-bill notification workflow), `patch-bill-payment` (PAID bills: `--paymentTrackingUrl`, `--paymentReference`, `--quickbooksBillId`, `--paymentDate` ISO; clearing a paid bill's QuickBooks link is not allowed), `patch-bill-invoice-number`, `get-bill-attachments`, `upload-bill-attachment` (`<billId>` + local path), `get-bill-details` |
287
319
  | **Quotations** | `list-quotations` (supports project, budget, supplier, status, requester, and text-search filters), `get-quotation`, `upload-quotation-attachment` (`<projectId>` + PDF/JPEG/PNG path up to 20MB; returns attachment JSON for payload use), `cleanup-staged-quotation-attachments`, `create-quotation` (`--payload` for `quotation.createDraft`; include `amountWithoutGst` and `gstAmount`), `update-quotation` (`--payload`; updates a `DRAFT` or `REJECTED` quotation), `delete-quotation`, `submit-quotation`, `approve-quotation`, `reject-quotation`, `download-quotation-pdf` (`original`, `staff`, or `final`) |
288
- | **Customer invoices** | `check-customer-invoice-readiness`, `list-eligible-customer-invoice-budgets`, `list-customer-invoices` (global/project/budget filters plus summary metrics; `--sortBy totalInvoiceAmount` sorts provider-confirmed totals including GST, with unknown amounts last), `get-customer-invoice`, `get-customer-invoice-email-context`, `create-customer-invoice`, `discard-customer-invoice`, `delete-customer-invoice`, `void-customer-invoice`, `approve-customer-invoice` (admin), `reject-customer-invoice` (admin), `send-customer-invoice-to-contact-person`, `download-customer-invoice-pdf` (non-admins cannot download while approval is pending), `sync-customer-invoice` |
320
+ | **Customer invoices** | `check-customer-invoice-readiness`, `list-eligible-customer-invoice-budgets`, `list-customer-invoices` (global/project/budget filters plus summary metrics; `--sortBy totalInvoiceAmount` sorts provider-confirmed totals including GST, with unknown amounts last), `get-customer-invoice`, `get-customer-invoice-email-context`, `preview-qbo-customer-invoice`, `import-qbo-customer-invoice` (BB-only link with reviewed token), `create-customer-invoice`, `discard-customer-invoice`, `delete-customer-invoice`, `void-customer-invoice`, `approve-customer-invoice` (admin), `reject-customer-invoice` (admin), `send-customer-invoice-to-contact-person`, `download-customer-invoice-pdf` (non-admins cannot download while approval is pending), `sync-customer-invoice` |
289
321
  | **Approvals** | `list-approvals` / `get-pending-approvals` (`--type budget\|supplier\|bill\|quotation\|customer_invoice\|all`), `approve-bill` / `reject-bill` (send reply email), `approve-budget` / `reject-budget` (send reply email), `approve-supplier` / `reject-supplier` (send reply email), `approve-quotation` / `reject-quotation`, `approve-customer-invoice` / `reject-customer-invoice` |
290
322
  | **Companies & projects** | `list-companies`, `get-company`, `create-company`, `update-company` (`--payload`), `delete-company`, `list-projects`, `get-project` (its budget overview returns `totalRevenue`, calculated only from Estimate Accepted and Estimate Closed budgets), `get-project-hub-status`, `setup-project-hub`, `sync-project-hub-commercial-documents`, `create-project` (required: `--name`, `--companyId`, `--contactPersonId`, `--insideSalesId`, `--businessDevelopmentId`, `--venue`, `--startDate` as ISO datetime for project/window start; when `--asanaTaskId` is omitted, the CLI searches open Asana lead tasks in the Deals project, prompts for one of the top five matches, and resolves Slack channel fields from the selected deal card; optional `--asanaSearch`, `--pax`, `--endDate`, `--description`, `--projectManagerId`; always requests QBO project import like the web app), `update-project` (`--payload` with `dateRange.from` / `dateRange.to` for the project window; optional `projectManagerId` and `requestQboAccountantNotification` in JSON), `delete-project`, `check-project-reconciliation <id>` (runs the web app's live checks without changing status), `reconcile-project <id>` (reruns checks transactionally and marks an eligible project `RECONCILED`), `complete-project <id>` (reruns validation, marks a reconciled project `COMPLETED`, and queues QuickBooks placeholder cleanup), `import-qbo-project`, `update-project-status` (`<id>` `<status>`: `PITCH` \| `WON` \| `COMPLETED` \| `RECONCILED` \| `LOST`; close-out follows `WON` → `RECONCILED` → `COMPLETED`, and reconciliation requires every accepted/closed budget line to have an Approved/Paid bill or claim or be explicitly Not Utilized; marking `COMPLETED` queues deletion of every remaining project placeholder bill from QuickBooks; for `PITCH` → `WON` also pass a Budget Builder user `--projectManagerId` or `--projectManagerEmail`; when marking `WON` without an accepted/closed budget or proof, pass `--wonOverrideReason`) |
291
323
  | **Contacts** | `list-contacts`, `create-contact-person` (`--payload`), `update-contact-person` (`--payload`) |
@@ -3643,6 +3643,112 @@
3643
3643
  "effects": [],
3644
3644
  "legacyTarget": "get-customer-invoice-email-context"
3645
3645
  },
3646
+ {
3647
+ "path": ["customer-invoice", "preview-qbo"],
3648
+ "legacyAliases": [
3649
+ "preview-qbo-customer-invoice",
3650
+ "preview_qbo_customer_invoice"
3651
+ ],
3652
+ "summary": "Preview an existing QuickBooks invoice for a BB budget using read-only QBO access. Arguments: <budgetId> <qboInvoiceId>.",
3653
+ "globalOptions": [
3654
+ {
3655
+ "name": "--help",
3656
+ "description": "Show help for this command."
3657
+ },
3658
+ {
3659
+ "name": "--quiet",
3660
+ "description": "Suppress non-error diagnostics."
3661
+ },
3662
+ {
3663
+ "name": "--debug",
3664
+ "description": "Emit sanitized diagnostic traces."
3665
+ },
3666
+ {
3667
+ "name": "--api-url",
3668
+ "description": "Override the Budget Builder API base URL."
3669
+ },
3670
+ {
3671
+ "name": "--no-browser",
3672
+ "description": "Print the authorization URL without opening a browser."
3673
+ },
3674
+ {
3675
+ "name": "--allow-state-change",
3676
+ "description": "Allow a Budget Builder state change in non-interactive use."
3677
+ },
3678
+ {
3679
+ "name": "--allow-email",
3680
+ "description": "Allow sending email in non-interactive use."
3681
+ },
3682
+ {
3683
+ "name": "--allow-external-write",
3684
+ "description": "Allow writes to external systems in non-interactive use."
3685
+ },
3686
+ {
3687
+ "name": "--allow-delete",
3688
+ "description": "Allow deleting data in non-interactive use."
3689
+ },
3690
+ {
3691
+ "name": "--allow-financial-write",
3692
+ "description": "Allow financial-record changes in non-interactive use."
3693
+ }
3694
+ ],
3695
+ "argumentMode": "legacy-passthrough",
3696
+ "effects": [],
3697
+ "legacyTarget": "preview-qbo-customer-invoice"
3698
+ },
3699
+ {
3700
+ "path": ["customer-invoice", "import-qbo"],
3701
+ "legacyAliases": [
3702
+ "import-qbo-customer-invoice",
3703
+ "import_qbo_customer_invoice"
3704
+ ],
3705
+ "summary": "Link an existing QuickBooks invoice in BB only after reviewing preview-qbo. Arguments: <budgetId> <qboInvoiceId> --token <previewToken>. Supports closed estimates; no QBO writes or emails.",
3706
+ "globalOptions": [
3707
+ {
3708
+ "name": "--help",
3709
+ "description": "Show help for this command."
3710
+ },
3711
+ {
3712
+ "name": "--quiet",
3713
+ "description": "Suppress non-error diagnostics."
3714
+ },
3715
+ {
3716
+ "name": "--debug",
3717
+ "description": "Emit sanitized diagnostic traces."
3718
+ },
3719
+ {
3720
+ "name": "--api-url",
3721
+ "description": "Override the Budget Builder API base URL."
3722
+ },
3723
+ {
3724
+ "name": "--no-browser",
3725
+ "description": "Print the authorization URL without opening a browser."
3726
+ },
3727
+ {
3728
+ "name": "--allow-state-change",
3729
+ "description": "Allow a Budget Builder state change in non-interactive use."
3730
+ },
3731
+ {
3732
+ "name": "--allow-email",
3733
+ "description": "Allow sending email in non-interactive use."
3734
+ },
3735
+ {
3736
+ "name": "--allow-external-write",
3737
+ "description": "Allow writes to external systems in non-interactive use."
3738
+ },
3739
+ {
3740
+ "name": "--allow-delete",
3741
+ "description": "Allow deleting data in non-interactive use."
3742
+ },
3743
+ {
3744
+ "name": "--allow-financial-write",
3745
+ "description": "Allow financial-record changes in non-interactive use."
3746
+ }
3747
+ ],
3748
+ "argumentMode": "legacy-passthrough",
3749
+ "effects": ["state-change", "financial-write"],
3750
+ "legacyTarget": "import-qbo-customer-invoice"
3751
+ },
3646
3752
  {
3647
3753
  "path": ["customer-invoice", "create"],
3648
3754
  "legacyAliases": ["create-customer-invoice", "create_customer_invoice"],
@@ -593,6 +593,22 @@ Legacy aliases: `get-customer-invoice-email-context`, `get_customer_invoice_emai
593
593
 
594
594
  Effects: none.
595
595
 
596
+ ## `bb customer-invoice preview-qbo`
597
+
598
+ Preview an existing QuickBooks invoice for a BB budget using read-only QBO access. Arguments: <budgetId> <qboInvoiceId>.
599
+
600
+ Legacy aliases: `preview-qbo-customer-invoice`, `preview_qbo_customer_invoice`.
601
+
602
+ Effects: none.
603
+
604
+ ## `bb customer-invoice import-qbo`
605
+
606
+ Link an existing QuickBooks invoice in BB only after reviewing preview-qbo. Arguments: <budgetId> <qboInvoiceId> --token <previewToken>. Supports closed estimates; no QBO writes or emails.
607
+
608
+ Legacy aliases: `import-qbo-customer-invoice`, `import_qbo_customer_invoice`.
609
+
610
+ Effects: state-change, financial-write.
611
+
596
612
  ## `bb customer-invoice create`
597
613
 
598
614
  Create Customer Invoice.
package/dist/index.js CHANGED
@@ -22134,6 +22134,19 @@ var getCustomerInvoiceEmailContext = async (batchId) => {
22134
22134
  });
22135
22135
  out(result);
22136
22136
  };
22137
+ var previewQboCustomerInvoice = async (input2) => {
22138
+ const result = await api2.customerInvoice.previewExistingInvoice.mutate(input2);
22139
+ out(result);
22140
+ };
22141
+ var importQboCustomerInvoice = async (input2) => {
22142
+ await confirmCurrentCommand({
22143
+ action: "Link existing QuickBooks invoice",
22144
+ target: `QuickBooks invoice ${input2.qboInvoiceId} to budget ${input2.budgetId}`,
22145
+ details: "records the reviewed invoice in Budget Builder only; QuickBooks access is read-only; preserves original evidence and revalidates the preview token"
22146
+ });
22147
+ const result = await api2.customerInvoice.linkExistingInvoice.mutate(input2);
22148
+ out(result);
22149
+ };
22137
22150
  var createCustomerInvoice = async (raw) => {
22138
22151
  const input2 = parseCreateCustomerInvoicePayload(raw);
22139
22152
  await assertSensitiveWorkflowConfirmed({
@@ -23166,6 +23179,7 @@ var externalWriteTargets = new Set([
23166
23179
  "void-customer-invoice"
23167
23180
  ]);
23168
23181
  var financialWriteTargets = new Set([
23182
+ "import-qbo-customer-invoice",
23169
23183
  "approve-bill",
23170
23184
  "approve-customer-invoice",
23171
23185
  "complete-project",
@@ -23223,6 +23237,12 @@ var effectsFor = (legacyTarget) => {
23223
23237
  };
23224
23238
  var titleCase = (value) => value.split("-").map((word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`).join(" ");
23225
23239
  var summaryFor = (legacyTarget) => {
23240
+ if (legacyTarget === "preview-qbo-customer-invoice") {
23241
+ return "Preview an existing QuickBooks invoice for a BB budget using read-only QBO access. Arguments: <budgetId> <qboInvoiceId>.";
23242
+ }
23243
+ if (legacyTarget === "import-qbo-customer-invoice") {
23244
+ return "Link an existing QuickBooks invoice in BB only after reviewing preview-qbo. Arguments: <budgetId> <qboInvoiceId> --token <previewToken>. Supports closed estimates; no QBO writes or emails.";
23245
+ }
23226
23246
  if (legacyTarget === "whoami")
23227
23247
  return "Show the active API-key identity.";
23228
23248
  if (legacyTarget === "create-bill") {
@@ -23408,6 +23428,14 @@ var registry2 = [
23408
23428
  "email-context",
23409
23429
  "get"
23410
23430
  ]),
23431
+ legacyCommand("preview-qbo-customer-invoice", [
23432
+ "customer-invoice",
23433
+ "preview-qbo"
23434
+ ]),
23435
+ legacyCommand("import-qbo-customer-invoice", [
23436
+ "customer-invoice",
23437
+ "import-qbo"
23438
+ ]),
23411
23439
  legacyCommand("create-customer-invoice", ["customer-invoice", "create"]),
23412
23440
  legacyCommand("discard-customer-invoice", ["customer-invoice", "discard"]),
23413
23441
  legacyCommand("delete-customer-invoice", ["customer-invoice", "delete"]),
@@ -23927,6 +23955,8 @@ Customer invoices
23927
23955
  No scope flag lists invoices globally. --projectId uses the project scope. A positional budgetId is a backward-compatible shortcut for --budgetIds.
23928
23956
  get-customer-invoice <batchId>
23929
23957
  get-customer-invoice-email-context <batchId> Contact, required CC, budget, and project context for composing an invoice email.
23958
+ preview-qbo-customer-invoice <budgetId> <qboInvoiceId> Preview linking an existing QBO transaction (numeric ID, not invoice number); works on accepted/closed estimates.
23959
+ import-qbo-customer-invoice <budgetId> <qboInvoiceId> --token <previewToken> Confirm the reviewed BB-only link; requires --allow-state-change --allow-financial-write in automation.
23930
23960
  create-customer-invoice --payload '<json>' budgetId + one split with label, percentage, and dueDate.
23931
23961
  discard-customer-invoice <batchId> Discard a reserved CREATING batch with no created QBO invoices.
23932
23962
  delete-customer-invoice <batchId> Delete QBO invoices and the local batch.
@@ -24371,7 +24401,7 @@ Waiting for approval\u2026
24371
24401
  }
24372
24402
  configureAccessToken(accessToken);
24373
24403
  }
24374
- if (!resolvedCommand.isLegacyAlias && cmd !== "create-bill" && resolvedCommand.command.effects.length > 0) {
24404
+ if (!resolvedCommand.isLegacyAlias && cmd !== "create-bill" && cmd !== "import-qbo-customer-invoice" && resolvedCommand.command.effects.length > 0) {
24375
24405
  await confirmCurrentCommand({
24376
24406
  action: resolvedCommand.command.path.join(" "),
24377
24407
  target: commandConfirmationTarget(resolvedCommand),
@@ -25123,6 +25153,26 @@ Waiting for approval\u2026
25123
25153
  await getCustomerInvoiceEmailContext(batchId);
25124
25154
  break;
25125
25155
  }
25156
+ case "preview-qbo-customer-invoice":
25157
+ case "import-qbo-customer-invoice": {
25158
+ const [budgetId, qboInvoiceId] = positional;
25159
+ if (!budgetId?.trim() || !qboInvoiceId || positional.length !== 2) {
25160
+ throw new Error(`${cmd} requires <budgetId> <qboInvoiceId>`);
25161
+ }
25162
+ if (!/^\d+$/.test(qboInvoiceId)) {
25163
+ throw new Error("qboInvoiceId must be the numeric QuickBooks transaction ID, not the invoice number");
25164
+ }
25165
+ if (cmd === "preview-qbo-customer-invoice") {
25166
+ await previewQboCustomerInvoice({ budgetId, qboInvoiceId });
25167
+ } else {
25168
+ const token = getFlag(flags, "token");
25169
+ if (!token || !/^[a-f0-9]{64}$/.test(token)) {
25170
+ throw new Error("import-qbo-customer-invoice requires --token <64-character preview token> from a reviewed preview-qbo result");
25171
+ }
25172
+ await importQboCustomerInvoice({ budgetId, qboInvoiceId, token });
25173
+ }
25174
+ break;
25175
+ }
25126
25176
  case "create-customer-invoice": {
25127
25177
  const payloadRaw = getFlag(flags, "payload");
25128
25178
  if (!payloadRaw) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/bb",
3
- "version": "2.31.0",
3
+ "version": "2.31.1",
4
4
  "description": "Budget Builder CLI for AI agents — manage budgets, bills, claims, quotations, and customer invoices with explicit workflow previews for sensitive changes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",