@go-labs-sg/bb 2.10.3 → 2.11.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
@@ -193,15 +193,31 @@ Every canonical command is classified by effect: `state-change`, `email`, `exter
193
193
 
194
194
  **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 sends the raw PDF returned by QuickBooks together with the standard terms and Budget Builder budget attachments, then marks the budget `ESTIMATE_SENT`. The command requires interactive `CONFIRM`; inspect the budget, recipients, and HTML first, and do not retry blindly after an ambiguous delivery failure.
195
195
 
196
- **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` uses the same protected email workflow as the web composer and marks a successful 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 payment mark-paid` requires an admin, a payment date, and a local PDF/GIF/JPEG/PNG proof up to 20MB; it uploads the proof, creates a QuickBooks Payment for the live outstanding balance when necessary, attempts to attach the proof in QuickBooks, records the result in Budget Builder, and sets the invoice status to `PAID`. `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.
196
+ **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` uses the same protected email workflow as the web composer and marks a successful 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.
197
197
 
198
198
  For agent-driven invoice work, use this read-before-write sequence:
199
199
 
200
200
  1. Run `bb customer-invoice get <batchId>` and, when composing email, `bb customer-invoice email-context get <batchId>`.
201
201
  2. For creation, run `bb customer-invoice eligible-budget list <projectId>` or `bb customer-invoice readiness check <budgetId>` first.
202
- 3. State the exact invoice or batch, current status, intended mutation, QuickBooks effect, email recipients, proof file, and estimate-closing/reopening effect to the user.
202
+ 3. State the exact invoice or batch, current status, intended mutation, QuickBooks effect, email recipients, and estimate-closing/reopening effect to the user.
203
203
  4. Wait for explicit confirmation, then run the sensitive command interactively and verify its JSON result. Do not treat the runtime `CONFIRM` prompt as user authorization.
204
204
 
205
+ ### Project Hub operations
206
+
207
+ Project Hub setup and commercial-document reconciliation are durable, asynchronous Budget Builder operations. Read status before requesting either mutation:
208
+
209
+ ```bash
210
+ bb project hub status <project-id>
211
+ bb project hub setup <project-id>
212
+ bb project hub sync <project-id>
213
+ ```
214
+
215
+ `status` reports eligibility, whether setup can be requested, the current setup status, the latest setup error, and the client Hub URL. `setup` uses the same eligibility and accepted-estimate checks as the web button and may create the operational Asana project before provisioning Drive and Project Hub. `sync` requires an existing `READY` native-layout integration and queues a complete reconciliation of accepted estimates, commercial attachments, win proofs, and eligible customer invoices into Asana and Drive.
216
+
217
+ Both mutation commands write external state asynchronously and therefore require `state-change` and `external-write` confirmation. In non-interactive use, pass both `--allow-state-change` and `--allow-external-write` only after obtaining explicit user approval. A successful `queued` response means the durable operation was accepted. `status` tracks setup readiness; the sync response returns its outbox operation ID, which an Admin can inspect with the integration-operation commands when completion or failure diagnostics are required.
218
+
219
+ The fleet-wide legacy-layout migration is intentionally not exposed through the normal CLI. It remains a restricted deployment script that defaults to a read-only dry run.
220
+
205
221
  ### Command overview
206
222
 
207
223
  The table below is a behavior index keyed by the compatibility dispatcher's legacy handler labels. It is not invocation syntax. AI agents and new automation must resolve and use the grouped v2 route from `bb help`, [`command-reference.md`](./command-reference.md), or [`command-manifest.json`](./command-manifest.json); never copy a flat label from this table into a new command.
@@ -213,9 +229,9 @@ Project and budget query payloads keep the Asana identities explicit: `asanaTask
213
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`) |
214
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` |
215
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`) |
216
- | **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`, `mark-customer-invoice-paid` (admin; high-level proof upload + QBO Payment workflow), `download-customer-invoice-payment-proof`, `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`, `sync-customer-invoice` |
217
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` |
218
- | **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), `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`) |
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`) |
219
235
  | **Contacts** | `list-contacts`, `create-contact-person` (`--payload`), `update-contact-person` (`--payload`) |
220
236
  | **Suppliers & items** | `list-suppliers` (defaults to active suppliers, `--perPage 10`, sorted by `createdAt` desc; supports `--name`, `--sortBy` for scalar supplier fields, `--sortDir`, `--createdBy`, `--gstRegistered`, `--status`, `--supplierTags`, `--active false` for archived suppliers), `create-supplier` (`--payload`; when supplier status is `PENDING_APPROVAL`, also runs `supplier.createSupplierApproval` and `email.sendSupplierApprovalRequestEmail`), `update-supplier` (`--payload`; when supplier status is `PENDING_APPROVAL`, also runs `supplier.createSupplierApproval` and `email.sendSupplierApprovalRequestEmail`), `delete-suppliers` (`--ids` CSV; admin; archives/deactivates related items), `reactivate-suppliers` (`--ids` CSV; admin; reactivates related items), `create-certification` / `create-payment-method` / `create-supplier-role` / `create-supplier-tag` (`--name`), `get-supplier-details` (includes `supplierApprovalSummary` for pending approvers, superseded approvers, and the actual responder/respondedAt metadata), `get-supplier-analytics` (admin), `list-items`, `create-item` (`--payload`), `update-item` (`--payload`), `archive-items` (`--ids` CSV, max 100; Budget Builder only; users can archive their own items, leads/admins any item; blocked by Draft/Pending Approval/Approved budgets), `delete-item` (admin; archives in Budget Builder and makes the item inactive in QuickBooks), `get-item`, `list-item-categories`, `create-item-category` / `update-item-category` / `delete-item-categories` (admin; `--ids` CSV for delete) |
221
237
  | **Dashboard & users** | `whoami` (current API-key owner identity and role), `list-users`, `create-user` (admin; provisions an API-only service identity with no Google sign-in; `--email`, optional `--name`, optional `--role` defaulting to `USER`), `create-api-key` (`--name`; defaults to the caller; admin-only `--userId` for another user; raw key shown once), `list-api-keys` (defaults to the caller; admin-only `--userId` for another user), `revoke-api-key` (key ID; admin-only `--userId` for another user), `get-user-performance`, `get-dashboard`, `get-monthly-metrics`, `get-system-overview`, `get-estimate-performance`, `get-financial-overview` (performance/dashboard commands are admin-only) |
@@ -241,7 +257,7 @@ From the monorepo root (after `bun install`):
241
257
 
242
258
  ```bash
243
259
  export BB_API_KEY=...
244
- bun run golabs -- bb budget list
260
+ bun run packages/budget-builder/cli/src/index.ts budget list
245
261
  ```
246
262
 
247
263
  Or from `packages/budget-builder/cli`:
@@ -3446,104 +3446,6 @@
3446
3446
  "effects": ["state-change", "email"],
3447
3447
  "legacyTarget": "send-customer-invoice-to-contact-person"
3448
3448
  },
3449
- {
3450
- "path": ["customer-invoice", "payment", "mark-paid"],
3451
- "legacyAliases": [
3452
- "mark-customer-invoice-paid",
3453
- "mark_customer_invoice_paid"
3454
- ],
3455
- "summary": "Mark Customer Invoice Paid.",
3456
- "globalOptions": [
3457
- {
3458
- "name": "--help",
3459
- "description": "Show help for this command."
3460
- },
3461
- {
3462
- "name": "--quiet",
3463
- "description": "Suppress non-error diagnostics."
3464
- },
3465
- {
3466
- "name": "--debug",
3467
- "description": "Emit sanitized diagnostic traces."
3468
- },
3469
- {
3470
- "name": "--api-url",
3471
- "description": "Override the Budget Builder API base URL."
3472
- },
3473
- {
3474
- "name": "--allow-state-change",
3475
- "description": "Allow a Budget Builder state change in non-interactive use."
3476
- },
3477
- {
3478
- "name": "--allow-email",
3479
- "description": "Allow sending email in non-interactive use."
3480
- },
3481
- {
3482
- "name": "--allow-external-write",
3483
- "description": "Allow writes to external systems in non-interactive use."
3484
- },
3485
- {
3486
- "name": "--allow-delete",
3487
- "description": "Allow deleting data in non-interactive use."
3488
- },
3489
- {
3490
- "name": "--allow-financial-write",
3491
- "description": "Allow financial-record changes in non-interactive use."
3492
- }
3493
- ],
3494
- "argumentMode": "legacy-passthrough",
3495
- "effects": ["state-change", "external-write", "financial-write"],
3496
- "legacyTarget": "mark-customer-invoice-paid"
3497
- },
3498
- {
3499
- "path": ["customer-invoice", "payment-proof", "download"],
3500
- "legacyAliases": [
3501
- "download-customer-invoice-payment-proof",
3502
- "download_customer_invoice_payment_proof"
3503
- ],
3504
- "summary": "Download Customer Invoice Payment Proof.",
3505
- "globalOptions": [
3506
- {
3507
- "name": "--help",
3508
- "description": "Show help for this command."
3509
- },
3510
- {
3511
- "name": "--quiet",
3512
- "description": "Suppress non-error diagnostics."
3513
- },
3514
- {
3515
- "name": "--debug",
3516
- "description": "Emit sanitized diagnostic traces."
3517
- },
3518
- {
3519
- "name": "--api-url",
3520
- "description": "Override the Budget Builder API base URL."
3521
- },
3522
- {
3523
- "name": "--allow-state-change",
3524
- "description": "Allow a Budget Builder state change in non-interactive use."
3525
- },
3526
- {
3527
- "name": "--allow-email",
3528
- "description": "Allow sending email in non-interactive use."
3529
- },
3530
- {
3531
- "name": "--allow-external-write",
3532
- "description": "Allow writes to external systems in non-interactive use."
3533
- },
3534
- {
3535
- "name": "--allow-delete",
3536
- "description": "Allow deleting data in non-interactive use."
3537
- },
3538
- {
3539
- "name": "--allow-financial-write",
3540
- "description": "Allow financial-record changes in non-interactive use."
3541
- }
3542
- ],
3543
- "argumentMode": "legacy-passthrough",
3544
- "effects": [],
3545
- "legacyTarget": "download-customer-invoice-payment-proof"
3546
- },
3547
3449
  {
3548
3450
  "path": ["customer-invoice", "pdf", "download"],
3549
3451
  "legacyAliases": [
@@ -4334,6 +4236,147 @@
4334
4236
  "effects": [],
4335
4237
  "legacyTarget": "get-project"
4336
4238
  },
4239
+ {
4240
+ "path": ["project", "hub", "status"],
4241
+ "legacyAliases": ["get-project-hub-status", "get_project_hub_status"],
4242
+ "summary": "Get project hub status.",
4243
+ "globalOptions": [
4244
+ {
4245
+ "name": "--help",
4246
+ "description": "Show help for this command."
4247
+ },
4248
+ {
4249
+ "name": "--quiet",
4250
+ "description": "Suppress non-error diagnostics."
4251
+ },
4252
+ {
4253
+ "name": "--debug",
4254
+ "description": "Emit sanitized diagnostic traces."
4255
+ },
4256
+ {
4257
+ "name": "--api-url",
4258
+ "description": "Override the Budget Builder API base URL."
4259
+ },
4260
+ {
4261
+ "name": "--allow-state-change",
4262
+ "description": "Allow a Budget Builder state change in non-interactive use."
4263
+ },
4264
+ {
4265
+ "name": "--allow-email",
4266
+ "description": "Allow sending email in non-interactive use."
4267
+ },
4268
+ {
4269
+ "name": "--allow-external-write",
4270
+ "description": "Allow writes to external systems in non-interactive use."
4271
+ },
4272
+ {
4273
+ "name": "--allow-delete",
4274
+ "description": "Allow deleting data in non-interactive use."
4275
+ },
4276
+ {
4277
+ "name": "--allow-financial-write",
4278
+ "description": "Allow financial-record changes in non-interactive use."
4279
+ }
4280
+ ],
4281
+ "argumentMode": "legacy-passthrough",
4282
+ "effects": [],
4283
+ "legacyTarget": "get-project-hub-status"
4284
+ },
4285
+ {
4286
+ "path": ["project", "hub", "setup"],
4287
+ "legacyAliases": ["setup-project-hub", "setup_project_hub"],
4288
+ "summary": "Setup Project Hub.",
4289
+ "globalOptions": [
4290
+ {
4291
+ "name": "--help",
4292
+ "description": "Show help for this command."
4293
+ },
4294
+ {
4295
+ "name": "--quiet",
4296
+ "description": "Suppress non-error diagnostics."
4297
+ },
4298
+ {
4299
+ "name": "--debug",
4300
+ "description": "Emit sanitized diagnostic traces."
4301
+ },
4302
+ {
4303
+ "name": "--api-url",
4304
+ "description": "Override the Budget Builder API base URL."
4305
+ },
4306
+ {
4307
+ "name": "--allow-state-change",
4308
+ "description": "Allow a Budget Builder state change in non-interactive use."
4309
+ },
4310
+ {
4311
+ "name": "--allow-email",
4312
+ "description": "Allow sending email in non-interactive use."
4313
+ },
4314
+ {
4315
+ "name": "--allow-external-write",
4316
+ "description": "Allow writes to external systems in non-interactive use."
4317
+ },
4318
+ {
4319
+ "name": "--allow-delete",
4320
+ "description": "Allow deleting data in non-interactive use."
4321
+ },
4322
+ {
4323
+ "name": "--allow-financial-write",
4324
+ "description": "Allow financial-record changes in non-interactive use."
4325
+ }
4326
+ ],
4327
+ "argumentMode": "legacy-passthrough",
4328
+ "effects": ["state-change", "external-write"],
4329
+ "legacyTarget": "setup-project-hub"
4330
+ },
4331
+ {
4332
+ "path": ["project", "hub", "sync"],
4333
+ "legacyAliases": [
4334
+ "sync-project-hub-commercial-documents",
4335
+ "sync_project_hub_commercial_documents"
4336
+ ],
4337
+ "summary": "Sync Project Hub Commercial Documents.",
4338
+ "globalOptions": [
4339
+ {
4340
+ "name": "--help",
4341
+ "description": "Show help for this command."
4342
+ },
4343
+ {
4344
+ "name": "--quiet",
4345
+ "description": "Suppress non-error diagnostics."
4346
+ },
4347
+ {
4348
+ "name": "--debug",
4349
+ "description": "Emit sanitized diagnostic traces."
4350
+ },
4351
+ {
4352
+ "name": "--api-url",
4353
+ "description": "Override the Budget Builder API base URL."
4354
+ },
4355
+ {
4356
+ "name": "--allow-state-change",
4357
+ "description": "Allow a Budget Builder state change in non-interactive use."
4358
+ },
4359
+ {
4360
+ "name": "--allow-email",
4361
+ "description": "Allow sending email in non-interactive use."
4362
+ },
4363
+ {
4364
+ "name": "--allow-external-write",
4365
+ "description": "Allow writes to external systems in non-interactive use."
4366
+ },
4367
+ {
4368
+ "name": "--allow-delete",
4369
+ "description": "Allow deleting data in non-interactive use."
4370
+ },
4371
+ {
4372
+ "name": "--allow-financial-write",
4373
+ "description": "Allow financial-record changes in non-interactive use."
4374
+ }
4375
+ ],
4376
+ "argumentMode": "legacy-passthrough",
4377
+ "effects": ["state-change", "external-write"],
4378
+ "legacyTarget": "sync-project-hub-commercial-documents"
4379
+ },
4337
4380
  {
4338
4381
  "path": ["project", "create"],
4339
4382
  "legacyAliases": ["create-project", "create_project"],
@@ -608,22 +608,6 @@ Legacy aliases: `send-customer-invoice-to-contact-person`, `send_customer_invoic
608
608
 
609
609
  Effects: state-change, email.
610
610
 
611
- ## `bb customer-invoice payment mark-paid`
612
-
613
- Mark Customer Invoice Paid.
614
-
615
- Legacy aliases: `mark-customer-invoice-paid`, `mark_customer_invoice_paid`.
616
-
617
- Effects: state-change, external-write, financial-write.
618
-
619
- ## `bb customer-invoice payment-proof download`
620
-
621
- Download Customer Invoice Payment Proof.
622
-
623
- Legacy aliases: `download-customer-invoice-payment-proof`, `download_customer_invoice_payment_proof`.
624
-
625
- Effects: none.
626
-
627
611
  ## `bb customer-invoice pdf download`
628
612
 
629
613
  Download Customer Invoice Pdf.
@@ -760,6 +744,30 @@ Legacy aliases: `get-project`, `get_project`.
760
744
 
761
745
  Effects: none.
762
746
 
747
+ ## `bb project hub status`
748
+
749
+ Get project hub status.
750
+
751
+ Legacy aliases: `get-project-hub-status`, `get_project_hub_status`.
752
+
753
+ Effects: none.
754
+
755
+ ## `bb project hub setup`
756
+
757
+ Setup Project Hub.
758
+
759
+ Legacy aliases: `setup-project-hub`, `setup_project_hub`.
760
+
761
+ Effects: state-change, external-write.
762
+
763
+ ## `bb project hub sync`
764
+
765
+ Sync Project Hub Commercial Documents.
766
+
767
+ Legacy aliases: `sync-project-hub-commercial-documents`, `sync_project_hub_commercial_documents`.
768
+
769
+ Effects: state-change, external-write.
770
+
763
771
  ## `bb project create`
764
772
 
765
773
  Create Project.
package/dist/commands.js CHANGED
@@ -264,18 +264,7 @@ const billAttachmentContentTypeForFileName = (fileName) => {
264
264
  return resolvedContentType;
265
265
  };
266
266
  const PAYMENT_PROOF_MAX_SIZE = 20 * 1024 * 1024;
267
- const CUSTOMER_INVOICE_PAYMENT_PROOF_MAX_SIZE = 20 * 1024 * 1024;
268
267
  const QUOTATION_ATTACHMENT_MAX_SIZE = 20 * 1024 * 1024;
269
- const customerInvoicePaymentProofContentTypeForFileName = (fileName) => {
270
- const resolvedContentType = contentTypeHeaderForFileName(fileName);
271
- if (resolvedContentType === "application/pdf" ||
272
- resolvedContentType === "image/gif" ||
273
- resolvedContentType === "image/jpeg" ||
274
- resolvedContentType === "image/png") {
275
- return resolvedContentType;
276
- }
277
- throw new Error(`Unsupported customer invoice payment-proof type: ${fileName}. Use PDF, GIF, JPEG, or PNG.`);
278
- };
279
268
  const quotationAttachmentContentTypeForFileName = (fileName) => {
280
269
  const resolvedContentType = contentTypeHeaderForFileName(fileName);
281
270
  if (resolvedContentType === "application/pdf" ||
@@ -1447,86 +1436,6 @@ export const sendCustomerInvoiceToContactPersonFromPayload = async (raw) => {
1447
1436
  const result = await api.customerInvoice.sendInvoiceToContactPerson.mutate(input);
1448
1437
  out(result);
1449
1438
  };
1450
- export const describeCustomerInvoicePaymentConfirmation = ({ invoiceId, paymentDate, paymentReference, proofFileName, proofSize, }) => `for invoice ${invoiceId}, records payment date ${paymentDate}${paymentReference
1451
- ? " with a payment reference"
1452
- : " without a payment reference"}; uploads ${proofFileName} (${proofSize} bytes); creates a QuickBooks Payment for the live outstanding balance when non-zero, attaches the proof to QBO when possible, and records the payment in Budget Builder`;
1453
- const assertPaymentDate = (paymentDate) => {
1454
- if (!/^\d{4}-\d{2}-\d{2}$/.test(paymentDate)) {
1455
- throw new Error("--paymentDate must use YYYY-MM-DD.");
1456
- }
1457
- const parsed = new Date(`${paymentDate}T00:00:00.000Z`);
1458
- if (Number.isNaN(parsed.getTime()) ||
1459
- parsed.toISOString().slice(0, 10) !== paymentDate) {
1460
- throw new Error("--paymentDate must be a valid calendar date.");
1461
- }
1462
- return paymentDate;
1463
- };
1464
- export const markCustomerInvoicePaidFromPath = async ({ invoiceId, paymentDate, paymentProofPath, paymentReference, }) => {
1465
- const normalizedPaymentDate = assertPaymentDate(paymentDate);
1466
- const proofContents = await readFile(paymentProofPath);
1467
- const proofFileName = basename(paymentProofPath);
1468
- if (proofContents.byteLength === 0) {
1469
- throw new Error("Customer invoice payment proof must not be empty.");
1470
- }
1471
- if (proofContents.byteLength > CUSTOMER_INVOICE_PAYMENT_PROOF_MAX_SIZE) {
1472
- throw new Error("Customer invoice payment proof must be 20MB or smaller.");
1473
- }
1474
- const proofContentType = customerInvoicePaymentProofContentTypeForFileName(proofFileName);
1475
- await assertSensitiveWorkflowConfirmed({
1476
- action: "Mark customer invoice paid",
1477
- entity: `invoice ${invoiceId}`,
1478
- details: describeCustomerInvoicePaymentConfirmation({
1479
- invoiceId,
1480
- paymentDate: normalizedPaymentDate,
1481
- paymentReference,
1482
- proofFileName,
1483
- proofSize: proofContents.byteLength,
1484
- }),
1485
- });
1486
- const proofUpload = await api.customerInvoice.requestPaymentProofUpload.mutate({
1487
- invoiceId,
1488
- fileName: proofFileName,
1489
- size: proofContents.byteLength,
1490
- contentType: proofContentType,
1491
- });
1492
- const uploadResponse = await fetch(proofUpload.uploadUrl, {
1493
- method: "PUT",
1494
- body: proofContents,
1495
- headers: { "Content-Type": proofUpload.contentType },
1496
- });
1497
- if (!uploadResponse.ok) {
1498
- throw new Error(`Payment-proof upload failed: HTTP ${uploadResponse.status} ${(await uploadResponse.text()).slice(0, 500)}`);
1499
- }
1500
- const result = await api.customerInvoice.markPaid.mutate({
1501
- invoiceId,
1502
- paymentDate: normalizedPaymentDate,
1503
- paymentReference,
1504
- proof: {
1505
- key: proofUpload.key,
1506
- name: proofUpload.name,
1507
- size: proofContents.byteLength,
1508
- contentType: proofUpload.contentType,
1509
- },
1510
- });
1511
- out(result);
1512
- };
1513
- export const downloadCustomerInvoicePaymentProof = async (invoiceId, outputPath) => {
1514
- const proof = await api.customerInvoice.getPaymentProofDownloadUrl.mutate({
1515
- invoiceId,
1516
- });
1517
- const response = await fetch(proof.url);
1518
- if (!response.ok) {
1519
- throw new Error(`Payment-proof download failed: HTTP ${response.status} ${(await response.text()).slice(0, 500)}`);
1520
- }
1521
- const contents = Buffer.from(await response.arrayBuffer());
1522
- const resolvedOutputPath = outputPath?.trim() || basename(proof.fileName);
1523
- await writeFile(resolvedOutputPath, contents);
1524
- out({
1525
- fileName: proof.fileName,
1526
- outputPath: resolvedOutputPath,
1527
- size: contents.byteLength,
1528
- });
1529
- };
1530
1439
  export async function updateBillFromPayload(raw) {
1531
1440
  const input = parseUpdateBillPayload(raw);
1532
1441
  const result = await api.bill.update.mutate(input);
@@ -2006,6 +1915,30 @@ export async function getProject(id) {
2006
1915
  ]);
2007
1916
  out({ project, budgetsOverview, billsOverview });
2008
1917
  }
1918
+ export const getProjectHubStatus = async (projectId) => {
1919
+ const result = await api.project.getProjectHubShareLink.query({ projectId });
1920
+ out(result);
1921
+ };
1922
+ export const setupProjectHub = async (projectId) => {
1923
+ await assertSensitiveWorkflowConfirmed({
1924
+ action: "Set up Project Hub",
1925
+ entity: `project ${projectId}`,
1926
+ details: "queue Asana, Drive, and client Hub provisioning",
1927
+ });
1928
+ const result = await api.project.setupProjectHub.mutate({ projectId });
1929
+ out(result);
1930
+ };
1931
+ export const syncProjectHubCommercialDocuments = async (projectId) => {
1932
+ await assertSensitiveWorkflowConfirmed({
1933
+ action: "Sync Project Hub commercial documents",
1934
+ entity: `project ${projectId}`,
1935
+ details: "queue a full Budget Builder to Google Drive and Asana reconciliation",
1936
+ });
1937
+ const result = await api.project.syncProjectHubCommercialDocuments.mutate({
1938
+ projectId,
1939
+ });
1940
+ out(result);
1941
+ };
2009
1942
  export async function createProject(opts) {
2010
1943
  const links = await resolveCreateProjectLinks({
2011
1944
  name: opts.name,
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { BUDGET_BUILDER_API_BASE_URL, configureBudgetBuilderApiBaseUrl, DEFAULT_BUDGET_BUILDER_API_BASE_URL, requireApiKey, } from "./api-client.js";
7
7
  import { consumeCliQuietFlags, logCliAction, sanitizeFlagsForTrace, setCliQuiet, shouldLogCliActions, } from "./cli-trace.js";
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, downloadCustomerInvoicePaymentProof, downloadCustomerInvoicePdf, downloadQuotationPdf, getApprovedBudgets, getBillAttachments, getBillDetails, getBudget, getBudgetCategories, getBudgetCategoryBenchmarks, getBudgetDetails, getBudgetItemsOnly, getBudgetVersions, getCompany, getCustomerInvoice, getCustomerInvoiceEmailContext, getDashboard, getErrorMetrics, getEstimatePerformance, getFinancialOverview, getInboundSource, getInboundSubmission, getItem, getItemPricingHistory, getMonthlyMetrics, getProject, 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, markCustomerInvoicePaidFromPath, patchBillInvoiceNumber, patchBillPayment, reactivateSuppliersByIds, reconcileProject, rejectBill, rejectBudget, rejectCustomerInvoice, rejectQuotation, rejectSupplier, removeBudgetItem, renameBudgetVersion, reorderBudgetItemsCli, restoreBudgetVersion, retryIntegrationOperation, revokeApiKeyForUser, saveInboundAutomationConfig, saveInboundMappingVersion, sendCustomerInvoiceToContactPersonFromPayload, sendEstimateToContactPersonFromPayload, setBudgetItemsNotUtilized, stageBillAttachmentsFromPaths, submitQuotation, syncCustomerInvoice, 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";
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
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";
11
11
  import { parseJsonFlag, parseOptionalNumber as parseOptNum, } from "./parse-json-flag.js";
@@ -261,9 +261,6 @@ Customer invoices
261
261
  approve-customer-invoice <batchId> Admin approval; rejects voided invoices, notifies the creator, and closes the estimate at cumulative 100% coverage.
262
262
  reject-customer-invoice <batchId> --reason <text> Admin rejection; voids QBO invoices and notifies the creator.
263
263
  send-customer-invoice-to-contact-person --payload '<json>' Same composer workflow as web; requires invoiceId, to, cc, replyTo, subject, HTML content, and HTML signature.
264
- mark-customer-invoice-paid <invoiceId> --paymentDate YYYY-MM-DD --paymentProof <path> [--paymentReference <text>]
265
- Admin-only. Uploads proof, creates a QBO Payment for the live balance when needed, attaches proof in QBO when possible, and records payment history in BB.
266
- download-customer-invoice-payment-proof <invoiceId> [--output <path>]
267
264
  download-customer-invoice-pdf <invoiceId> [--output <path>]
268
265
  sync-customer-invoice <invoiceId> Refresh local status and balance from QuickBooks; paid invoices become PAID, reversals restore delivery/approval, and QBO voids can reopen an estimate.
269
266
 
@@ -1453,29 +1450,6 @@ export async function runCli(argv = process.argv, runtime = createProcessRuntime
1453
1450
  await sendCustomerInvoiceToContactPersonFromPayload(parseJsonFlag(String(payloadRaw), "--payload"));
1454
1451
  break;
1455
1452
  }
1456
- case "mark-customer-invoice-paid": {
1457
- const invoiceId = positional[0];
1458
- const paymentDate = getFlag(flags, "paymentDate");
1459
- const paymentProofPath = getFlag(flags, "paymentProof");
1460
- if (!invoiceId || !paymentDate || !paymentProofPath) {
1461
- throw new Error("mark-customer-invoice-paid requires <invoiceId> --paymentDate YYYY-MM-DD --paymentProof <path> [--paymentReference <text>]");
1462
- }
1463
- await markCustomerInvoicePaidFromPath({
1464
- invoiceId,
1465
- paymentDate,
1466
- paymentProofPath,
1467
- paymentReference: getFlag(flags, "paymentReference"),
1468
- });
1469
- break;
1470
- }
1471
- case "download-customer-invoice-payment-proof": {
1472
- const invoiceId = positional[0];
1473
- if (!invoiceId) {
1474
- throw new Error("download-customer-invoice-payment-proof requires <invoiceId> [--output <path>]");
1475
- }
1476
- await downloadCustomerInvoicePaymentProof(invoiceId, getFlag(flags, "output"));
1477
- break;
1478
- }
1479
1453
  case "download-customer-invoice-pdf": {
1480
1454
  const invoiceId = positional[0];
1481
1455
  if (!invoiceId) {
@@ -1616,6 +1590,30 @@ export async function runCli(argv = process.argv, runtime = createProcessRuntime
1616
1590
  await getProject(id);
1617
1591
  break;
1618
1592
  }
1593
+ case "get-project-hub-status": {
1594
+ const projectId = positional[0];
1595
+ if (!projectId) {
1596
+ throw new Error("get-project-hub-status requires <projectId>");
1597
+ }
1598
+ await getProjectHubStatus(projectId);
1599
+ break;
1600
+ }
1601
+ case "setup-project-hub": {
1602
+ const projectId = positional[0];
1603
+ if (!projectId) {
1604
+ throw new Error("setup-project-hub requires <projectId>");
1605
+ }
1606
+ await setupProjectHub(projectId);
1607
+ break;
1608
+ }
1609
+ case "sync-project-hub-commercial-documents": {
1610
+ const projectId = positional[0];
1611
+ if (!projectId) {
1612
+ throw new Error("sync-project-hub-commercial-documents requires <projectId>");
1613
+ }
1614
+ await syncProjectHubCommercialDocuments(projectId);
1615
+ break;
1616
+ }
1619
1617
  case "create-project": {
1620
1618
  const name = getFlag(flags, "name");
1621
1619
  const companyId = getFlag(flags, "companyId");
@@ -59,6 +59,7 @@ const mutationPrefixes = [
59
59
  "save-",
60
60
  "send-",
61
61
  "stage-",
62
+ "setup-",
62
63
  "submit-",
63
64
  "sync-",
64
65
  "update-",
@@ -110,13 +111,14 @@ const externalWriteTargets = new Set([
110
111
  "import-qbo-project",
111
112
  "mark-budget-items-not-utilized",
112
113
  "mark-budget-won",
113
- "mark-customer-invoice-paid",
114
114
  "reconcile-project",
115
115
  "reject-customer-invoice",
116
116
  "reactivate-suppliers",
117
117
  "restore-budget-item",
118
118
  "restore-budget-version",
119
119
  "retry-integration-operation",
120
+ "setup-project-hub",
121
+ "sync-project-hub-commercial-documents",
120
122
  "sync-customer-invoice",
121
123
  "update-bill",
122
124
  "update-budget-status",
@@ -143,7 +145,6 @@ const financialWriteTargets = new Set([
143
145
  "delete-customer-invoice",
144
146
  "mark-budget-items-not-utilized",
145
147
  "mark-budget-won",
146
- "mark-customer-invoice-paid",
147
148
  "patch-bill-payment",
148
149
  "reject-customer-invoice",
149
150
  "restore-budget-item",
@@ -343,16 +344,6 @@ const registry = [
343
344
  "customer-invoice",
344
345
  "send",
345
346
  ]),
346
- legacyCommand("mark-customer-invoice-paid", [
347
- "customer-invoice",
348
- "payment",
349
- "mark-paid",
350
- ]),
351
- legacyCommand("download-customer-invoice-payment-proof", [
352
- "customer-invoice",
353
- "payment-proof",
354
- "download",
355
- ]),
356
347
  legacyCommand("download-customer-invoice-pdf", [
357
348
  "customer-invoice",
358
349
  "pdf",
@@ -374,6 +365,13 @@ const registry = [
374
365
  legacyCommand("delete-company", ["company", "delete"]),
375
366
  legacyCommand("list-projects", ["project", "list"]),
376
367
  legacyCommand("get-project", ["project", "get"]),
368
+ legacyCommand("get-project-hub-status", ["project", "hub", "status"]),
369
+ legacyCommand("setup-project-hub", ["project", "hub", "setup"]),
370
+ legacyCommand("sync-project-hub-commercial-documents", [
371
+ "project",
372
+ "hub",
373
+ "sync",
374
+ ]),
377
375
  legacyCommand("create-project", ["project", "create"]),
378
376
  legacyCommand("update-project", ["project", "update"]),
379
377
  legacyCommand("delete-project", ["project", "delete"]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/bb",
3
- "version": "2.10.3",
3
+ "version": "2.11.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",
@@ -89,6 +89,7 @@ This table highlights the role-sensitive command groups an agent is most likely
89
89
  | `bb auth whoami`; own `bb user api-key create`, `bb user api-key list`, and `bb user api-key revoke` | Any active role; omit `--userId` to act as the current user. |
90
90
  | Cross-user API-key commands; `bb user create` | `ADMIN`. |
91
91
  | Budget/project mutations, estimates, and budget attachments | `ADMIN`, budget creator, or project assignment as Business Development, Inside Sales, or Project Manager, depending on the endpoint. |
92
+ | `bb project hub status`, `bb project hub setup`, and `bb project hub sync` | `ADMIN`, project creator, or project assignment as Business Development, Inside Sales, or Project Manager. Setup and sync also enforce Project Hub lifecycle/readiness checks. |
92
93
  | Edit/delete a bill or quotation | Usually creator or `ADMIN`, with a supported status. |
93
94
  | `bb budget approve`, `bb budget reject`, `bb supplier approve`, and `bb supplier reject` | Current user must be the pending approver and currently have role `LEAD` or `ADMIN`. |
94
95
  | `bb bill approve` and `bb bill reject` | Current user must be the pending approver and currently have role `ACCOUNTING_TEAM` for the finance/check stage or `ADMIN` for the final approval stage. |
@@ -97,7 +98,6 @@ This table highlights the role-sensitive command groups an agent is most likely
97
98
  | `bb bill status update ... APPROVED` or `... PAID` | `ADMIN`; state, integration, payment-reference, and payment-proof requirements still apply. |
98
99
  | `bb bill payment patch`; `bb bill invoice-number patch` | `ACCOUNTING_TEAM` or `ADMIN`, with the procedure's bill-state constraints. |
99
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. |
100
- | `bb customer-invoice payment mark-paid` | `ADMIN`. |
101
101
  | Dashboard/performance, supplier analytics, errors, integration operations | `ADMIN`. |
102
102
  | `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
103
 
@@ -121,6 +121,7 @@ The following distinctions are especially important:
121
121
  - Budget, supplier, bill, and quotation decisions require both the matching live role and a pending approval assigned to the current identity. Customer-invoice approve/reject are Admin batch operations selected by batch ID; the API resolves their internal approval record.
122
122
  - Some company, contact, project, supplier, and catalog collaboration procedures intentionally allow every active authenticated role. Treat these endpoint-specific rules as exceptions rather than inferring access to adjacent mutations.
123
123
  - Budget and project commands often depend on creator/assignment access even when the role itself is valid.
124
+ - Project Hub setup and sync are asynchronous external writes. Read `bb project hub status <project-id>` first, obtain explicit approval, and request the mutation once instead of repeatedly queueing it. Setup readiness remains visible through `status`; commercial sync returns an outbox operation ID for Admin diagnostics.
124
125
  - Bill/claim status commands have separate role rules for `CHECKED`, `APPROVED`, and `PAID`.
125
126
  - `bb bill create --payload` uses `isClaimable=false`; `isClaimable=true` creates a claim. Claims intentionally permit some base-user flows that supplier bills do not.
126
127
  - Staged attachments belong to the user in the database. Do not reuse, share, or manually construct staged keys.