@go-labs-sg/bb 1.20.0 → 2.0.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
@@ -6,96 +6,185 @@ For AI agents: bills and claims are stored as the same underlying bill records.
6
6
 
7
7
  For AI agents using this CLI: always check whether a newer `@go-labs-sg/bb` version is available before running Budget Builder commands, then update the CLI if needed so command flags and API behavior stay current.
8
8
 
9
- For AI agents using this CLI: do not execute approval decisions, approval requests, status updates, estimate emails, customer-invoice changes, integration retries, `mark-budget-won`, `submit-quotation`, or supplier approval request side effects until the user explicitly confirms the exact entity, target state, and side effects. Inspect the entity first, summarize what will change and whether emails/integrations will run, then wait for a clear confirmation from the user. These sensitive workflow commands also require an interactive `CONFIRM` prompt at runtime and abort in non-interactive shells.
9
+ For AI agents using this CLI: run `bb auth whoami` before reading or changing Budget Builder data. The CLI displays every command regardless of the authenticated user's role, but the API authorizes each request using the API-key owner's role, resource ownership or project assignment, current workflow state, and approval assignment. See the [role-aware agent guide](./role-aware-agent-guide.md) before choosing commands.
10
+
11
+ For AI agents using this CLI: do not execute mutations until the user explicitly confirms the exact entity, target state, and side effects. Inspect the entity first, summarize what will change and whether emails, external systems, deletions, or financial records are involved, then wait for a clear confirmation from the user. Canonical v2 mutations print an effect-aware workflow preview before one interactive `CONFIRM` prompt. Non-interactive automation must supply every applicable granular `--allow-*` flag; the CLI rejects the command before its first API call when any flag is missing.
10
12
 
11
13
  **Registry:** [`@go-labs-sg/bb`](https://www.npmjs.com/package/@go-labs-sg/bb)
12
14
 
13
15
  ## Requirements
14
16
 
15
- - **Node.js** 18+ (ESM; relative imports in `dist` use `.js` extensions)
16
- - A **Budget Builder API key** (same key used for the MCP server)
17
+ - **Node.js** 24+ (ESM; relative imports in `dist` use `.js` extensions)
18
+ - A Budget Builder account with any active user role
19
+ - A **Budget Builder API key** (the same key is used for the CLI and MCP server)
17
20
 
18
21
  ## Install
19
22
 
20
23
  ```bash
21
- npm install -g @go-labs-sg/bb
24
+ bun add --global @go-labs-sg/bb
22
25
  ```
23
26
 
24
27
  Or run without installing:
25
28
 
26
29
  ```bash
27
- npx @go-labs-sg/bb <command>
30
+ bunx @go-labs-sg/bb <command>
28
31
  ```
29
32
 
30
33
  ## Authentication
31
34
 
32
- Set your API key before running commands:
35
+ Sign in to Budget Builder and create your first key under **API Keys**. Every
36
+ active user can manage their own keys; an admin is only required to manage a key
37
+ for someone else.
38
+
39
+ Load the key from your shell's secret manager or environment before running
40
+ commands:
41
+
42
+ ```bash
43
+ export BB_API_KEY="<your-key>"
44
+ ```
45
+
46
+ Do not put the raw key in a command argument, commit it to a repository, or paste
47
+ it into logs. The CLI redacts API-key values from its request tracing. If you use
48
+ a local `.env`, keep it out of version control and restrict its file permissions.
49
+
50
+ Once authenticated, any user can rotate or manage their own keys from the CLI:
33
51
 
34
52
  ```bash
35
- export BB_API_KEY=<your-key>
53
+ bb auth whoami
54
+ bb user api-key create --name "My CLI"
55
+ bb user api-key list
56
+ bb user api-key revoke <api-key-id>
36
57
  ```
37
58
 
38
- Create or revoke keys in the web app: **Goracle API Keys** (MCP API keys).
59
+ The raw key is returned only by `bb user api-key create`; copy it immediately. Listing
60
+ keys returns metadata only. Revoking the key currently stored in `BB_API_KEY`
61
+ will cause subsequent commands to fail until it is replaced.
39
62
 
40
63
  Admins can provision API-only service identities and manage their keys from the
41
64
  CLI. These identities are not human web accounts: `create-user` does not create
42
65
  or link a Google OAuth account, so the resulting user cannot sign in with Google.
43
66
 
44
67
  ```bash
45
- bb create-user --email agent@example.com --name "Budget Agent" --role USER
46
- bb create-api-key --userId <user-id> --name "Budget Agent CLI"
47
- bb list-api-keys --userId <user-id>
48
- bb revoke-api-key <api-key-id> --userId <user-id>
68
+ bb user create --email agent@example.com --name "Budget Agent" --role USER
69
+ bb user api-key create --userId <user-id> --name "Budget Agent CLI"
70
+ bb user api-key list --userId <user-id>
71
+ bb user api-key revoke <api-key-id> --userId <user-id>
72
+ ```
73
+
74
+ The CLI talks to `https://budget-builder.getout.events` by default. For staging or local development, the target precedence is `--api-url`, then `BB_API_URL`, then production. Overrides must use HTTPS, except `http://localhost` and `http://127.0.0.1` for local development. `bb version` reports the effective target.
75
+
76
+ ## Role-aware agent startup
77
+
78
+ Every agent session that uses an API key must begin with:
79
+
80
+ ```bash
81
+ bb version
82
+ bb auth whoami
83
+ ```
84
+
85
+ `whoami` returns the identity and database role attached to the active key inside the v2 response envelope:
86
+
87
+ ```json
88
+ {
89
+ "ok": true,
90
+ "data": {
91
+ "id": "...",
92
+ "name": "...",
93
+ "email": "...",
94
+ "role": "USER"
95
+ },
96
+ "meta": {
97
+ "command": "auth whoami",
98
+ "cliVersion": "2.0.0",
99
+ "apiBaseUrl": "https://budget-builder.getout.events"
100
+ }
101
+ }
49
102
  ```
50
103
 
51
- The raw key is returned only by `create-api-key`; copy it immediately. Listing
52
- keys returns metadata only.
104
+ Record that identity in the agent's working context before selecting commands. Do not infer the role from the key name, email address, task description, or commands shown by `bb help`. Do not switch to another key to bypass a permission failure.
53
105
 
54
- The CLI talks to the production API: `https://budget-builder.getout.events`.
106
+ The following is a practical guide, not a client-side allowlist. The API remains authoritative.
107
+
108
+ | `whoami.role` | What an agent may normally do | Important limits |
109
+ | --- | --- | --- |
110
+ | `USER` | Manage its own API keys; use collaboration endpoints intentionally shared with every authenticated role, including company, contact, and project creation/update; work with resources it created or projects/budgets where its user ID is assigned; create its own bills, claims, and quotation drafts; request approval. | Shared collaboration endpoints are explicit API exceptions, not a general grant over every resource. No admin dashboards, user provisioning, integration recovery, final finance actions, or approval decisions. |
111
+ | `INSIDE_SALES` | All applicable base-user operations, assigned project/budget work, and the broader supplier workflow exposed to Inside Sales. | Budget and supplier approval requests still follow the Lead/Admin workflow. Assignment, ownership, state, and recorded-approver checks still apply. |
112
+ | `LEAD` | All applicable base-user operations plus Lead-stage budget and supplier approval work; supplier creation is auto-approved for this role. | Lead is not Admin. Do not attempt admin dashboards, user administration, integration recovery, final bill payment, or admin-only customer-invoice actions. |
113
+ | `ACCOUNTING_TEAM` | All applicable authenticated operations plus finance-stage bill work, including checking bills and correcting allowed payment or invoice-number metadata. Act on bill approvals only when the API lists the user as an approver. | Direct `APPROVED` and `PAID` bill status changes, customer-invoice payment, user administration, and system operations remain Admin-only. |
114
+ | `ADMIN` | Cross-user API-key and service-identity administration, global dashboard/error/integration operations, broad resource access, final bill approval/payment, and admin customer-invoice workflows. | Admin access does not bypass valid state transitions, required evidence, resource consistency, external-operation locks, or the agent's duty to obtain explicit confirmation for sensitive commands. |
115
+
116
+ Permissions are also resource- and state-dependent:
117
+
118
+ - Budget/project mutations generally require Admin, resource ownership, or assignment as Business Development, Inside Sales, or Project Manager.
119
+ - Bill and quotation edits/deletes are commonly limited to their creator or Admin, and only in supported statuses.
120
+ - Budget, supplier, bill, and quotation approval decisions require both a pending approval assigned to the authenticated user and the user's current database role. Budget/supplier decisions require `LEAD` or `ADMIN`, bill decisions require `ACCOUNTING_TEAM` or `ADMIN`, and quotation decisions require `ADMIN`; a stale assignment does not preserve authority after a role change.
121
+ - Customer-invoice approve/reject commands are Admin batch operations selected by batch ID, rather than approval-record-ID commands. The API maintains and resolves the internal pending Admin approval record as part of the batch workflow.
122
+ - Some company, contact, project, supplier, and catalog collaboration procedures intentionally allow every active authenticated role. Treat those endpoint-specific rules as exceptions; do not infer access to adjacent mutations.
123
+ - Own-key commands are available to every active role. Passing `--userId` to manage another user's keys requires Admin.
124
+ - `bb help` is a command catalog, not proof that the current identity is authorized.
125
+
126
+ If the API returns `FORBIDDEN`, stop. Report the command, target resource, `whoami` identity/role, and the missing ownership, assignment, approver, or role condition when known. Do not retry through a lower-level command or another credential unless the user explicitly changes the operating identity.
55
127
 
56
128
  ## Usage
57
129
 
58
130
  ```bash
59
131
  bb help
60
- bb list-budgets
61
- bb get-budget <budget-id>
62
- bb update-budget-status <budget-id> <status> [--markProjectWon true] [--projectStatusOnCommercialRejection PITCH|LOST]
63
- bb list-bills
64
- bb approve-bill <bill-id>
65
- bb list-approvals
66
- bb list-suppliers
132
+ bb version
133
+ bb auth whoami
134
+ bb budget list
135
+ bb budget get <budget-id>
136
+ bb budget status update <budget-id> <status>
137
+ bb bill list
138
+ bb bill approve <bill-id>
139
+ bb approval list
140
+ bb supplier list
67
141
  ```
68
142
 
69
- Global options and flags use `--key=value` or `--key value` (see `bb help`).
143
+ Global options and command flags use `--key=value` or `--key value`. Run `bb help` for the canonical command catalog and `bb help --legacy` for the transition-era detailed flat-command reference.
70
144
 
71
- **Authoritative command list:** run `bb help` it includes every command, positional args, and flags. MCP exposes a subset of the same tRPC surface; the CLI additionally includes a few procedures mainly used by the web UI (e.g. `reorder-budget-items`, `update-budget-item-supplier`). You can also use MCP-style `snake_case` (e.g. `bb list_bills`); it is normalized to kebab-case.
145
+ Run `bb version` before agent-driven work to record the installed package version and production API target. This is read-only and does not require `BB_API_KEY`.
72
146
 
73
- **Budget status automation:** Setting a budget to `ESTIMATE_ACCEPTED` requires a confirmed win-proof attachment. If the parent project is `PITCH` or `LOST`, the API now marks it `WON` automatically and, when the `asana-tasks` feature flag is enabled, creates an Asana Event Ops section from Prompt 5 tasks. 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. When rejecting the only accepted/closed budget on a commercial project, pass `--projectStatusOnCommercialRejection PITCH|LOST`.
147
+ **Authoritative command list:** run `bb help` or inspect the generated `command-reference.md` / `command-manifest.json`. The catalog includes every canonical route, global option, and effect classification, but does not filter by the current `whoami` role. During the handler migration, command-specific arguments retain the compatibility dispatcher's validation and are explicitly marked `legacy-passthrough` in the manifest; use `bb help --legacy` for their detailed transition reference. Version 2 uses grouped, resource-first commands such as `bb budget list` and `bb bill attachment upload`. Flat commands such as `bb list-budgets` and their MCP-style `snake_case` aliases remain available during the v2 transition, with a deprecation notice and an equivalent grouped command in help. MCP exposes a subset of the same tRPC surface; the CLI additionally includes a few procedures mainly used by the web UI.
74
148
 
75
- **Sensitive workflow changes:** Approval decisions/requests, entity status updates, estimate emails, customer-invoice changes, integration retries, `mark-budget-won`, `submit-quotation`, and supplier approval request side effects require an interactive `CONFIRM` prompt. Non-interactive runs abort before the guarded workflow mutation. Agents must get user confirmation in chat before attempting the command; the prompt is a final runtime guard, not a replacement for user approval.
149
+ **Budget status automation:** Setting a budget to `ESTIMATE_ACCEPTED` requires a confirmed win-proof attachment. The first accepted estimate also requires `--invoiceSettings '<json>'` while customer invoicing is enabled. Provide `depositPercentage`, `balancePercentage`, `deliveryMethod`, and the matching `billingEmail` or HTTPS `uploadUrl`; revenue at or below S$5,000 requires `100/0`. If the parent project is `PITCH` or `LOST`, the API marks it `WON` automatically and runs enabled Asana automation. When rejecting the only accepted/closed budget on a commercial project, pass `--projectStatusOnCommercialRejection PITCH|LOST`.
150
+
151
+ ### Mutation safety
152
+
153
+ Every canonical command is classified by effect: `state-change`, `email`, `external-write`, `delete`, and/or `financial-write`. Interactive runs require one exact `CONFIRM`. Non-interactive runs require the corresponding flags (`--allow-state-change`, `--allow-email`, `--allow-external-write`, `--allow-delete`, and `--allow-financial-write`); commands with multiple effects require every matching flag. Agents must still get user confirmation in chat first—the runtime gate is not user authorization.
76
154
 
77
155
  **Approval email exclusions:** Approval requests create pending database records for every eligible approver, including configured non-recipient admin accounts. Automated approval-request emails skip those accounts.
78
156
 
79
- **Locked estimate budget changes:** Budget-changing CLI commands require an interactive `yes` confirmation when the current budget status is `ESTIMATE_CREATED`, `ESTIMATE_SENT`, `ESTIMATE_ACCEPTED`, `ESTIMATE_REJECTED`, or `ESTIMATE_CLOSED`. There is no force/override flag; non-interactive runs abort before mutating locked budgets.
157
+ **Locked estimate budget changes:** Legacy flat commands retain their additional interactive `yes` prompt when the current budget is locked. Canonical v2 commands use the single effect-aware confirmation gate; non-interactive execution requires `--allow-state-change` plus any other effects declared for that command.
158
+
159
+ **Mutations with `--payload`:** Commands such as `bb budget create`, `bb bill create`, and `bb supplier update` take a single JSON object (`--payload '<json>'`) matching the corresponding tRPC procedure input. Use ISO strings for date/datetime fields; the CLI coerces them where needed. The API still validates the full shape. Plain `description` fields for item create/update are converted to `descriptionRichText`; pass `descriptionRichText` directly when formatted Tiptap JSON is required. For **`bb project update`**, the project window is `dateRange.from` and `dateRange.to` (optional end); there are no separate event-date fields on the project payload. **`bb budget create` / `bb budget update`** do not accept `asanaTaskId`; configure the deal card on the project (`bb project update` / project settings).
160
+
161
+ **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.
80
162
 
81
- **Mutations with `--payload`:** Commands such as `create-budget`, `create-bill`, `update-supplier`, etc. take a single JSON object (`--payload '<json>'`) matching the corresponding tRPC procedure input. Use ISO strings for date/datetime fields; the CLI coerces them where needed. The API still validates the full shape. Plain `description` fields for item create/update are converted to `descriptionRichText`; pass `descriptionRichText` directly when formatted Tiptap JSON is required. For **`update-project`**, the project window is `dateRange.from` and `dateRange.to` (optional end); there are no separate event-date fields on the project payload. **`create-budget` / `update-budget`** do not accept `asanaTaskId`; configure the deal card on the project (`update-project` / project settings).
163
+ **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.
82
164
 
83
- **Contact-person estimate email:** `send-estimate-to-contact-person --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.
165
+ For agent-driven invoice work, use this read-before-write sequence:
166
+
167
+ 1. Run `bb customer-invoice get <batchId>` and, when composing email, `bb customer-invoice email-context get <batchId>`.
168
+ 2. For creation, run `bb customer-invoice eligible-budget list <projectId>` or `bb customer-invoice readiness check <budgetId>` first.
169
+ 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.
170
+ 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.
84
171
 
85
172
  ### Command overview
86
173
 
87
- | Area | Commands (non-exhaustive) |
174
+ 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.
175
+
176
+ | Area | Legacy handler labels (reference only; non-exhaustive) |
88
177
  | --- | --- |
89
178
  | **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`) |
90
- | **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), `stage-bill-attachment` (securely uploads invoice/payment-proof files before creation and returns attachment JSON), `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`, and is created pending finance approval; non-legacy supplier bills from 1 Jul 2026 00:00 SGT also require approved quotation coverage), `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` |
179
+ | **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` |
91
180
  | **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`) |
92
- | **Customer invoices** | `check-customer-invoice-readiness`, `list-customer-invoices`, `get-customer-invoice`, `create-customer-invoice`, `discard-customer-invoice`, `delete-customer-invoice`, `void-customer-invoice`, `approve-customer-invoice`, `reject-customer-invoice`, `download-customer-invoice-pdf`, `sync-customer-invoice` |
181
+ | **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` |
93
182
  | **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` |
94
183
  | **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`) |
95
184
  | **Contacts** | `list-contacts`, `create-contact-person` (`--payload`), `update-contact-person` (`--payload`) |
96
- | **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`, `list-items`, `create-item` (`--payload`), `update-item` (`--payload`), `delete-item`, `get-item`, `list-item-categories`, `create-item-category`, `update-item-category`, `delete-item-categories` (`--ids` CSV; admin) |
97
- | **Dashboard & users** | `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` (admin; `--userId`, `--name`; raw key shown once), `list-api-keys` (admin; optional `--userId`), `revoke-api-key` (admin; key ID plus `--userId` for another user's key), `get-user-performance`, `get-dashboard`, `get-monthly-metrics`, `get-system-overview`, `get-estimate-performance`, `get-financial-overview` |
98
- | **Errors** | `get-recent-errors`, `get-error-metrics` |
185
+ | **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`), `delete-item`, `get-item`, `list-item-categories`, `create-item-category` / `update-item-category` / `delete-item-categories` (admin; `--ids` CSV for delete) |
186
+ | **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) |
187
+ | **Errors (admin)** | `get-recent-errors`, `get-error-metrics` |
99
188
  | **Automation (admin)** | `list-integration-operations`, `retry-integration-operation` |
100
189
  | **Historical / benchmarks** | `get-approved-budgets`, `get-budget-category-benchmarks`, `get-item-pricing-history`, `get-supplier-pricing-history` |
101
190
 
@@ -103,10 +192,11 @@ The CLI intentionally wraps low-level upload-request/confirm procedures into fil
103
192
 
104
193
  ## Output
105
194
 
106
- - **Stdout:** JSON (pretty-printed)
107
- - **Stderr:** Error objects as JSON on failure
108
- - **Action logs (default on):** Each run logs to **stderr**: (1) JSON lines for command start, then `ok` or `error` with `durationMs` (`--payload` and similar flags are redacted); (2) tRPC lines for every API procedure (path, timing). **Stdout** stays JSON-only for piping (e.g. `| jq`). Suppress with `--quiet`, `-q`, or `BB_CLI_QUIET=1`.
109
- - **Exit code:** `0` on success, `1` on error
195
+ - **Canonical stdout:** a pretty-printed `{ "ok": true, "data": ..., "meta": ... }` JSON envelope
196
+ - **Canonical stderr:** a structured `{ "ok": false, "error": { "code", "message", ... }, "meta": ... }` JSON envelope on failure
197
+ - **Diagnostics:** off by default for canonical commands; use `--debug` for sanitized stderr tracing and `--quiet`, `-q`, or `BB_CLI_QUIET=1` to suppress diagnostics
198
+ - **Legacy compatibility:** flat aliases keep their raw success JSON, `{ "error": "..." }` failures, and default action tracing during the transition
199
+ - **Exit codes:** `0` success, `2` usage/validation/confirmation failure, `1` API/auth/network/internal failure, `130` interruption
110
200
 
111
201
  A `.env` file in the **current working directory** is loaded automatically (for `BB_API_KEY`, etc.).
112
202
 
@@ -116,15 +206,15 @@ From the monorepo root (after `bun install`):
116
206
 
117
207
  ```bash
118
208
  export BB_API_KEY=...
119
- bun run bb -- list-budgets
209
+ bun run golabs -- bb budget list
120
210
  ```
121
211
 
122
- Or from `packages/cli`:
212
+ Or from `packages/budget-builder/cli`:
123
213
 
124
214
  ```bash
125
215
  bun run build # emit dist/ via tsc
126
216
  bun run typecheck # tsc --noEmit
127
- bun run src/index.ts list-budgets
217
+ bun run src/index.ts budget list
128
218
  ```
129
219
 
130
220
  ### Layout
@@ -134,4 +224,13 @@ bun run src/index.ts list-budgets
134
224
 
135
225
  ### Publish
136
226
 
137
- `prepack` strips `workspace:` / `catalog:` entries from this package’s `package.json`, runs `tsc`, then `postpack` restores the file — so the npm tarball does not contain monorepo protocol dependencies. Types from `@go-labs/budget-builder-api` are compile-time only (`import type`). Prisma enum **values** used at runtime live in `src/prisma-enums.ts` (kept in sync with `packages/budget-builder/db` generated enums) so npm installs do not need `@go-labs/budget-builder-db`.
227
+ Build the package first, then create its publishable archive with Bun:
228
+
229
+ ```bash
230
+ bun run build
231
+ bun run pack:artifact
232
+ ```
233
+
234
+ `pack:artifact` copies only the publish allowlist into a temporary staging directory and writes a sanitized publish manifest there. It never edits the source `package.json`, and the staged archive contains no workspace-only development dependencies. The release workflow packs once, validates the resulting archive, tests that exact tarball under Node and Bun, then publishes that exact tarball. Types from `@go-labs/budget-builder-api` are compile-time only (`import type`). Prisma enum **values** used at runtime live in `src/prisma-enums.ts` (kept in sync with `packages/budget-builder/db` generated enums) so npm installs do not need `@go-labs/budget-builder-db`.
235
+
236
+ The generated `command-reference.md` and `command-manifest.json` are release artifacts. Regenerate them whenever the command registry changes; the release workflow rejects a tarball that does not include both files.