@go-labs-sg/bb 2.43.0 → 2.44.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
@@ -63,7 +63,7 @@ See the [supplier review and rollout policy](../api/docs/supplier-edit-review.md
63
63
 
64
64
  ## Runtime and installation
65
65
 
66
- The CLI requires [Bun](https://bun.com/docs/installation) 1.4.0 or newer and a Budget Builder account with any active user role.
66
+ The same CLI package supports macOS and Windows. It requires [Bun](https://bun.com/docs/installation) 1.4.0 or newer and a Budget Builder account with any active user role. Windows users can run the commands below in native PowerShell; Bash and `export PATH=...` are not required.
67
67
 
68
68
  Check whether a compatible Bun version is already available:
69
69
 
@@ -106,6 +106,25 @@ Or run without installing:
106
106
  bunx @go-labs-sg/bb <command>
107
107
  ```
108
108
 
109
+ ### Historical budget reads (Admin only)
110
+
111
+ Budget reads are active-only by default. The API enforces the historical-read permission; the CLI does not grant access based on the flag alone.
112
+
113
+ ```bash
114
+ # Active budgets only (default)
115
+ bb list-budgets
116
+ bb get-budget <budget-id>
117
+
118
+ # Include inactive budgets; each form requires an ADMIN identity
119
+ bb list-budgets --active false
120
+ bb list-budgets --active all
121
+ bb get-budget <budget-id> --includeInactive
122
+ bb get-budget-items <budget-id> --includeInactive
123
+ bb get-budget-details <budget-id> --includeInactive
124
+ ```
125
+
126
+ `--active false` lists inactive budgets only. `--active all` lists active and inactive budgets. `--includeInactive` allows the three budget/detail/item commands to read inactive history. If the authenticated identity is not an Admin, the API rejects the historical request before reading budget data. For `get-budget`, the CLI requests the budget and its line items concurrently; each API read independently enforces the same active-only default and Admin-only historical opt-in.
127
+
109
128
  ## Authentication
110
129
 
111
130
  Interactive users authorize the CLI through Budget Builder's normal Google-backed
@@ -123,6 +142,11 @@ credentials through `Bun.secrets`. It never stores Google credentials. Use
123
142
  `bb auth logout` to revoke the interactive session. For SSH or containers, pass
124
143
  `--no-browser` and open the printed URL on another device.
125
144
 
145
+ Credentials use macOS Keychain or Windows Credential Manager, not plaintext files.
146
+ The per-user lock directory contains only random ownership markers used to serialize
147
+ login, refresh, and logout. macOS/Linux enforce owner-only POSIX permissions there;
148
+ Windows uses its native filesystem access controls rather than POSIX mode checks.
149
+
126
150
  Budget Builder API keys remain supported for MCP, CI, and non-interactive
127
151
  automation. When `BB_API_KEY` is present it takes precedence over the interactive
128
152
  session for that process. Create and manage automation keys under **API Keys** or
@@ -199,7 +199,7 @@
199
199
  {
200
200
  "path": ["budget", "list"],
201
201
  "legacyAliases": ["list-budgets", "list_budgets"],
202
- "summary": "List budgets.",
202
+ "summary": "List budgets. Omit --active for active budgets only; use --active false or --active all for Admin-only historical reads.",
203
203
  "globalOptions": [
204
204
  {
205
205
  "name": "--help",
@@ -240,6 +240,10 @@
240
240
  {
241
241
  "name": "--allow-financial-write",
242
242
  "description": "Allow financial-record changes in non-interactive use."
243
+ },
244
+ {
245
+ "name": "--active <true|false|all>",
246
+ "description": "Omit for active budgets only; false or all requires Admin access."
243
247
  }
244
248
  ],
245
249
  "argumentMode": "legacy-passthrough",
@@ -249,7 +253,7 @@
249
253
  {
250
254
  "path": ["budget", "get"],
251
255
  "legacyAliases": ["get-budget", "get_budget"],
252
- "summary": "Get budget.",
256
+ "summary": "Get a budget by ID. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.",
253
257
  "globalOptions": [
254
258
  {
255
259
  "name": "--help",
@@ -290,6 +294,10 @@
290
294
  {
291
295
  "name": "--allow-financial-write",
292
296
  "description": "Allow financial-record changes in non-interactive use."
297
+ },
298
+ {
299
+ "name": "--includeInactive",
300
+ "description": "Include inactive historical budgets; Admin only."
293
301
  }
294
302
  ],
295
303
  "argumentMode": "legacy-passthrough",
@@ -399,7 +407,7 @@
399
407
  {
400
408
  "path": ["budget", "item", "list"],
401
409
  "legacyAliases": ["get-budget-items", "get_budget_items"],
402
- "summary": "Get budget items.",
410
+ "summary": "Get line items for a budget. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.",
403
411
  "globalOptions": [
404
412
  {
405
413
  "name": "--help",
@@ -440,6 +448,10 @@
440
448
  {
441
449
  "name": "--allow-financial-write",
442
450
  "description": "Allow financial-record changes in non-interactive use."
451
+ },
452
+ {
453
+ "name": "--includeInactive",
454
+ "description": "Include inactive historical budgets; Admin only."
443
455
  }
444
456
  ],
445
457
  "argumentMode": "legacy-passthrough",
@@ -449,7 +461,7 @@
449
461
  {
450
462
  "path": ["budget", "detail", "get"],
451
463
  "legacyAliases": ["get-budget-details", "get_budget_details"],
452
- "summary": "Get budget details.",
464
+ "summary": "Get budget details. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.",
453
465
  "globalOptions": [
454
466
  {
455
467
  "name": "--help",
@@ -490,6 +502,10 @@
490
502
  {
491
503
  "name": "--allow-financial-write",
492
504
  "description": "Allow financial-record changes in non-interactive use."
505
+ },
506
+ {
507
+ "name": "--includeInactive",
508
+ "description": "Include inactive historical budgets; Admin only."
493
509
  }
494
510
  ],
495
511
  "argumentMode": "legacy-passthrough",
@@ -51,7 +51,7 @@ Effects: none.
51
51
 
52
52
  ## `bb budget list`
53
53
 
54
- List budgets.
54
+ List budgets. Omit --active for active budgets only; use --active false or --active all for Admin-only historical reads.
55
55
 
56
56
  Legacy aliases: `list-budgets`, `list_budgets`.
57
57
 
@@ -59,7 +59,7 @@ Effects: none.
59
59
 
60
60
  ## `bb budget get`
61
61
 
62
- Get budget.
62
+ Get a budget by ID. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.
63
63
 
64
64
  Legacy aliases: `get-budget`, `get_budget`.
65
65
 
@@ -83,7 +83,7 @@ Effects: state-change, external-write, financial-write.
83
83
 
84
84
  ## `bb budget item list`
85
85
 
86
- Get budget items.
86
+ Get line items for a budget. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.
87
87
 
88
88
  Legacy aliases: `get-budget-items`, `get_budget_items`.
89
89
 
@@ -91,7 +91,7 @@ Effects: none.
91
91
 
92
92
  ## `bb budget detail get`
93
93
 
94
- Get budget details.
94
+ Get budget details. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.
95
95
 
96
96
  Legacy aliases: `get-budget-details`, `get_budget_details`.
97
97
 
package/dist/index.js CHANGED
@@ -6071,7 +6071,7 @@ var ensurePrivateCredentialLockDirectory = async () => {
6071
6071
  return directoryStat;
6072
6072
  };
6073
6073
  const directoryStat = await inspectDirectory();
6074
- if ((directoryStat.mode & 63) !== 0) {
6074
+ if (process.platform !== "win32" && (directoryStat.mode & 63) !== 0) {
6075
6075
  await chmod(directory, 448);
6076
6076
  const securedDirectoryStat = await inspectDirectory();
6077
6077
  if ((securedDirectoryStat.mode & 63) !== 0) {
@@ -8604,6 +8604,7 @@ async function listBudgets(opts) {
8604
8604
  createdBy: opts.createdBy,
8605
8605
  dateFrom: opts.dateFrom,
8606
8606
  dateTo: opts.dateTo,
8607
+ active: opts.active,
8607
8608
  sortBy: opts.sortBy,
8608
8609
  sortDir: opts.sortDir,
8609
8610
  page: opts.page ?? 1,
@@ -8613,10 +8614,16 @@ async function listBudgets(opts) {
8613
8614
  const result = await api2.budget.getAllBudgets.query(input);
8614
8615
  out(result);
8615
8616
  }
8616
- async function getBudget(id) {
8617
+ async function getBudget(id, opts) {
8617
8618
  const [budget, items] = await Promise.all([
8618
- api2.budget.getBudget.query({ id }),
8619
- api2.budget.getBudgetItems.query({ budgetId: id })
8619
+ api2.budget.getBudget.query({
8620
+ id,
8621
+ includeInactive: opts?.includeInactive ?? false
8622
+ }),
8623
+ api2.budget.getBudgetItems.query({
8624
+ budgetId: id,
8625
+ includeInactive: opts?.includeInactive ?? false
8626
+ })
8620
8627
  ]);
8621
8628
  out({ budget, items });
8622
8629
  }
@@ -9223,8 +9230,11 @@ async function getBillDetails(id) {
9223
9230
  const result = await api2.bill.getById.query({ id });
9224
9231
  out(result);
9225
9232
  }
9226
- async function getBudgetItemsOnly(budgetId) {
9227
- const items = await api2.budget.getBudgetItems.query({ budgetId });
9233
+ async function getBudgetItemsOnly(budgetId, opts) {
9234
+ const items = await api2.budget.getBudgetItems.query({
9235
+ budgetId,
9236
+ includeInactive: opts?.includeInactive ?? false
9237
+ });
9228
9238
  out(items);
9229
9239
  }
9230
9240
  async function addBudgetItems(opts) {
@@ -9325,8 +9335,11 @@ var sendEstimateToContactPersonFromPayload = async (raw) => {
9325
9335
  const result = await api2.email.sendEstimateToContactPerson.mutate(input);
9326
9336
  out(result);
9327
9337
  };
9328
- async function getBudgetDetails(budgetId) {
9329
- const detail = await api2.budget.getBudgetDetail.query({ id: budgetId });
9338
+ async function getBudgetDetails(budgetId, opts) {
9339
+ const detail = await api2.budget.getBudgetDetail.query({
9340
+ id: budgetId,
9341
+ includeInactive: opts?.includeInactive ?? false
9342
+ });
9330
9343
  out(detail);
9331
9344
  }
9332
9345
  async function createBudgetFromPayload(raw) {
@@ -10903,6 +10916,18 @@ var summaryFor = (legacyTarget) => {
10903
10916
  }
10904
10917
  if (legacyTarget === "whoami")
10905
10918
  return "Show the active API-key identity.";
10919
+ if (legacyTarget === "list-budgets") {
10920
+ return "List budgets. Omit --active for active budgets only; use --active false or --active all for Admin-only historical reads.";
10921
+ }
10922
+ if (legacyTarget === "get-budget") {
10923
+ return "Get a budget by ID. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.";
10924
+ }
10925
+ if (legacyTarget === "get-budget-items") {
10926
+ return "Get line items for a budget. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.";
10927
+ }
10928
+ if (legacyTarget === "get-budget-details") {
10929
+ return "Get budget details. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.";
10930
+ }
10906
10931
  if (legacyTarget === "create-bill") {
10907
10932
  return "Create a bill or claim with an approved supplier. All amount fields exclude GST: amount is the portion billed; extractedAmount is the full invoice subtotal; lineAmounts and quotationAllocations also exclude GST. Example: 1800 + 162 GST = 1962 total requires amount=1800 and extractedAmount=1800 for a full bill. The CLI does not strip GST from input. --draft-only guarantees DRAFT without email or external writes; otherwise unpaid creation skips email permission but keeps external-write permission.";
10908
10933
  }
@@ -10977,12 +11002,32 @@ var registry2 = [
10977
11002
  authCommand("status", "Show the active CLI authentication method and session status."),
10978
11003
  authCommand("logout", "Revoke the interactive CLI session and remove its local credential."),
10979
11004
  legacyCommand("whoami", ["auth", "whoami"]),
10980
- legacyCommand("list-budgets", ["budget", "list"]),
10981
- legacyCommand("get-budget", ["budget", "get"]),
11005
+ legacyCommand("list-budgets", ["budget", "list"], [], [
11006
+ {
11007
+ name: "--active <true|false|all>",
11008
+ description: "Omit for active budgets only; false or all requires Admin access."
11009
+ }
11010
+ ]),
11011
+ legacyCommand("get-budget", ["budget", "get"], [], [
11012
+ {
11013
+ name: "--includeInactive",
11014
+ description: "Include inactive historical budgets; Admin only."
11015
+ }
11016
+ ]),
10982
11017
  legacyCommand("upload-budget-attachment", ["budget", "attachment", "upload"]),
10983
11018
  legacyCommand("mark-budget-won", ["budget", "won", "mark"]),
10984
- legacyCommand("get-budget-items", ["budget", "item", "list"]),
10985
- legacyCommand("get-budget-details", ["budget", "detail", "get"]),
11019
+ legacyCommand("get-budget-items", ["budget", "item", "list"], [], [
11020
+ {
11021
+ name: "--includeInactive",
11022
+ description: "Include inactive historical budgets; Admin only."
11023
+ }
11024
+ ]),
11025
+ legacyCommand("get-budget-details", ["budget", "detail", "get"], [], [
11026
+ {
11027
+ name: "--includeInactive",
11028
+ description: "Include inactive historical budgets; Admin only."
11029
+ }
11030
+ ]),
10986
11031
  legacyCommand("get-budget-categories", ["budget", "category", "list"]),
10987
11032
  legacyCommand("get-budget-versions", ["budget", "version", "list"]),
10988
11033
  legacyCommand("rename-budget-version", ["budget", "version", "rename"]),
@@ -11526,6 +11571,22 @@ function parseOptionalBoolFlag(flags, key) {
11526
11571
  return false;
11527
11572
  throw new Error(`--${key} must be true or false.`);
11528
11573
  }
11574
+ function parseBudgetActiveFlag(flags) {
11575
+ const raw = flags.active;
11576
+ if (raw === undefined)
11577
+ return;
11578
+ if (raw === true) {
11579
+ throw new Error("--active requires true, false, or all.");
11580
+ }
11581
+ const value = String(raw).toLowerCase();
11582
+ if (value === "all")
11583
+ return "all";
11584
+ if (value === "true" || value === "1")
11585
+ return true;
11586
+ if (value === "false" || value === "0")
11587
+ return false;
11588
+ throw new Error("--active must be true, false, or all.");
11589
+ }
11529
11590
  var CUSTOMER_INVOICE_SORT_FIELDS = [
11530
11591
  "createdAt",
11531
11592
  "status",
@@ -12219,6 +12280,7 @@ Waiting for approval\u2026
12219
12280
  createdBy: getFlag(flags, "createdBy"),
12220
12281
  dateFrom: getFlag(flags, "dateFrom"),
12221
12282
  dateTo: getFlag(flags, "dateTo"),
12283
+ active: parseBudgetActiveFlag(flags),
12222
12284
  sortBy: getFlag(flags, "sortBy"),
12223
12285
  sortDir: getFlag(flags, "sortDir"),
12224
12286
  page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
@@ -12231,7 +12293,9 @@ Waiting for approval\u2026
12231
12293
  const id = positional[0];
12232
12294
  if (!id)
12233
12295
  throw new Error("get-budget requires <id>");
12234
- await getBudget(id);
12296
+ await getBudget(id, {
12297
+ includeInactive: flags.includeInactive === true ? true : parseOptionalBoolFlag(flags, "includeInactive") ?? false
12298
+ });
12235
12299
  break;
12236
12300
  }
12237
12301
  case "upload-budget-attachment": {
@@ -12258,14 +12322,18 @@ Waiting for approval\u2026
12258
12322
  const id = positional[0];
12259
12323
  if (!id)
12260
12324
  throw new Error("get-budget-items requires <budgetId>");
12261
- await getBudgetItemsOnly(id);
12325
+ await getBudgetItemsOnly(id, {
12326
+ includeInactive: flags.includeInactive === true ? true : parseOptionalBoolFlag(flags, "includeInactive") ?? false
12327
+ });
12262
12328
  break;
12263
12329
  }
12264
12330
  case "get-budget-details": {
12265
12331
  const id = positional[0];
12266
12332
  if (!id)
12267
12333
  throw new Error("get-budget-details requires <budgetId>");
12268
- await getBudgetDetails(id);
12334
+ await getBudgetDetails(id, {
12335
+ includeInactive: flags.includeInactive === true ? true : parseOptionalBoolFlag(flags, "includeInactive") ?? false
12336
+ });
12269
12337
  break;
12270
12338
  }
12271
12339
  case "get-budget-categories": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/bb",
3
- "version": "2.43.0",
3
+ "version": "2.44.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",