@go-labs-sg/bb 1.2.8 → 1.2.9

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
@@ -56,12 +56,12 @@ Global options and flags use `--key=value` or `--key value` (see `bb help`).
56
56
 
57
57
  | Area | Commands (non-exhaustive) |
58
58
  | --- | --- |
59
- | **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`, `update-budget-status`, `create-budget` / `update-budget` (`--payload`), `delete-budget`, `create-budget-approval`, `add-budget-items`, `update-budget-item`, `remove-budget-item`, `reorder-budget-items`, `update-budget-item-supplier`, `create-budget-category`, `update-budget-category`, `delete-budget-category`, `update-budget-commission`, `update-budget-discount` (`--payload` where noted), `upload-budget-attachment` (`<budgetId>` + local file path; uses `attachment.requestBudgetAttachmentUpload` + PUT + `attachment.confirmBudgetAttachment`) |
60
- | **Bills** | `list-bills`, `list-claims`, `create-bill` (`--payload`), `update-bill` (`--payload`), `delete-bill`, `create-bill-approval`, `update-bill-status`, `patch-bill-payment` (PAID bills: `--paymentTrackingUrl`, `--paymentReference`, `--quickbooksBillId`, `--paymentDate` ISO; clear with `--clearPaymentTrackingUrl` / `--clearPaymentReference` / `--clearQuickbooksBillId` / `--clearPaymentDate` `true`), `patch-bill-invoice-number`, `get-bill-attachments`, `upload-bill-attachment` (`<billId>` + local path), `get-bill-details` |
61
- | **Approvals** | `list-approvals` / `get-pending-approvals`, `approve-bill`, `reject-bill`, `approve-budget`, `reject-budget`, `approve-supplier`, `reject-supplier` |
59
+ | **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`, `update-budget-status`, `create-budget` / `update-budget` (`--payload`), `delete-budget`, `create-budget-approval` (also sends approval request emails), `add-budget-items`, `update-budget-item`, `remove-budget-item`, `reorder-budget-items`, `update-budget-item-supplier`, `create-budget-category`, `update-budget-category`, `delete-budget-category`, `update-budget-commission`, `update-budget-discount` (`--payload` where noted), `upload-budget-attachment` (`<budgetId>` + local file path; uses `attachment.requestBudgetAttachmentUpload` + PUT + `attachment.confirmBudgetAttachment`) |
60
+ | **Bills** | `list-bills`, `list-claims`, `create-bill` (`--payload`), `update-bill` (`--payload`), `delete-bill`, `create-bill-approval` (also sends approval request emails), `update-bill-status`, `patch-bill-payment` (PAID bills: `--paymentTrackingUrl`, `--paymentReference`, `--quickbooksBillId`, `--paymentDate` ISO; clear with `--clearPaymentTrackingUrl` / `--clearPaymentReference` / `--clearQuickbooksBillId` / `--clearPaymentDate` `true`), `patch-bill-invoice-number`, `get-bill-attachments`, `upload-bill-attachment` (`<billId>` + local path), `get-bill-details` |
61
+ | **Approvals** | `list-approvals` / `get-pending-approvals`, `approve-bill` / `reject-bill` (send reply email), `approve-budget` / `reject-budget` (send reply email), `approve-supplier` / `reject-supplier` (send reply email) |
62
62
  | **Companies & projects** | `list-companies`, `get-company`, `create-company`, `update-company` (`--payload`), `delete-company`, `list-projects`, `get-project`, `create-project` (`--asanaTaskId`, `--slackChannelId`, `--slackChannelUrl`, `--slackChannelName`, …; optional `--requestQboAccountantNotification false` to skip QBO accountant emails), `update-project` (`--payload`, optional `requestQboAccountantNotification` in JSON), `delete-project`, `update-project-status` (`<id>` `<status>`: `PITCH` \| `EVENT` \| `COMPLETED` \| `LOST`) |
63
63
  | **Contacts** | `list-contacts`, `create-contact-person` (`--payload`), `update-contact-person` (`--payload`) |
64
- | **Suppliers & items** | `list-suppliers`, `create-supplier` (`--payload`; when supplier status is `PENDING_APPROVAL`, also runs `supplier.createSupplierApproval` and `email.sendSupplierApprovalRequestEmail`), `update-supplier` (`--payload`), `delete-suppliers` (`--ids` CSV; admin), `create-certification` / `create-payment-method` / `create-supplier-role` / `create-supplier-tag` (`--name`), `get-supplier-details`, `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) |
64
+ | **Suppliers & items** | `list-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), `create-certification` / `create-payment-method` / `create-supplier-role` / `create-supplier-tag` (`--name`), `get-supplier-details`, `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) |
65
65
  | **Dashboard & users** | `list-users`, `get-user-performance`, `get-dashboard`, `get-monthly-metrics`, `get-system-overview`, `get-estimate-performance`, `get-financial-overview` |
66
66
  | **Errors** | `get-recent-errors`, `get-error-metrics` |
67
67
  | **Historical / benchmarks** | `get-approved-budgets`, `get-budget-category-benchmarks`, `get-item-pricing-history`, `get-supplier-pricing-history` |
package/dist/commands.js CHANGED
@@ -81,7 +81,21 @@ export async function approveBill(billId) {
81
81
  billApprovalId: approval.id,
82
82
  status: "APPROVED",
83
83
  });
84
- out(result);
84
+ let email;
85
+ try {
86
+ await api.email.sendReplyBillApprovalEmail.mutate(result.updatedApproval);
87
+ email = { sent: true };
88
+ }
89
+ catch (error) {
90
+ email = {
91
+ sent: false,
92
+ error: error instanceof Error ? error.message : String(error),
93
+ };
94
+ }
95
+ out({
96
+ ...result,
97
+ email,
98
+ });
85
99
  }
86
100
  export async function listApprovals(opts) {
87
101
  const notifications = await api.budget.getNotifications.query();
@@ -137,7 +151,28 @@ export async function listSuppliers(opts) {
137
151
  // --- Bills (MCP parity) ---
138
152
  export async function createBillApproval(billId) {
139
153
  const result = await api.bill.createApproval.mutate({ billId });
140
- out(result);
154
+ const approvalIds = (result.results ?? []).map((item) => ({ id: item.id }));
155
+ let email;
156
+ if (approvalIds.length === 0) {
157
+ email = { sent: false, count: 0, error: "No approval ids returned" };
158
+ }
159
+ else {
160
+ try {
161
+ await api.email.sendBillApprovalRequestEmail.mutate(approvalIds);
162
+ email = { sent: true, count: approvalIds.length };
163
+ }
164
+ catch (error) {
165
+ email = {
166
+ sent: false,
167
+ count: approvalIds.length,
168
+ error: error instanceof Error ? error.message : String(error),
169
+ };
170
+ }
171
+ }
172
+ out({
173
+ ...result,
174
+ email,
175
+ });
141
176
  }
142
177
  export async function updateBillStatus(opts) {
143
178
  const result = await api.bill.updateStatus.mutate({
@@ -264,7 +299,28 @@ export async function getBudgetVersions(budgetId) {
264
299
  }
265
300
  export async function createBudgetApproval(budgetId) {
266
301
  const result = await api.budget.createBudgetApproval.mutate({ budgetId });
267
- out(result);
302
+ const approvalIds = (result.results ?? []).map((item) => ({ id: item.id }));
303
+ let email;
304
+ if (approvalIds.length === 0) {
305
+ email = { sent: false, count: 0, error: "No approval ids returned" };
306
+ }
307
+ else {
308
+ try {
309
+ await api.email.sendApprovalRequestEmail.mutate(approvalIds);
310
+ email = { sent: true, count: approvalIds.length };
311
+ }
312
+ catch (error) {
313
+ email = {
314
+ sent: false,
315
+ count: approvalIds.length,
316
+ error: error instanceof Error ? error.message : String(error),
317
+ };
318
+ }
319
+ }
320
+ out({
321
+ ...result,
322
+ email,
323
+ });
268
324
  }
269
325
  export async function getBudgetDetails(budgetId) {
270
326
  const detail = await api.budget.getBudgetDetail.query({ id: budgetId });
@@ -412,8 +468,59 @@ export async function createSupplierFromPayload(raw) {
412
468
  }
413
469
  export async function updateSupplierFromPayload(raw) {
414
470
  const input = parseSupplierUpdatePayload(raw);
415
- const result = await api.supplier.updateSupplier.mutate(input);
416
- out(result);
471
+ const updated = await api.supplier.updateSupplier.mutate(input);
472
+ const supplier = updated.result;
473
+ if (supplier.status !== "PENDING_APPROVAL") {
474
+ out(updated);
475
+ return;
476
+ }
477
+ let approvalResult;
478
+ try {
479
+ const approval = await api.supplier.createSupplierApproval.mutate({
480
+ supplierId: supplier.id,
481
+ });
482
+ const approvalIds = (approval.results ?? []).map((item) => ({
483
+ id: item.id,
484
+ }));
485
+ if (approvalIds.length === 0) {
486
+ approvalResult = {
487
+ requested: true,
488
+ approvalCount: 0,
489
+ email: { sent: false, count: 0, error: "No approval ids returned" },
490
+ };
491
+ }
492
+ else {
493
+ try {
494
+ await api.email.sendSupplierApprovalRequestEmail.mutate(approvalIds);
495
+ approvalResult = {
496
+ requested: true,
497
+ approvalCount: approvalIds.length,
498
+ email: { sent: true, count: approvalIds.length },
499
+ };
500
+ }
501
+ catch (error) {
502
+ approvalResult = {
503
+ requested: true,
504
+ approvalCount: approvalIds.length,
505
+ email: {
506
+ sent: false,
507
+ count: approvalIds.length,
508
+ error: error instanceof Error ? error.message : String(error),
509
+ },
510
+ };
511
+ }
512
+ }
513
+ }
514
+ catch (error) {
515
+ approvalResult = {
516
+ requested: false,
517
+ error: error instanceof Error ? error.message : String(error),
518
+ };
519
+ }
520
+ out({
521
+ ...updated,
522
+ supplierApproval: approvalResult,
523
+ });
417
524
  }
418
525
  export async function createItemFromPayload(raw) {
419
526
  const input = parseItemCreatePayload(raw);
@@ -457,11 +564,25 @@ export async function approveBudget(budgetId) {
457
564
  if (!approval) {
458
565
  throw new Error("No pending budget approval found for this budget and approver.");
459
566
  }
460
- await api.budget.updateBudgetApproval.mutate({
567
+ const result = await api.budget.updateBudgetApproval.mutate({
461
568
  budgetApprovalId: approval.id,
462
569
  status: ExtendedApprovalStatus.APPROVED,
463
570
  });
464
- out({ success: true, message: "Budget approved successfully" });
571
+ let email;
572
+ try {
573
+ await api.email.sendReplyApprovalEmail.mutate(result.updatedApproval);
574
+ email = { sent: true };
575
+ }
576
+ catch (error) {
577
+ email = {
578
+ sent: false,
579
+ error: error instanceof Error ? error.message : String(error),
580
+ };
581
+ }
582
+ out({
583
+ ...result,
584
+ email,
585
+ });
465
586
  }
466
587
  export async function rejectBudget(budgetId, reason) {
467
588
  const notifications = await api.budget.getNotifications.query();
@@ -474,12 +595,26 @@ export async function rejectBudget(budgetId, reason) {
474
595
  if (!approval) {
475
596
  throw new Error("No pending budget approval found for this budget and approver.");
476
597
  }
477
- await api.budget.updateBudgetApproval.mutate({
598
+ const result = await api.budget.updateBudgetApproval.mutate({
478
599
  budgetApprovalId: approval.id,
479
600
  status: ExtendedApprovalStatus.REJECTED,
480
601
  rejectionReason: reason,
481
602
  });
482
- out({ success: true, message: "Budget rejected" });
603
+ let email;
604
+ try {
605
+ await api.email.sendReplyApprovalEmail.mutate(result.updatedApproval);
606
+ email = { sent: true };
607
+ }
608
+ catch (error) {
609
+ email = {
610
+ sent: false,
611
+ error: error instanceof Error ? error.message : String(error),
612
+ };
613
+ }
614
+ out({
615
+ ...result,
616
+ email,
617
+ });
483
618
  }
484
619
  export async function getSupplierDetails(supplierId) {
485
620
  const supplier = await api.supplier.getSupplierById.query({
@@ -497,11 +632,28 @@ export async function approveSupplier(supplierId) {
497
632
  if (!approval) {
498
633
  throw new Error("No pending supplier approval found.");
499
634
  }
500
- await api.supplier.updateSupplierApproval.mutate({
635
+ const result = await api.supplier.updateSupplierApproval.mutate({
501
636
  supplierApprovalId: approval.id,
502
637
  status: ExtendedApprovalStatus.APPROVED,
503
638
  });
504
- out({ success: true, message: "Supplier approved successfully" });
639
+ let email;
640
+ try {
641
+ await api.email.sendReplySupplierApprovalEmail.mutate({
642
+ ...result.updatedApproval,
643
+ rejectionReason: result.updatedApproval.rejectionReason ?? undefined,
644
+ });
645
+ email = { sent: true };
646
+ }
647
+ catch (error) {
648
+ email = {
649
+ sent: false,
650
+ error: error instanceof Error ? error.message : String(error),
651
+ };
652
+ }
653
+ out({
654
+ ...result,
655
+ email,
656
+ });
505
657
  }
506
658
  export async function rejectSupplier(supplierId, reason) {
507
659
  const notifications = await api.budget.getNotifications.query();
@@ -513,12 +665,29 @@ export async function rejectSupplier(supplierId, reason) {
513
665
  if (!approval) {
514
666
  throw new Error("No pending supplier approval found.");
515
667
  }
516
- await api.supplier.updateSupplierApproval.mutate({
668
+ const result = await api.supplier.updateSupplierApproval.mutate({
517
669
  supplierApprovalId: approval.id,
518
670
  status: ExtendedApprovalStatus.REJECTED,
519
671
  rejectionReason: reason,
520
672
  });
521
- out({ success: true, message: "Supplier rejected" });
673
+ let email;
674
+ try {
675
+ await api.email.sendReplySupplierApprovalEmail.mutate({
676
+ ...result.updatedApproval,
677
+ rejectionReason: result.updatedApproval.rejectionReason ?? undefined,
678
+ });
679
+ email = { sent: true };
680
+ }
681
+ catch (error) {
682
+ email = {
683
+ sent: false,
684
+ error: error instanceof Error ? error.message : String(error),
685
+ };
686
+ }
687
+ out({
688
+ ...result,
689
+ email,
690
+ });
522
691
  }
523
692
  export async function rejectBill(billId, reason) {
524
693
  const notifications = await api.budget.getNotifications.query();
@@ -530,12 +699,26 @@ export async function rejectBill(billId, reason) {
530
699
  if (!approval) {
531
700
  throw new Error("No pending bill approval found.");
532
701
  }
533
- await api.bill.updateApproval.mutate({
702
+ const result = await api.bill.updateApproval.mutate({
534
703
  billApprovalId: approval.id,
535
704
  status: ExtendedApprovalStatus.REJECTED,
536
705
  rejectionReason: reason,
537
706
  });
538
- out({ success: true, message: "Bill rejected" });
707
+ let email;
708
+ try {
709
+ await api.email.sendReplyBillApprovalEmail.mutate(result.updatedApproval);
710
+ email = { sent: true };
711
+ }
712
+ catch (error) {
713
+ email = {
714
+ sent: false,
715
+ error: error instanceof Error ? error.message : String(error),
716
+ };
717
+ }
718
+ out({
719
+ ...result,
720
+ email,
721
+ });
539
722
  }
540
723
  // --- Companies & projects ---
541
724
  export async function listCompanies(opts) {
package/dist/index.js CHANGED
@@ -79,7 +79,7 @@ Budgets
79
79
  create-budget --payload '<json>' (budget.createBudget; ISO dates for date/startTime/endTime)
80
80
  update-budget --payload '<json>' (budget.updateBudget; must include id)
81
81
  delete-budget <budgetId>
82
- create-budget-approval <budgetId>
82
+ create-budget-approval <budgetId> (also sends approval request emails)
83
83
  add-budget-items --budgetId --items '[{"itemId":"…","quantity":1,"markup":30},…]'
84
84
  update-budget-item --id <budgetItemId> [--description] [--note] [--quantity] [--markup] [--cost] [--unitPrice] [--isFreeOfCharge true|false] [--gstInclusive true|false]
85
85
  remove-budget-item <budgetItemId>
@@ -95,7 +95,7 @@ Budgets
95
95
  Bills
96
96
  list-bills [--projectId] [--budgetId] [--status CSV] [--search <text>] [--isClaimable true|false] [--createdByIds <csv>] [--sortBy createdAt|amount|status] [--sortDir asc|desc] [--page] [--pageSize]
97
97
  list-claims same flags as list-bills; only reimbursable claims (ignores --isClaimable)
98
- create-bill-approval <billId>
98
+ create-bill-approval <billId> (also sends approval request emails)
99
99
  create-bill --payload '<json>' (bill.create; use upload-bill-attachment after create to add files)
100
100
  update-bill --payload '<json>' (bill.update; must include id)
101
101
  delete-bill <billId>
@@ -110,12 +110,12 @@ Bills
110
110
 
111
111
  Approvals
112
112
  list-approvals | get-pending-approvals [--type budget|supplier|bill|all]
113
- approve-bill <billId>
114
- reject-bill <billId> --reason <text>
115
- approve-budget <budgetId>
116
- reject-budget <budgetId> --reason <text>
117
- approve-supplier <supplierId>
118
- reject-supplier <supplierId> --reason <text>
113
+ approve-bill <billId> (also sends requester reply email)
114
+ reject-bill <billId> --reason <text> (also sends requester reply email)
115
+ approve-budget <budgetId> (also sends requester reply email)
116
+ reject-budget <budgetId> --reason <text> (also sends requester reply email)
117
+ approve-supplier <supplierId> (also sends requester reply email)
118
+ reject-supplier <supplierId> --reason <text> (also sends requester reply email)
119
119
 
120
120
  Companies & projects
121
121
  list-companies [--name] [--createdBy] [--page] [--perPage]
@@ -139,7 +139,7 @@ Contacts
139
139
  Suppliers & items
140
140
  list-suppliers [--name] [--page] [--perPage]
141
141
  create-supplier --payload '<json>' (supplier.createSupplier; auto-requests approval + emails when status is PENDING_APPROVAL)
142
- update-supplier --payload '<json>' (supplier.updateSupplier; must include id)
142
+ update-supplier --payload '<json>' (supplier.updateSupplier; must include id; auto-requests approval + emails when status is PENDING_APPROVAL)
143
143
  delete-suppliers --ids <csv> (supplier.deleteSuppliers; admin)
144
144
  create-certification --name (supplier.createCertification)
145
145
  create-payment-method --name (supplier.createPaymentMethod)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/bb",
3
- "version": "1.2.8",
3
+ "version": "1.2.9",
4
4
  "description": "Budget Builder CLI — list budgets, bills, approvals, suppliers; approve bills; change status. For AI agents (e.g. Chuck/OpenClaw).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",