@go-labs-sg/bb 1.2.7 → 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`), `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 });
@@ -374,13 +430,97 @@ export async function deleteBillById(id) {
374
430
  }
375
431
  export async function createSupplierFromPayload(raw) {
376
432
  const input = parseSupplierCreatePayload(raw);
377
- const result = await api.supplier.createSupplier.mutate(input);
378
- out(result);
433
+ const created = await api.supplier.createSupplier.mutate(input);
434
+ const supplier = created.result;
435
+ if (supplier.status !== "PENDING_APPROVAL") {
436
+ out(created);
437
+ return;
438
+ }
439
+ const approval = await api.supplier.createSupplierApproval.mutate({
440
+ supplierId: supplier.id,
441
+ });
442
+ let emailResult;
443
+ const approvalIds = (approval.results ?? []).map((item) => ({ id: item.id }));
444
+ if (approvalIds.length === 0) {
445
+ emailResult = { sent: false, count: 0, error: "No approval ids returned" };
446
+ }
447
+ else {
448
+ try {
449
+ await api.email.sendSupplierApprovalRequestEmail.mutate(approvalIds);
450
+ emailResult = { sent: true, count: approvalIds.length };
451
+ }
452
+ catch (error) {
453
+ emailResult = {
454
+ sent: false,
455
+ count: approvalIds.length,
456
+ error: error instanceof Error ? error.message : String(error),
457
+ };
458
+ }
459
+ }
460
+ out({
461
+ ...created,
462
+ supplierApproval: {
463
+ requested: true,
464
+ approvalCount: approvalIds.length,
465
+ email: emailResult,
466
+ },
467
+ });
379
468
  }
380
469
  export async function updateSupplierFromPayload(raw) {
381
470
  const input = parseSupplierUpdatePayload(raw);
382
- const result = await api.supplier.updateSupplier.mutate(input);
383
- 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
+ });
384
524
  }
385
525
  export async function createItemFromPayload(raw) {
386
526
  const input = parseItemCreatePayload(raw);
@@ -424,11 +564,25 @@ export async function approveBudget(budgetId) {
424
564
  if (!approval) {
425
565
  throw new Error("No pending budget approval found for this budget and approver.");
426
566
  }
427
- await api.budget.updateBudgetApproval.mutate({
567
+ const result = await api.budget.updateBudgetApproval.mutate({
428
568
  budgetApprovalId: approval.id,
429
569
  status: ExtendedApprovalStatus.APPROVED,
430
570
  });
431
- 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
+ });
432
586
  }
433
587
  export async function rejectBudget(budgetId, reason) {
434
588
  const notifications = await api.budget.getNotifications.query();
@@ -441,12 +595,26 @@ export async function rejectBudget(budgetId, reason) {
441
595
  if (!approval) {
442
596
  throw new Error("No pending budget approval found for this budget and approver.");
443
597
  }
444
- await api.budget.updateBudgetApproval.mutate({
598
+ const result = await api.budget.updateBudgetApproval.mutate({
445
599
  budgetApprovalId: approval.id,
446
600
  status: ExtendedApprovalStatus.REJECTED,
447
601
  rejectionReason: reason,
448
602
  });
449
- 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
+ });
450
618
  }
451
619
  export async function getSupplierDetails(supplierId) {
452
620
  const supplier = await api.supplier.getSupplierById.query({
@@ -464,11 +632,28 @@ export async function approveSupplier(supplierId) {
464
632
  if (!approval) {
465
633
  throw new Error("No pending supplier approval found.");
466
634
  }
467
- await api.supplier.updateSupplierApproval.mutate({
635
+ const result = await api.supplier.updateSupplierApproval.mutate({
468
636
  supplierApprovalId: approval.id,
469
637
  status: ExtendedApprovalStatus.APPROVED,
470
638
  });
471
- 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
+ });
472
657
  }
473
658
  export async function rejectSupplier(supplierId, reason) {
474
659
  const notifications = await api.budget.getNotifications.query();
@@ -480,12 +665,29 @@ export async function rejectSupplier(supplierId, reason) {
480
665
  if (!approval) {
481
666
  throw new Error("No pending supplier approval found.");
482
667
  }
483
- await api.supplier.updateSupplierApproval.mutate({
668
+ const result = await api.supplier.updateSupplierApproval.mutate({
484
669
  supplierApprovalId: approval.id,
485
670
  status: ExtendedApprovalStatus.REJECTED,
486
671
  rejectionReason: reason,
487
672
  });
488
- 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
+ });
489
691
  }
490
692
  export async function rejectBill(billId, reason) {
491
693
  const notifications = await api.budget.getNotifications.query();
@@ -497,12 +699,26 @@ export async function rejectBill(billId, reason) {
497
699
  if (!approval) {
498
700
  throw new Error("No pending bill approval found.");
499
701
  }
500
- await api.bill.updateApproval.mutate({
702
+ const result = await api.bill.updateApproval.mutate({
501
703
  billApprovalId: approval.id,
502
704
  status: ExtendedApprovalStatus.REJECTED,
503
705
  rejectionReason: reason,
504
706
  });
505
- 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
+ });
506
722
  }
507
723
  // --- Companies & projects ---
508
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]
@@ -138,8 +138,8 @@ Contacts
138
138
 
139
139
  Suppliers & items
140
140
  list-suppliers [--name] [--page] [--perPage]
141
- create-supplier --payload '<json>' (supplier.createSupplier)
142
- update-supplier --payload '<json>' (supplier.updateSupplier; must include id)
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; 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.7",
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",