@go-labs-sg/bb 2.29.0 → 2.30.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 +4 -0
- package/command-manifest.json +5 -1
- package/command-reference.md +1 -1
- package/dist/index.js +47 -10
- package/package.json +1 -1
- package/role-aware-agent-guide.md +2 -0
package/README.md
CHANGED
|
@@ -230,6 +230,10 @@ commands return a conflict until delivery becomes `SENT` or `FAILED`.
|
|
|
230
230
|
|
|
231
231
|
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.
|
|
232
232
|
|
|
233
|
+
**Draft-only bills:** Use `bb bill create --draft-only --payload '<bill-json>' --allow-state-change --allow-financial-write` when the user authorizes a draft only. The equivalent payload field is `draftOnly: true`. This creates `DRAFT` for every role, without auto-checking, auto-approval, approval requests, emails, or QuickBooks writes. It cannot be combined with `alreadyPaid: true`; conflicting flag and payload values are rejected. Draft-only creation uses the dedicated `bill.createDraft` API route and fails on older servers without falling back to ordinary creation. Deploy backend support before releasing the updated CLI. Reconciled projects must be moved to Won separately before draft-only creation, because reopening a project can trigger external integrations. Completed projects retain their existing creation restriction.
|
|
234
|
+
|
|
235
|
+
Bill creation determines its required permissions from the parsed input, for both canonical and legacy commands. Ordinary unpaid creation requires state-change, financial-write, and external-write permissions, but no email permission. Already-paid creation also requires email permission. Without draft-only mode, existing role behavior remains: ordinary users create drafts, Accounting creates checked bills, and Admin creates approved bills and writes to QuickBooks. Ordinary creation can also adjust existing QuickBooks placeholder bills. Explicit drafts defer those adjustments until the later approval/QuickBooks workflow. When later authorized to submit the draft, use `bb bill approval create <billId>` with its state-change and email permissions; Admin-created drafts also enter finance review through this command. The static command catalog lists the union of possible effects.
|
|
236
|
+
|
|
233
237
|
**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.
|
|
234
238
|
|
|
235
239
|
**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.
|
package/command-manifest.json
CHANGED
|
@@ -2063,7 +2063,7 @@
|
|
|
2063
2063
|
{
|
|
2064
2064
|
"path": ["bill", "create"],
|
|
2065
2065
|
"legacyAliases": ["create-bill", "create_bill"],
|
|
2066
|
-
"summary": "Create a bill or claim with an approved supplier.",
|
|
2066
|
+
"summary": "Create a bill or claim with an approved supplier. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.",
|
|
2067
2067
|
"globalOptions": [
|
|
2068
2068
|
{
|
|
2069
2069
|
"name": "--help",
|
|
@@ -2104,6 +2104,10 @@
|
|
|
2104
2104
|
{
|
|
2105
2105
|
"name": "--allow-financial-write",
|
|
2106
2106
|
"description": "Allow financial-record changes in non-interactive use."
|
|
2107
|
+
},
|
|
2108
|
+
{
|
|
2109
|
+
"name": "--draft-only",
|
|
2110
|
+
"description": "Create a draft for every role; no approval, email, or QuickBooks writes. Conflicts with alreadyPaid: true."
|
|
2107
2111
|
}
|
|
2108
2112
|
],
|
|
2109
2113
|
"argumentMode": "legacy-passthrough",
|
package/command-reference.md
CHANGED
|
@@ -347,7 +347,7 @@ Effects: state-change, email.
|
|
|
347
347
|
|
|
348
348
|
## `bb bill create`
|
|
349
349
|
|
|
350
|
-
Create a bill or claim with an approved supplier.
|
|
350
|
+
Create a bill or claim with an approved supplier. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.
|
|
351
351
|
|
|
352
352
|
Legacy aliases: `create-bill`, `create_bill`.
|
|
353
353
|
|
package/dist/index.js
CHANGED
|
@@ -20259,7 +20259,7 @@ var billAttachment = (x, i, field = "attachments") => {
|
|
|
20259
20259
|
size: Number(a.size)
|
|
20260
20260
|
};
|
|
20261
20261
|
};
|
|
20262
|
-
function parseCreateBillPayload(raw) {
|
|
20262
|
+
function parseCreateBillPayload(raw, draftOnlyFlag) {
|
|
20263
20263
|
const o = requireObject(raw, "create-bill payload");
|
|
20264
20264
|
const isClaimable = o.isClaimable;
|
|
20265
20265
|
if (typeof isClaimable !== "boolean") {
|
|
@@ -20271,7 +20271,18 @@ function parseCreateBillPayload(raw) {
|
|
|
20271
20271
|
}
|
|
20272
20272
|
const attachmentsRaw = o.attachments;
|
|
20273
20273
|
const attachments = Array.isArray(attachmentsRaw) ? attachmentsRaw.map((x, i) => billAttachment(x, i)) : [];
|
|
20274
|
+
const draftOnlyRaw = o.draftOnly;
|
|
20275
|
+
if (draftOnlyRaw !== undefined && typeof draftOnlyRaw !== "boolean") {
|
|
20276
|
+
throw new Error("create-bill payload.draftOnly must be boolean.");
|
|
20277
|
+
}
|
|
20278
|
+
if (draftOnlyFlag !== undefined && draftOnlyRaw !== undefined && draftOnlyFlag !== draftOnlyRaw) {
|
|
20279
|
+
throw new Error("--draft-only conflicts with payload.draftOnly.");
|
|
20280
|
+
}
|
|
20281
|
+
const draftOnly = draftOnlyFlag ?? draftOnlyRaw;
|
|
20274
20282
|
const alreadyPaidRaw = o.alreadyPaid;
|
|
20283
|
+
if (draftOnly && alreadyPaidRaw === true) {
|
|
20284
|
+
throw new Error("Draft-only creation cannot be combined with alreadyPaid: true.");
|
|
20285
|
+
}
|
|
20275
20286
|
if (alreadyPaidRaw !== undefined && alreadyPaidRaw !== null && typeof alreadyPaidRaw !== "boolean") {
|
|
20276
20287
|
throw new Error("create-bill payload.alreadyPaid must be boolean.");
|
|
20277
20288
|
}
|
|
@@ -20315,6 +20326,7 @@ function parseCreateBillPayload(raw) {
|
|
|
20315
20326
|
isClaimable,
|
|
20316
20327
|
attachments,
|
|
20317
20328
|
alreadyPaid: alreadyPaidRaw ?? undefined,
|
|
20329
|
+
draftOnly,
|
|
20318
20330
|
paymentReference: optionalString(o.paymentReference),
|
|
20319
20331
|
paymentProofAttachments,
|
|
20320
20332
|
extractedSupplierName: optionalString(o.extractedSupplierName),
|
|
@@ -20327,8 +20339,12 @@ function parseCreateBillPayload(raw) {
|
|
|
20327
20339
|
}
|
|
20328
20340
|
function parseUpdateBillPayload(raw) {
|
|
20329
20341
|
const o = requireObject(raw, "update-bill payload");
|
|
20342
|
+
const { draftOnly, ...input2 } = parseCreateBillPayload(raw);
|
|
20343
|
+
if (draftOnly !== undefined) {
|
|
20344
|
+
throw new Error("draftOnly is only supported when creating a bill.");
|
|
20345
|
+
}
|
|
20330
20346
|
return {
|
|
20331
|
-
...
|
|
20347
|
+
...input2,
|
|
20332
20348
|
id: requiredString(o.id, "update-bill payload.id")
|
|
20333
20349
|
};
|
|
20334
20350
|
}
|
|
@@ -22030,9 +22046,24 @@ async function deleteItemCategoriesByIds(ids) {
|
|
|
22030
22046
|
const result = await api2.itemCategory.deleteItemCategories.mutate({ ids });
|
|
22031
22047
|
out(result);
|
|
22032
22048
|
}
|
|
22033
|
-
async function createBillFromPayload(raw) {
|
|
22034
|
-
const input2 = parseCreateBillPayload(raw);
|
|
22035
|
-
|
|
22049
|
+
async function createBillFromPayload(raw, draftOnlyFlag) {
|
|
22050
|
+
const input2 = parseCreateBillPayload(raw, draftOnlyFlag);
|
|
22051
|
+
await confirmCurrentCommand({
|
|
22052
|
+
action: input2.draftOnly ? "bill create (draft only)" : "bill create",
|
|
22053
|
+
target: `project ${input2.projectId}, supplier ${input2.supplierId}`,
|
|
22054
|
+
effects: [
|
|
22055
|
+
"state-change",
|
|
22056
|
+
"financial-write",
|
|
22057
|
+
...input2.draftOnly ? [] : ["external-write"],
|
|
22058
|
+
...input2.alreadyPaid ? ["email"] : []
|
|
22059
|
+
],
|
|
22060
|
+
details: input2.draftOnly ? "Create a DRAFT bill without approval, email, or QuickBooks writes." : "Uses the caller's normal bill creation workflow; Accounting auto-checks and Admin auto-approves unpaid bills."
|
|
22061
|
+
});
|
|
22062
|
+
const result = input2.draftOnly ? await api2.bill.createDraft.mutate({
|
|
22063
|
+
...input2,
|
|
22064
|
+
draftOnly: true,
|
|
22065
|
+
alreadyPaid: false
|
|
22066
|
+
}) : await api2.bill.create.mutate(input2);
|
|
22036
22067
|
out(result);
|
|
22037
22068
|
}
|
|
22038
22069
|
async function listQuotations(opts) {
|
|
@@ -23189,7 +23220,7 @@ var summaryFor = (legacyTarget) => {
|
|
|
23189
23220
|
if (legacyTarget === "whoami")
|
|
23190
23221
|
return "Show the active API-key identity.";
|
|
23191
23222
|
if (legacyTarget === "create-bill") {
|
|
23192
|
-
return "Create a bill or claim with an approved supplier.";
|
|
23223
|
+
return "Create a bill or claim with an approved supplier. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.";
|
|
23193
23224
|
}
|
|
23194
23225
|
if (legacyTarget === "validate-bill-selection") {
|
|
23195
23226
|
return "Validate the approved supplier and selected bill line items.";
|
|
@@ -23297,7 +23328,12 @@ var registry2 = [
|
|
|
23297
23328
|
legacyCommand("list-bills", ["bill", "list"]),
|
|
23298
23329
|
legacyCommand("list-claims", ["claim", "list"]),
|
|
23299
23330
|
legacyCommand("create-bill-approval", ["bill", "approval", "create"]),
|
|
23300
|
-
legacyCommand("create-bill", ["bill", "create"]
|
|
23331
|
+
legacyCommand("create-bill", ["bill", "create"], [], [
|
|
23332
|
+
{
|
|
23333
|
+
name: "--draft-only",
|
|
23334
|
+
description: "Create a draft for every role; no approval, email, or QuickBooks writes. Conflicts with alreadyPaid: true."
|
|
23335
|
+
}
|
|
23336
|
+
]),
|
|
23301
23337
|
legacyCommand("validate-bill-selection", ["bill", "selection", "validate"]),
|
|
23302
23338
|
legacyCommand("update-bill", ["bill", "update"]),
|
|
23303
23339
|
legacyCommand("update-bill-payment-evidence", [
|
|
@@ -23843,7 +23879,7 @@ Bills
|
|
|
23843
23879
|
isClaimable differentiates the shared bill/claim records: false = bill, true = claim.
|
|
23844
23880
|
Non-legacy supplier bills from 1 Jul 2026 00:00 SGT require approved quotation coverage linked to every selected line item, independent of the bill supplier, including already-paid bills.
|
|
23845
23881
|
create-bill-approval <billId> (also queues approval request emails)
|
|
23846
|
-
create-bill --payload '<json>' (bill.create; payload.isClaimable false = bill, true = claim; supplier must be APPROVED)
|
|
23882
|
+
create-bill --payload '<json>' [--draft-only] (bill.create; payload.isClaimable false = bill, true = claim; supplier must be APPROVED)
|
|
23847
23883
|
validate-bill-selection --payload '<json>' Require an APPROVED supplier, then verify approved quotation links for each budgetItemId; alreadyPaid never bypasses quotation checks.
|
|
23848
23884
|
stage-bill-attachment <projectId> <filePath...> [--file <path>] [--files <csv>] Upload files before create-bill; returns attachment JSON for attachments/paymentProofAttachments
|
|
23849
23885
|
cleanup-staged-bill-attachments <projectId> --keys <csv> Delete unattached staged bill/claim uploads.
|
|
@@ -24328,7 +24364,7 @@ Waiting for approval\u2026
|
|
|
24328
24364
|
}
|
|
24329
24365
|
configureAccessToken(accessToken);
|
|
24330
24366
|
}
|
|
24331
|
-
if (!resolvedCommand.isLegacyAlias && resolvedCommand.command.effects.length > 0) {
|
|
24367
|
+
if (!resolvedCommand.isLegacyAlias && cmd !== "create-bill" && resolvedCommand.command.effects.length > 0) {
|
|
24332
24368
|
await confirmCurrentCommand({
|
|
24333
24369
|
action: resolvedCommand.command.path.join(" "),
|
|
24334
24370
|
target: commandConfirmationTarget(resolvedCommand),
|
|
@@ -24695,7 +24731,8 @@ Waiting for approval\u2026
|
|
|
24695
24731
|
throw new Error("create-bill requires --payload '<json>'");
|
|
24696
24732
|
}
|
|
24697
24733
|
const raw = parseJsonFlag(String(payloadRaw), "--payload");
|
|
24698
|
-
|
|
24734
|
+
const draftOnly = flags["draft-only"] === true ? true : parseOptionalBoolFlag(flags, "draft-only");
|
|
24735
|
+
await createBillFromPayload(raw, draftOnly);
|
|
24699
24736
|
break;
|
|
24700
24737
|
}
|
|
24701
24738
|
case "validate-bill-selection": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-labs-sg/bb",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.30.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",
|
|
@@ -129,6 +129,8 @@ The following distinctions are especially important:
|
|
|
129
129
|
- 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.
|
|
130
130
|
- Bill/claim status commands have separate role rules for `CHECKED`, `APPROVED`, and `PAID`.
|
|
131
131
|
- `bb bill create --payload` uses `isClaimable=false`; `isClaimable=true` creates a claim. Claims intentionally permit some base-user flows that supplier bills do not.
|
|
132
|
+
- For a draft-only bill or claim, use `bb bill create --draft-only --payload` (or `draftOnly: true` in the payload). This forces `DRAFT` for every role and requires only `--allow-state-change --allow-financial-write`. It sends no approval/email and performs no QuickBooks writes. Do not combine it with `alreadyPaid: true`. An older API without `bill.createDraft` rejects the command; never retry with ordinary creation to bypass that error.
|
|
133
|
+
- Ordinary unpaid creation does not require `--allow-email`, but remains role-dependent: Accounting auto-checks and Admin auto-approves. It still requires `--allow-external-write`, including for possible placeholder adjustments. Already-paid creation retains the email gate.
|
|
132
134
|
- Staged attachments belong to the user in the database. Do not reuse, share, or manually construct staged keys.
|
|
133
135
|
|
|
134
136
|
## Handling authorization failures
|