@go-labs-sg/bb 2.14.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -229,7 +229,7 @@ Project and budget query payloads keep the Asana identities explicit: `asanaTask
229
229
  | **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`), `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`, `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`) |
230
230
  | **Bills / claims** | `list-bills` (`--isClaimable false` for bills, `--isClaimable true` for claims, omit for both), `list-claims` (claims only), `validate-bill-selection` (`--payload`; runs the same supplier, line-item, and quotation-coverage checks as the web flow; `alreadyPaid` never bypasses them), `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 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` |
231
231
  | **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`) |
232
- | **Customer invoices** | `check-customer-invoice-readiness`, `list-eligible-customer-invoice-budgets`, `list-customer-invoices` (global/project/budget filters plus summary metrics), `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`, `sync-customer-invoice` |
232
+ | **Customer invoices** | `check-customer-invoice-readiness`, `list-eligible-customer-invoice-budgets`, `list-customer-invoices` (global/project/budget filters plus summary metrics), `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` |
233
233
  | **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` |
234
234
  | **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`) |
235
235
  | **Contacts** | `list-contacts`, `create-contact-person` (`--payload`), `update-contact-person` (`--payload`) |
@@ -3452,7 +3452,7 @@
3452
3452
  "download-customer-invoice-pdf",
3453
3453
  "download_customer_invoice_pdf"
3454
3454
  ],
3455
- "summary": "Download Customer Invoice Pdf.",
3455
+ "summary": "Download a customer invoice PDF when permitted.",
3456
3456
  "globalOptions": [
3457
3457
  {
3458
3458
  "name": "--help",
@@ -610,7 +610,7 @@ Effects: state-change, email.
610
610
 
611
611
  ## `bb customer-invoice pdf download`
612
612
 
613
- Download Customer Invoice Pdf.
613
+ Download a customer invoice PDF when permitted.
614
614
 
615
615
  Legacy aliases: `download-customer-invoice-pdf`, `download_customer_invoice_pdf`.
616
616
 
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { BUDGET_BUILDER_API_BASE_URL, configureBudgetBuilderApiBaseUrl, DEFAULT_
7
7
  import { consumeCliQuietFlags, logCliAction, sanitizeFlagsForTrace, setCliQuiet, shouldLogCliActions, } from "./cli-trace.js";
8
8
  import { addBudgetItems, approveBill, approveBudget, approveCustomerInvoice, approveQuotation, approveSupplier, archiveItemsByIds, checkCustomerInvoiceReadiness, checkProjectReconciliation, cleanupStagedBillAttachments, cleanupStagedQuotationAttachments, completeProject, createApiKeyForUser, createBillApproval, createBillFromPayload, createBudgetApproval, createBudgetCategory, createBudgetFromPayload, createCompany, createContactPersonFromPayload, createCustomerInvoice, createEstimate, createInboundSource, createItemCategory, createItemFromPayload, createPlaceholderBillForBudgetItem, createProject, createQuotationFromPayload, createSupplierCertification, createSupplierFromPayload, createSupplierPaymentMethod, createSupplierRoleOption, createSupplierTagOption, createUser, deleteBillById, deleteBudgetById, deleteBudgetCategory, deleteBudgetCommission, deleteBudgetDiscount, deleteCompanyById, deleteCustomerInvoice, deleteItemById, deleteItemCategoriesByIds, deleteProjectById, deleteQuotationById, deleteSuppliersByIds, discardCreatingCustomerInvoice, downloadCustomerInvoicePdf, downloadQuotationPdf, getApprovedBudgets, getBillAttachments, getBillDetails, getBudget, getBudgetCategories, getBudgetCategoryBenchmarks, getBudgetDetails, getBudgetItemsOnly, getBudgetVersions, getCompany, getCustomerInvoice, getCustomerInvoiceEmailContext, getDashboard, getErrorMetrics, getEstimatePerformance, getFinancialOverview, getInboundSource, getInboundSubmission, getItem, getItemPricingHistory, getMonthlyMetrics, getProject, getProjectHubStatus, getQuotationDetails, getRecentErrors, getSupplierAnalytics, getSupplierDetails, getSupplierPricingHistory, getSystemOverview, getUserPerformance, importQuickBooksProjectId, listApiKeysForUser, listApprovals, listBills, listBudgets, listCompanies, listContacts, listCustomerInvoices, listEligibleCustomerInvoiceBudgets, listInboundSources, listInboundSubmissions, listIntegrationOperations, listItemCategories, listItems, listProjects, listQuotations, listSuppliers, listUsers, markBudgetWonWithProof, patchBillInvoiceNumber, patchBillPayment, reactivateSuppliersByIds, reconcileProject, rejectBill, rejectBudget, rejectCustomerInvoice, rejectQuotation, rejectSupplier, removeBudgetItem, renameBudgetVersion, reorderBudgetItemsCli, restoreBudgetVersion, retryIntegrationOperation, revokeApiKeyForUser, saveInboundAutomationConfig, saveInboundMappingVersion, sendCustomerInvoiceToContactPersonFromPayload, sendEstimateToContactPersonFromPayload, setBudgetItemsNotUtilized, setupProjectHub, stageBillAttachmentsFromPaths, submitQuotation, syncCustomerInvoice, syncProjectHubCommercialDocuments, updateBillFromPayload, updateBillPaymentEvidenceFromPayload, updateBillStatus, updateBudgetCategory, updateBudgetCommissionFromPayload, updateBudgetDiscountFromPayload, updateBudgetFromPayload, updateBudgetItem, updateBudgetItemSupplierCli, updateBudgetStatus, updateCompanyFromPayload, updateContactPersonFromPayload, updateInboundProcessing, updateInboundSource, updateItemCategory, updateItemFromPayload, updateProjectFromPayload, updateProjectStatus, updateQuotationFromPayload, updateSupplierFromPayload, uploadBillAttachmentFromPath, uploadBillAttachmentsFromPaths, uploadBillDocumentsFromPaths, uploadBudgetAttachmentFromPath, uploadQuotationAttachmentFromPath, validateBillSelectionFromPayload, voidCustomerInvoice, whoAmI, } from "./commands.js";
9
9
  import { getFlag, parseArgs } from "./parse-args.js";
10
- import { billStatusesForUpdateHelp, budgetStatusesForHelp, isBudgetStatusUpdate, parseApprovalTypeFlag, parseBillStatusForUpdate, parseCommaSeparatedBillStatuses, parseCommaSeparatedBudgetStatuses, parseCommaSeparatedCustomerInvoiceStatuses, parseCommaSeparatedIds, parseCommaSeparatedQuotationStatuses, parseCommaSeparatedSupplierStatuses, parseOptionalBillListSortBy, parseOptionalBillListSortDir, parseOptionalDashboardRole, parseOptionalDeals, parseOptionalErrorSeverity, parseOptionalErrorStatus, parseOptionalExtendedProjectStatus, parseOptionalFinancialRole, parseOptionalSupplierAnalyticsTimeFrame, parseOptionalTimeFrame, parseProjectStatusForUpdate, parseUserRole, projectStatusesForHelp, userRolesForHelp, } from "./parse-cli-enums.js";
10
+ import { billStatusesForUpdateHelp, budgetStatusesForHelp, isBudgetStatusUpdate, parseApprovalTypeFlag, parseBillStatusForUpdate, parseCommaSeparatedBillStatuses, parseCommaSeparatedBudgetStatuses, parseCommaSeparatedCustomerInvoiceStatuses, parseCommaSeparatedIds, parseCommaSeparatedIntegrationOperationStatuses, parseCommaSeparatedQuotationStatuses, parseCommaSeparatedSupplierStatuses, parseOptionalBillListSortBy, parseOptionalBillListSortDir, parseOptionalDashboardRole, parseOptionalDeals, parseOptionalErrorSeverity, parseOptionalErrorStatus, parseOptionalExtendedProjectStatus, parseOptionalFinancialRole, parseOptionalSupplierAnalyticsTimeFrame, parseOptionalTimeFrame, parseProjectStatusForUpdate, parseUserRole, projectStatusesForHelp, userRolesForHelp, } from "./parse-cli-enums.js";
11
11
  import { parseJsonFlag, parseOptionalNumber as parseOptNum, } from "./parse-json-flag.js";
12
12
  import { createCompletionScript, createHumanHelp, resolveCommand, } from "./registry/index.js";
13
13
  import { CliRuntimeError, clearActiveCliSession, confirmCurrentCommand, createProcessRuntime, emitCommandError, emitCommandResult, setActiveCliSession, } from "./runtime/index.js";
@@ -45,21 +45,6 @@ function parseCustomerInvoiceSortBy(value) {
45
45
  }
46
46
  return value;
47
47
  }
48
- const INTEGRATION_OPERATION_STATUSES = [
49
- "PENDING",
50
- "PROCESSING",
51
- "FAILED",
52
- "COMPLETED",
53
- ];
54
- function parseIntegrationOperationStatus(value) {
55
- if (value === undefined)
56
- return undefined;
57
- const normalized = value.toUpperCase();
58
- if (!INTEGRATION_OPERATION_STATUSES.includes(normalized)) {
59
- throw new Error(`Invalid --status "${value}". Use one of: ${INTEGRATION_OPERATION_STATUSES.join(", ")}.`);
60
- }
61
- return normalized;
62
- }
63
48
  function parseCommercialRejectionProjectStatus(flags) {
64
49
  const raw = flags.projectStatusOnCommercialRejection;
65
50
  if (raw === undefined)
@@ -235,7 +220,8 @@ Bills
235
220
  get-bill-details <billId>
236
221
 
237
222
  Quotations
238
- list-quotations [--projectId] [--budgetId] [--supplierId] [--status CSV] [--requesterIds CSV] [--search] [--page] [--pageSize]
223
+ list-quotations [--projectId] [--budgetId] [--supplierId] [--status CSV] [--createdBy CSV] [--search] [--page] [--perPage]
224
+ Legacy aliases --requesterIds and --pageSize remain supported.
239
225
  get-quotation <quotationId>
240
226
  upload-quotation-attachment <projectId> <filePath> Upload one PDF, JPEG, or PNG up to 20MB; returns attachment JSON for a quotation payload.
241
227
  cleanup-staged-quotation-attachments <projectId> --keys <csv> Delete unattached staged quotation uploads.
@@ -344,7 +330,7 @@ Errors
344
330
  get-error-metrics (admin)
345
331
 
346
332
  Automation (admin)
347
- list-integration-operations [--destination] [--status PENDING|PROCESSING|FAILED|COMPLETED] [--page] [--perPage]
333
+ list-integration-operations [--search] [--destination <provider,...>] [--status <PENDING|PROCESSING|FAILED|COMPLETED,...>] [--page] [--perPage]
348
334
  retry-integration-operation <operationId> [--confirmExternalStateReconciled true|false]
349
335
 
350
336
  Historical / benchmarks
@@ -1224,19 +1210,19 @@ export async function runCli(argv = process.argv, runtime = createProcessRuntime
1224
1210
  }
1225
1211
  case "list-quotations": {
1226
1212
  const statusRaw = getFlag(flags, "status");
1213
+ const createdByRaw = getFlag(flags, "createdBy") ?? getFlag(flags, "requesterIds");
1214
+ const perPageRaw = getFlag(flags, "perPage") ?? getFlag(flags, "pageSize");
1227
1215
  await listQuotations({
1228
1216
  projectId: getFlag(flags, "projectId"),
1229
1217
  budgetId: getFlag(flags, "budgetId"),
1230
1218
  supplierId: getFlag(flags, "supplierId"),
1231
1219
  status: parseCommaSeparatedQuotationStatuses(statusRaw),
1232
- requesterIds: parseCommaSeparatedIds(getFlag(flags, "requesterIds")),
1220
+ createdBy: parseCommaSeparatedIds(createdByRaw),
1233
1221
  search: getFlag(flags, "search"),
1234
1222
  page: getFlag(flags, "page") !== undefined
1235
1223
  ? Number(getFlag(flags, "page"))
1236
1224
  : undefined,
1237
- pageSize: getFlag(flags, "pageSize") !== undefined
1238
- ? Number(getFlag(flags, "pageSize"))
1239
- : undefined,
1225
+ perPage: perPageRaw !== undefined ? Number(perPageRaw) : undefined,
1240
1226
  });
1241
1227
  break;
1242
1228
  }
@@ -2009,10 +1995,11 @@ export async function runCli(argv = process.argv, runtime = createProcessRuntime
2009
1995
  }
2010
1996
  case "list-integration-operations": {
2011
1997
  await listIntegrationOperations({
2012
- destination: getFlag(flags, "destination"),
1998
+ destination: parseCommaSeparatedIds(getFlag(flags, "destination")),
2013
1999
  page: parsePositiveIntFlag(getFlag(flags, "page"), "--page") ?? 1,
2014
2000
  perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage") ?? 25,
2015
- status: parseIntegrationOperationStatus(getFlag(flags, "status")),
2001
+ search: getFlag(flags, "search"),
2002
+ status: parseCommaSeparatedIntegrationOperationStatuses(getFlag(flags, "status")),
2016
2003
  });
2017
2004
  break;
2018
2005
  }
@@ -26,6 +26,12 @@ const SUPPLIER_ANALYTICS_TF = new Set([
26
26
  const SUPPLIER_APPROVAL_STATUS_VALUES = new Set(Object.values(ExtendedApprovalStatus));
27
27
  const EXT_ERROR_SEVERITY_VALUES = new Set(Object.values(ExtendedErrorSeverity));
28
28
  const EXT_ERROR_STATUS_VALUES = new Set(Object.values(ExtendedErrorStatus));
29
+ const INTEGRATION_OPERATION_STATUS_VALUES = new Set([
30
+ "PENDING",
31
+ "PROCESSING",
32
+ "FAILED",
33
+ "COMPLETED",
34
+ ]);
29
35
  export function isBudgetStatusListToken(s) {
30
36
  if (s === "ALL")
31
37
  return true;
@@ -139,6 +145,22 @@ export function parseCommaSeparatedIds(raw) {
139
145
  .filter(Boolean);
140
146
  return ids.length > 0 ? ids : undefined;
141
147
  }
148
+ export function parseCommaSeparatedIntegrationOperationStatuses(raw) {
149
+ if (raw === undefined)
150
+ return undefined;
151
+ const statuses = raw
152
+ .split(",")
153
+ .map((value) => value.trim().toUpperCase())
154
+ .filter(Boolean);
155
+ if (statuses.length === 0)
156
+ return undefined;
157
+ for (const status of statuses) {
158
+ if (!INTEGRATION_OPERATION_STATUS_VALUES.has(status)) {
159
+ throw new Error(`Invalid integration operation status "${status}". Use one or more of: ${[...INTEGRATION_OPERATION_STATUS_VALUES].join(", ")}.`);
160
+ }
161
+ }
162
+ return statuses;
163
+ }
142
164
  export function parseCommaSeparatedBudgetStatuses(raw) {
143
165
  if (raw === undefined)
144
166
  return undefined;
@@ -192,6 +192,9 @@ const titleCase = (value) => value
192
192
  const summaryFor = (legacyTarget) => {
193
193
  if (legacyTarget === "whoami")
194
194
  return "Show the active API-key identity.";
195
+ if (legacyTarget === "download-customer-invoice-pdf") {
196
+ return "Download a customer invoice PDF when permitted.";
197
+ }
195
198
  if (legacyTarget.startsWith("list-")) {
196
199
  return `List ${legacyTarget.slice("list-".length).replaceAll("-", " ")}.`;
197
200
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/bb",
3
- "version": "2.14.0",
3
+ "version": "2.16.0",
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",
@@ -98,6 +98,7 @@ This table highlights the role-sensitive command groups an agent is most likely
98
98
  | `bb bill status update ... APPROVED` or `... PAID` | `ADMIN`; state, integration, payment-reference, and payment-proof requirements still apply. |
99
99
  | `bb bill payment patch`; `bb bill invoice-number patch` | `ACCOUNTING_TEAM` or `ADMIN`, with the procedure's bill-state constraints. |
100
100
  | `bb customer-invoice approve` and `bb customer-invoice reject` | `ADMIN` batch operations selected by batch ID; the API resolves the internal pending Admin approval record. |
101
+ | `bb customer-invoice pdf download` | `ADMIN` for pending-approval batches; other roles may download after pending approval. QuickBooks sync errors cannot be previewed or downloaded. |
101
102
  | Dashboard/performance, supplier analytics, errors, integration operations | `ADMIN`. |
102
103
  | `bb item category create`, `bb item category update`, `bb item category delete`, `bb supplier delete`, `bb supplier reactivate`, and `bb budget item placeholder-bill create` | `ADMIN`. |
103
104
 
@@ -163,7 +164,7 @@ Admin inspecting a failed integration before a confirmed retry:
163
164
 
164
165
  ```bash
165
166
  bb auth whoami
166
- bb integration operation list --status FAILED
167
+ bb integration operation list --status FAILED,PENDING --search "QuickBooks"
167
168
  bb integration operation retry <operation-id>
168
169
  ```
169
170