@go-labs-sg/bb 1.6.0 → 1.7.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 +10 -6
- package/dist/commands.js +81 -10
- package/dist/index.js +77 -19
- package/dist/parse-cli-enums.js +20 -1
- package/dist/parse-mutation-payload.js +1 -1
- package/dist/prisma-enums.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# bb
|
|
2
2
|
|
|
3
|
-
Command-line interface for **Budget Builder**. Call budgets, bills, approvals, suppliers, dashboard, and more from your terminal or from tools that don’t support MCP (e.g. some AI agents). Responses are JSON on stdout.
|
|
3
|
+
Command-line interface for **Budget Builder**. Call budgets, bills, claims, approvals, suppliers, dashboard, and more from your terminal or from tools that don’t support MCP (e.g. some AI agents). Responses are JSON on stdout.
|
|
4
|
+
|
|
5
|
+
For AI agents: bills and claims are stored as the same underlying bill records. Use the `isClaimable` column/flag to distinguish them: `isClaimable=false` means a normal bill, while `isClaimable=true` means a reimbursable claim.
|
|
6
|
+
|
|
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.
|
|
4
8
|
|
|
5
9
|
**Registry:** [`@go-labs-sg/bb`](https://www.npmjs.com/package/@go-labs-sg/bb)
|
|
6
10
|
|
|
@@ -39,7 +43,7 @@ The CLI talks to the production API: `https://budget-builder.getout.events`.
|
|
|
39
43
|
bb help
|
|
40
44
|
bb list-budgets
|
|
41
45
|
bb get-budget <budget-id>
|
|
42
|
-
bb update-budget-status <budget-id> <status>
|
|
46
|
+
bb update-budget-status <budget-id> <status> [--markProjectWon true]
|
|
43
47
|
bb list-bills
|
|
44
48
|
bb approve-bill <bill-id>
|
|
45
49
|
bb list-approvals
|
|
@@ -56,12 +60,12 @@ Global options and flags use `--key=value` or `--key value` (see `bb help`).
|
|
|
56
60
|
|
|
57
61
|
| Area | Commands (non-exhaustive) |
|
|
58
62
|
| --- | --- |
|
|
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
|
|
60
|
-
| **Bills** | `list-bills
|
|
63
|
+
| **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` (`--markProjectWon true` can move the parent project to `WON` for accepted/closed estimates), `mark-budget-won` (`<budgetId>` + proof file path; optional `--markProjectWon true`), `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`) |
|
|
64
|
+
| **Bills / claims** | `list-bills` (`--isClaimable false` for bills, `--isClaimable true` for claims, omit for both), `list-claims` (claims only), `create-bill` (`--payload`; set `isClaimable=false` for a bill or `isClaimable=true` for a claim), `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
65
|
| **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
|
-
| **Companies & projects** | `list-companies`, `get-company`, `create-company`, `update-company` (`--payload`), `delete-company`, `list-projects`, `get-project`, `create-project` (required: `--name`, `--companyId`, `--contactPersonId`, `--insideSalesId`, `--businessDevelopmentId`, `--venue`, `--
|
|
66
|
+
| **Companies & projects** | `list-companies`, `get-company`, `create-company`, `update-company` (`--payload`), `delete-company`, `list-projects`, `get-project`, `create-project` (required: `--name`, `--companyId`, `--contactPersonId`, `--insideSalesId`, `--businessDevelopmentId`, `--venue`, `--asanaTaskId`, `--slackChannelId`, `--slackChannelUrl`, `--slackChannelName`, `--startDate` as ISO datetime for project/window start; optional `--pax`; optional `--endDate`; optional `--description`; optional `--requestQboAccountantNotification false` to skip QBO accountant emails), `update-project` (`--payload` with `dateRange.from` / `dateRange.to` for the project window; optional `requestQboAccountantNotification` in JSON), `delete-project`, `update-project-status` (`<id>` `<status>`: `PITCH` \| `WON` \| `COMPLETED` \| `LOST`; for `PITCH` → `WON` also pass `--projectManagerId` or `--projectManagerName`; when marking `WON` without an accepted/closed budget or proof, pass `--wonOverrideReason`) |
|
|
63
67
|
| **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`; 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
|
|
68
|
+
| **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) |
|
|
65
69
|
| **Dashboard & users** | `list-users`, `get-user-performance`, `get-dashboard`, `get-monthly-metrics`, `get-system-overview`, `get-estimate-performance`, `get-financial-overview` |
|
|
66
70
|
| **Errors** | `get-recent-errors`, `get-error-metrics` |
|
|
67
71
|
| **Historical / benchmarks** | `get-approved-budgets`, `get-budget-category-benchmarks`, `get-item-pricing-history`, `get-supplier-pricing-history` |
|
package/dist/commands.js
CHANGED
|
@@ -48,13 +48,13 @@ export async function getBudget(id) {
|
|
|
48
48
|
]);
|
|
49
49
|
out({ budget, items });
|
|
50
50
|
}
|
|
51
|
-
export async function updateBudgetStatus(budgetId, status) {
|
|
52
|
-
if (status === "ESTIMATE_ACCEPTED") {
|
|
53
|
-
throw new Error("Use mark-budget-won <budgetId> <filePath> so proof upload is included before setting ESTIMATE_ACCEPTED.");
|
|
54
|
-
}
|
|
51
|
+
export async function updateBudgetStatus(budgetId, status, opts) {
|
|
55
52
|
const result = await api.budget.updateBudgetStatus.mutate({
|
|
56
53
|
id: budgetId,
|
|
57
54
|
status,
|
|
55
|
+
...(opts?.markProjectWon !== undefined && {
|
|
56
|
+
markProjectWon: opts.markProjectWon,
|
|
57
|
+
}),
|
|
58
58
|
});
|
|
59
59
|
out(result);
|
|
60
60
|
}
|
|
@@ -110,7 +110,7 @@ const runBudgetPreflight = async (budgetId, preflight) => {
|
|
|
110
110
|
insideSalesId: project.insideSalesId ?? "",
|
|
111
111
|
businessDevelopmentId: project.businessDevelopmentId ?? "",
|
|
112
112
|
venue: project.venue ?? "",
|
|
113
|
-
pax: String(project.pax),
|
|
113
|
+
pax: String(project.pax ?? ""),
|
|
114
114
|
...(project.slackChannelId &&
|
|
115
115
|
project.slackChannelUrl &&
|
|
116
116
|
project.slackChannelName
|
|
@@ -217,8 +217,14 @@ export async function listApprovals(opts) {
|
|
|
217
217
|
export async function listSuppliers(opts) {
|
|
218
218
|
const result = await api.supplier.getSuppliers.query({
|
|
219
219
|
page: opts.page ?? 1,
|
|
220
|
-
perPage: opts.perPage ??
|
|
220
|
+
perPage: opts.perPage ?? 10,
|
|
221
|
+
sort: opts.sort ?? [{ id: "createdAt", desc: true }],
|
|
221
222
|
name: opts.name,
|
|
223
|
+
createdBy: opts.createdBy,
|
|
224
|
+
gstRegistered: opts.gstRegistered ?? null,
|
|
225
|
+
status: opts.status,
|
|
226
|
+
supplierTags: opts.supplierTags,
|
|
227
|
+
active: opts.active ?? true,
|
|
222
228
|
});
|
|
223
229
|
out(result);
|
|
224
230
|
}
|
|
@@ -338,7 +344,7 @@ export async function uploadBudgetAttachmentFromPath(budgetId, filePath) {
|
|
|
338
344
|
});
|
|
339
345
|
out(confirmed);
|
|
340
346
|
}
|
|
341
|
-
export async function uploadBudgetWinProofFromPath(budgetId, filePath) {
|
|
347
|
+
export async function uploadBudgetWinProofFromPath(budgetId, filePath, opts) {
|
|
342
348
|
const buf = await readFile(filePath);
|
|
343
349
|
const fileName = basename(filePath);
|
|
344
350
|
const size = buf.byteLength;
|
|
@@ -364,11 +370,14 @@ export async function uploadBudgetWinProofFromPath(budgetId, filePath) {
|
|
|
364
370
|
const result = await api.budget.updateBudgetStatus.mutate({
|
|
365
371
|
id: budgetId,
|
|
366
372
|
status: "ESTIMATE_ACCEPTED",
|
|
373
|
+
...(opts?.markProjectWon !== undefined && {
|
|
374
|
+
markProjectWon: opts.markProjectWon,
|
|
375
|
+
}),
|
|
367
376
|
});
|
|
368
377
|
out(result);
|
|
369
378
|
}
|
|
370
|
-
export async function markBudgetWonWithProof(budgetId, filePath) {
|
|
371
|
-
await uploadBudgetWinProofFromPath(budgetId, filePath);
|
|
379
|
+
export async function markBudgetWonWithProof(budgetId, filePath, opts) {
|
|
380
|
+
await uploadBudgetWinProofFromPath(budgetId, filePath, opts);
|
|
372
381
|
}
|
|
373
382
|
export async function getBillDetails(id) {
|
|
374
383
|
const result = await api.bill.getById.query({ id });
|
|
@@ -508,6 +517,10 @@ export async function deleteSuppliersByIds(ids) {
|
|
|
508
517
|
const result = await api.supplier.deleteSuppliers.mutate({ ids });
|
|
509
518
|
out(result);
|
|
510
519
|
}
|
|
520
|
+
export async function reactivateSuppliersByIds(ids) {
|
|
521
|
+
const result = await api.supplier.reactivateSuppliers.mutate({ ids });
|
|
522
|
+
out(result);
|
|
523
|
+
}
|
|
511
524
|
export async function createSupplierCertification(name) {
|
|
512
525
|
const result = await api.supplier.createCertification.mutate({ name });
|
|
513
526
|
out(result);
|
|
@@ -742,7 +755,62 @@ export async function getSupplierDetails(supplierId) {
|
|
|
742
755
|
const supplier = await api.supplier.getSupplierById.query({
|
|
743
756
|
id: supplierId,
|
|
744
757
|
});
|
|
745
|
-
|
|
758
|
+
const respondedStatuses = new Set([
|
|
759
|
+
ExtendedApprovalStatus.APPROVED,
|
|
760
|
+
ExtendedApprovalStatus.REJECTED,
|
|
761
|
+
]);
|
|
762
|
+
const latestRespondedApproval = [
|
|
763
|
+
...supplier.approvals.filter((approval) => respondedStatuses.has(approval.status)),
|
|
764
|
+
].sort((a, b) => new Date(b.respondedAt ?? b.updatedAt).getTime() -
|
|
765
|
+
new Date(a.respondedAt ?? a.updatedAt).getTime() ||
|
|
766
|
+
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0];
|
|
767
|
+
const pendingApprovals = supplier.approvals.filter((approval) => approval.status === ExtendedApprovalStatus.PENDING_APPROVAL);
|
|
768
|
+
const supersededApprovals = supplier.approvals.filter((approval) => approval.status === ExtendedApprovalStatus.SUPERSEDED);
|
|
769
|
+
const respondedByApproval = latestRespondedApproval ?? null;
|
|
770
|
+
const responseUser = respondedByApproval
|
|
771
|
+
? (respondedByApproval.responder ?? respondedByApproval.approver)
|
|
772
|
+
: null;
|
|
773
|
+
out({
|
|
774
|
+
...supplier,
|
|
775
|
+
supplierApprovalSummary: {
|
|
776
|
+
pendingApprovalCount: pendingApprovals.length,
|
|
777
|
+
pendingApprovers: pendingApprovals.map((approval) => ({
|
|
778
|
+
approvalId: approval.id,
|
|
779
|
+
approverId: approval.approverId,
|
|
780
|
+
name: approval.approver.name,
|
|
781
|
+
email: approval.approver.email,
|
|
782
|
+
createdAt: approval.createdAt,
|
|
783
|
+
updatedAt: approval.updatedAt,
|
|
784
|
+
})),
|
|
785
|
+
supersededApprovalCount: supersededApprovals.length,
|
|
786
|
+
supersededApprovers: supersededApprovals.map((approval) => ({
|
|
787
|
+
approvalId: approval.id,
|
|
788
|
+
approverId: approval.approverId,
|
|
789
|
+
name: approval.approver.name,
|
|
790
|
+
email: approval.approver.email,
|
|
791
|
+
respondedAt: approval.respondedAt,
|
|
792
|
+
responder: approval.responder
|
|
793
|
+
? {
|
|
794
|
+
id: approval.responder.id,
|
|
795
|
+
name: approval.responder.name,
|
|
796
|
+
email: approval.responder.email,
|
|
797
|
+
}
|
|
798
|
+
: null,
|
|
799
|
+
})),
|
|
800
|
+
respondedBy: respondedByApproval && responseUser
|
|
801
|
+
? {
|
|
802
|
+
approvalId: respondedByApproval.id,
|
|
803
|
+
status: respondedByApproval.status,
|
|
804
|
+
approverId: respondedByApproval.approverId,
|
|
805
|
+
responderId: respondedByApproval.responderId,
|
|
806
|
+
name: responseUser.name,
|
|
807
|
+
email: responseUser.email,
|
|
808
|
+
respondedAt: respondedByApproval.respondedAt,
|
|
809
|
+
updatedAt: respondedByApproval.updatedAt,
|
|
810
|
+
}
|
|
811
|
+
: null,
|
|
812
|
+
},
|
|
813
|
+
});
|
|
746
814
|
}
|
|
747
815
|
export async function approveSupplier(supplierId) {
|
|
748
816
|
const notifications = await api.budget.getNotifications.query();
|
|
@@ -910,6 +978,9 @@ export async function updateProjectStatus(id, status, opts) {
|
|
|
910
978
|
...(opts?.projectManagerName && {
|
|
911
979
|
projectManagerName: opts.projectManagerName,
|
|
912
980
|
}),
|
|
981
|
+
...(opts?.wonOverrideReason && {
|
|
982
|
+
wonOverrideReason: opts.wonOverrideReason,
|
|
983
|
+
}),
|
|
913
984
|
});
|
|
914
985
|
out(result);
|
|
915
986
|
}
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import "./load-env.js";
|
|
3
3
|
import { requireApiKey } from "./api-client.js";
|
|
4
4
|
import { consumeCliQuietFlags, logCliAction, sanitizeFlagsForTrace, setCliQuiet, shouldLogCliActions, } from "./cli-trace.js";
|
|
5
|
-
import { addBudgetItems, approveBill, approveBudget, approveSupplier, createBillApproval, createBillFromPayload, createBudgetApproval, createBudgetCategory, createBudgetFromPayload, createCompany, createContactPersonFromPayload, createEstimate, createItemCategory, createItemFromPayload, createProject, createSupplierCertification, createSupplierFromPayload, createSupplierPaymentMethod, createSupplierRoleOption, createSupplierTagOption, deleteBillById, deleteBudgetById, deleteBudgetCategory, deleteCompanyById, deleteItemById, deleteItemCategoriesByIds, deleteProjectById, deleteSuppliersByIds, getApprovedBudgets, getBillAttachments, getBillDetails, getBudget, getBudgetCategories, getBudgetCategoryBenchmarks, getBudgetDetails, getBudgetItemsOnly, getBudgetVersions, getCompany, getDashboard, getErrorMetrics, getEstimatePerformance, getFinancialOverview, getItem, getItemPricingHistory, getMonthlyMetrics, getProject, getRecentErrors, getSupplierAnalytics, getSupplierDetails, getSupplierPricingHistory, getSystemOverview, getUserPerformance, listApprovals, listBills, listBudgets, listCompanies, listContacts, listItemCategories, listItems, listProjects, listSuppliers, listUsers, markBudgetWonWithProof, patchBillInvoiceNumber, patchBillPayment, rejectBill, rejectBudget, rejectSupplier, removeBudgetItem, reorderBudgetItemsCli, updateBillFromPayload, updateBillStatus, updateBudgetCategory, updateBudgetCommissionFromPayload, updateBudgetDiscountFromPayload, updateBudgetFromPayload, updateBudgetItem, updateBudgetItemSupplierCli, updateBudgetStatus, updateCompanyFromPayload, updateContactPersonFromPayload, updateItemCategory, updateItemFromPayload, updateProjectFromPayload, updateProjectStatus, updateSupplierFromPayload, uploadBillAttachmentFromPath, uploadBudgetAttachmentFromPath, } from "./commands.js";
|
|
5
|
+
import { addBudgetItems, approveBill, approveBudget, approveSupplier, createBillApproval, createBillFromPayload, createBudgetApproval, createBudgetCategory, createBudgetFromPayload, createCompany, createContactPersonFromPayload, createEstimate, createItemCategory, createItemFromPayload, createProject, createSupplierCertification, createSupplierFromPayload, createSupplierPaymentMethod, createSupplierRoleOption, createSupplierTagOption, deleteBillById, deleteBudgetById, deleteBudgetCategory, deleteCompanyById, deleteItemById, deleteItemCategoriesByIds, deleteProjectById, deleteSuppliersByIds, getApprovedBudgets, getBillAttachments, getBillDetails, getBudget, getBudgetCategories, getBudgetCategoryBenchmarks, getBudgetDetails, getBudgetItemsOnly, getBudgetVersions, getCompany, getDashboard, getErrorMetrics, getEstimatePerformance, getFinancialOverview, getItem, getItemPricingHistory, getMonthlyMetrics, getProject, getRecentErrors, getSupplierAnalytics, getSupplierDetails, getSupplierPricingHistory, getSystemOverview, getUserPerformance, listApprovals, listBills, listBudgets, listCompanies, listContacts, listItemCategories, listItems, listProjects, listSuppliers, listUsers, markBudgetWonWithProof, patchBillInvoiceNumber, patchBillPayment, reactivateSuppliersByIds, rejectBill, rejectBudget, rejectSupplier, removeBudgetItem, reorderBudgetItemsCli, updateBillFromPayload, updateBillStatus, updateBudgetCategory, updateBudgetCommissionFromPayload, updateBudgetDiscountFromPayload, updateBudgetFromPayload, updateBudgetItem, updateBudgetItemSupplierCli, updateBudgetStatus, updateCompanyFromPayload, updateContactPersonFromPayload, updateItemCategory, updateItemFromPayload, updateProjectFromPayload, updateProjectStatus, updateSupplierFromPayload, uploadBillAttachmentFromPath, uploadBudgetAttachmentFromPath, } from "./commands.js";
|
|
6
6
|
import { getFlag, parseArgs } from "./parse-args.js";
|
|
7
|
-
import { billStatusesForUpdateHelp, budgetStatusesForHelp, isBudgetStatusUpdate, parseApprovalTypeFlag, parseBillStatusForUpdate, parseCommaSeparatedBillStatuses, parseCommaSeparatedBudgetStatuses, parseCommaSeparatedIds, parseOptionalBillListSortBy, parseOptionalBillListSortDir, parseOptionalDashboardRole, parseOptionalDeals, parseOptionalErrorSeverity, parseOptionalErrorStatus, parseOptionalExtendedProjectStatus, parseOptionalFinancialRole, parseOptionalSupplierAnalyticsTimeFrame, parseOptionalTimeFrame, parseProjectStatusForUpdate, projectStatusesForHelp, } from "./parse-cli-enums.js";
|
|
7
|
+
import { billStatusesForUpdateHelp, budgetStatusesForHelp, isBudgetStatusUpdate, parseApprovalTypeFlag, parseBillStatusForUpdate, parseCommaSeparatedBillStatuses, parseCommaSeparatedBudgetStatuses, parseCommaSeparatedIds, parseCommaSeparatedSupplierStatuses, parseOptionalBillListSortBy, parseOptionalBillListSortDir, parseOptionalDashboardRole, parseOptionalDeals, parseOptionalErrorSeverity, parseOptionalErrorStatus, parseOptionalExtendedProjectStatus, parseOptionalFinancialRole, parseOptionalSupplierAnalyticsTimeFrame, parseOptionalTimeFrame, parseProjectStatusForUpdate, projectStatusesForHelp, } from "./parse-cli-enums.js";
|
|
8
8
|
import { parseJsonFlag, parseOptionalNumber as parseOptNum, } from "./parse-json-flag.js";
|
|
9
9
|
function parsePositiveIntFlag(value, flagName) {
|
|
10
10
|
if (value === undefined)
|
|
@@ -45,6 +45,45 @@ function parseListBillsFlags(flags) {
|
|
|
45
45
|
pageSize: parsePositiveIntFlag(getFlag(flags, "pageSize"), "--pageSize"),
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
+
const SUPPLIER_SORT_FIELDS = [
|
|
49
|
+
"createdAt",
|
|
50
|
+
"updatedAt",
|
|
51
|
+
"name",
|
|
52
|
+
"email",
|
|
53
|
+
"phone",
|
|
54
|
+
"gstRegistered",
|
|
55
|
+
"status",
|
|
56
|
+
"active",
|
|
57
|
+
];
|
|
58
|
+
function parseOptionalSupplierSortBy(raw) {
|
|
59
|
+
if (raw === undefined || raw === "")
|
|
60
|
+
return undefined;
|
|
61
|
+
if (!SUPPLIER_SORT_FIELDS.includes(raw)) {
|
|
62
|
+
throw new Error(`Invalid --sortBy "${raw}". Use one of: ${SUPPLIER_SORT_FIELDS.join(", ")}.`);
|
|
63
|
+
}
|
|
64
|
+
return raw;
|
|
65
|
+
}
|
|
66
|
+
function parseListSuppliersFlags(flags) {
|
|
67
|
+
const createdByRaw = getFlag(flags, "createdBy");
|
|
68
|
+
const statusRaw = getFlag(flags, "status");
|
|
69
|
+
const supplierTagsRaw = getFlag(flags, "supplierTags");
|
|
70
|
+
const sortBy = parseOptionalSupplierSortBy(getFlag(flags, "sortBy"));
|
|
71
|
+
const sortDirRaw = getFlag(flags, "sortDir");
|
|
72
|
+
const sortDir = parseOptionalBillListSortDir(sortDirRaw !== undefined ? String(sortDirRaw) : undefined);
|
|
73
|
+
return {
|
|
74
|
+
name: getFlag(flags, "name"),
|
|
75
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
76
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
77
|
+
sort: sortBy !== undefined || sortDir !== undefined
|
|
78
|
+
? [{ id: sortBy ?? "createdAt", desc: (sortDir ?? "desc") === "desc" }]
|
|
79
|
+
: undefined,
|
|
80
|
+
createdBy: parseCommaSeparatedIds(createdByRaw !== undefined ? String(createdByRaw) : undefined),
|
|
81
|
+
gstRegistered: parseOptionalBoolFlag(flags, "gstRegistered"),
|
|
82
|
+
status: parseCommaSeparatedSupplierStatuses(statusRaw !== undefined ? String(statusRaw) : undefined),
|
|
83
|
+
supplierTags: parseCommaSeparatedIds(supplierTagsRaw !== undefined ? String(supplierTagsRaw) : undefined),
|
|
84
|
+
active: parseOptionalBoolFlag(flags, "active"),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
48
87
|
function parseDashboardQueryFlags(flags) {
|
|
49
88
|
const roleRaw = getFlag(flags, "role");
|
|
50
89
|
const timeFrameRaw = getFlag(flags, "timeFrame");
|
|
@@ -59,7 +98,7 @@ function parseDashboardQueryFlags(flags) {
|
|
|
59
98
|
}
|
|
60
99
|
function printHelp() {
|
|
61
100
|
const help = `
|
|
62
|
-
bb — Budget Builder CLI (parity with MCP tools)
|
|
101
|
+
bb — Budget Builder CLI for AI agents (parity with MCP tools)
|
|
63
102
|
|
|
64
103
|
Usage: bb <command> [options] [args]
|
|
65
104
|
|
|
@@ -74,9 +113,9 @@ Budgets
|
|
|
74
113
|
get-budget-details <id> Full detail (margins, suppliers, etc.)
|
|
75
114
|
get-budget-categories
|
|
76
115
|
get-budget-versions <budgetId>
|
|
77
|
-
update-budget-status <id> <status>
|
|
116
|
+
update-budget-status <id> <status> [--markProjectWon true|false]
|
|
78
117
|
status: ${budgetStatusesForHelp.join(", ")}
|
|
79
|
-
mark-budget-won <budgetId> <filePath> Upload signed quote/PO proof and set status to ESTIMATE_ACCEPTED
|
|
118
|
+
mark-budget-won <budgetId> <filePath> [--markProjectWon true|false] Upload signed quote/PO proof and set status to ESTIMATE_ACCEPTED
|
|
80
119
|
create-budget --payload '<json>' (budget.createBudget; Asana deal card is on the project)
|
|
81
120
|
update-budget --payload '<json>' (budget.updateBudget; must include id)
|
|
82
121
|
delete-budget <budgetId>
|
|
@@ -97,8 +136,9 @@ Budgets
|
|
|
97
136
|
Bills
|
|
98
137
|
list-bills [--projectId] [--budgetId] [--status CSV] [--search <text>] [--isClaimable true|false] [--createdByIds <csv>] [--sortBy createdAt|amount|status] [--sortDir asc|desc] [--page] [--pageSize]
|
|
99
138
|
list-claims same flags as list-bills; only reimbursable claims (ignores --isClaimable)
|
|
139
|
+
isClaimable differentiates the shared bill/claim records: false = bill, true = claim.
|
|
100
140
|
create-bill-approval <billId> (also sends approval request emails)
|
|
101
|
-
create-bill --payload '<json>' (bill.create; use upload-bill-attachment after create to add files)
|
|
141
|
+
create-bill --payload '<json>' (bill.create; payload.isClaimable false = bill, true = claim; use upload-bill-attachment after create to add files)
|
|
102
142
|
update-bill --payload '<json>' (bill.update; must include id)
|
|
103
143
|
delete-bill <billId>
|
|
104
144
|
update-bill-status <id> <status> [--rejectionReason] [--paymentTrackingUrl] [--paymentReference]
|
|
@@ -127,11 +167,12 @@ Companies & projects
|
|
|
127
167
|
delete-company <id>
|
|
128
168
|
list-projects [--companyId] [--name] [--status] [--page] [--perPage]
|
|
129
169
|
get-project <id>
|
|
130
|
-
create-project --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --
|
|
170
|
+
create-project --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --asanaTaskId --slackChannelId --slackChannelUrl --slackChannelName --startDate <ISO> [--pax] [--endDate <ISO>] [--description] [--requestQboAccountantNotification false]
|
|
131
171
|
update-project --payload '<json>' (project.updateProject; optional requestQboAccountantNotification; default notify like web)
|
|
132
172
|
delete-project <id>
|
|
133
|
-
update-project-status <id> <status> [--projectManagerId <id>] [--projectManagerName <name>]
|
|
173
|
+
update-project-status <id> <status> [--projectManagerId <id>] [--projectManagerName <name>] [--wonOverrideReason <text>]
|
|
134
174
|
Required when moving PITCH → WON: supply --projectManagerId or --projectManagerName.
|
|
175
|
+
Required when moving to WON without accepted/closed budget or win proof: --wonOverrideReason (10+ chars).
|
|
135
176
|
status: ${projectStatusesForHelp.join(", ")}
|
|
136
177
|
|
|
137
178
|
Contacts
|
|
@@ -140,10 +181,12 @@ Contacts
|
|
|
140
181
|
update-contact-person --payload '<json>' (contactPerson.updateContactPerson; must include id)
|
|
141
182
|
|
|
142
183
|
Suppliers & items
|
|
143
|
-
list-suppliers [--name] [--page] [--perPage]
|
|
184
|
+
list-suppliers [--name] [--page] [--perPage] [--sortBy createdAt|updatedAt|name|email|phone|gstRegistered|status|active] [--sortDir asc|desc] [--createdBy <csv>] [--gstRegistered true|false] [--status CSV] [--supplierTags <csv>] [--active true|false]
|
|
185
|
+
Defaults: --active true, --perPage 10, --sortBy createdAt, --sortDir desc (same as the web list); pass --active false for archived suppliers.
|
|
144
186
|
create-supplier --payload '<json>' (supplier.createSupplier; auto-requests approval + emails when status is PENDING_APPROVAL)
|
|
145
187
|
update-supplier --payload '<json>' (supplier.updateSupplier; must include id; auto-requests approval + emails when status is PENDING_APPROVAL)
|
|
146
|
-
delete-suppliers --ids <csv> (supplier.deleteSuppliers; admin)
|
|
188
|
+
delete-suppliers --ids <csv> (supplier.deleteSuppliers; admin; archives/deactivates supplier items)
|
|
189
|
+
reactivate-suppliers --ids <csv> (supplier.reactivateSuppliers; admin; reactivates supplier items)
|
|
147
190
|
create-certification --name (supplier.createCertification)
|
|
148
191
|
create-payment-method --name (supplier.createPaymentMethod)
|
|
149
192
|
create-supplier-role --name (supplier.createSupplierRole)
|
|
@@ -256,7 +299,9 @@ async function main() {
|
|
|
256
299
|
if (!budgetId || !filePath) {
|
|
257
300
|
throw new Error("mark-budget-won requires <budgetId> <filePath>");
|
|
258
301
|
}
|
|
259
|
-
await markBudgetWonWithProof(budgetId, filePath
|
|
302
|
+
await markBudgetWonWithProof(budgetId, filePath, {
|
|
303
|
+
markProjectWon: parseOptionalBoolFlag(flags, "markProjectWon"),
|
|
304
|
+
});
|
|
260
305
|
break;
|
|
261
306
|
}
|
|
262
307
|
case "get-budget-items": {
|
|
@@ -292,7 +337,9 @@ async function main() {
|
|
|
292
337
|
if (!isBudgetStatusUpdate(status)) {
|
|
293
338
|
throw new Error(`Invalid status. Use one of: ${budgetStatusesForHelp.join(", ")}`);
|
|
294
339
|
}
|
|
295
|
-
await updateBudgetStatus(id, status
|
|
340
|
+
await updateBudgetStatus(id, status, {
|
|
341
|
+
markProjectWon: parseOptionalBoolFlag(flags, "markProjectWon"),
|
|
342
|
+
});
|
|
296
343
|
break;
|
|
297
344
|
}
|
|
298
345
|
case "create-budget-approval": {
|
|
@@ -750,13 +797,12 @@ async function main() {
|
|
|
750
797
|
!insideSalesId ||
|
|
751
798
|
!businessDevelopmentId ||
|
|
752
799
|
!venue ||
|
|
753
|
-
!pax ||
|
|
754
800
|
!asanaTaskId ||
|
|
755
801
|
!slackChannelId ||
|
|
756
802
|
!slackChannelUrl ||
|
|
757
803
|
!slackChannelName ||
|
|
758
804
|
!startDate) {
|
|
759
|
-
throw new Error("create-project requires --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --
|
|
805
|
+
throw new Error("create-project requires --name --companyId --contactPersonId --insideSalesId --businessDevelopmentId --venue --asanaTaskId --slackChannelId --slackChannelUrl --slackChannelName --startDate (ISO/datetime flags as appropriate); optional --pax and --endDate");
|
|
760
806
|
}
|
|
761
807
|
await createProject({
|
|
762
808
|
name,
|
|
@@ -800,9 +846,13 @@ async function main() {
|
|
|
800
846
|
}
|
|
801
847
|
const pmIdFlag = getFlag(flags, "projectManagerId");
|
|
802
848
|
const pmNameFlag = getFlag(flags, "projectManagerName");
|
|
849
|
+
const wonOverrideReasonFlag = getFlag(flags, "wonOverrideReason");
|
|
803
850
|
await updateProjectStatus(id, parseProjectStatusForUpdate(statusRaw), {
|
|
804
851
|
...(pmIdFlag && { projectManagerId: String(pmIdFlag) }),
|
|
805
852
|
...(pmNameFlag && { projectManagerName: String(pmNameFlag) }),
|
|
853
|
+
...(wonOverrideReasonFlag && {
|
|
854
|
+
wonOverrideReason: String(wonOverrideReasonFlag),
|
|
855
|
+
}),
|
|
806
856
|
});
|
|
807
857
|
break;
|
|
808
858
|
}
|
|
@@ -832,11 +882,7 @@ async function main() {
|
|
|
832
882
|
break;
|
|
833
883
|
}
|
|
834
884
|
case "list-suppliers": {
|
|
835
|
-
await listSuppliers(
|
|
836
|
-
name: getFlag(flags, "name"),
|
|
837
|
-
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
838
|
-
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
839
|
-
});
|
|
885
|
+
await listSuppliers(parseListSuppliersFlags(flags));
|
|
840
886
|
break;
|
|
841
887
|
}
|
|
842
888
|
case "create-supplier": {
|
|
@@ -869,6 +915,18 @@ async function main() {
|
|
|
869
915
|
await deleteSuppliersByIds(ids);
|
|
870
916
|
break;
|
|
871
917
|
}
|
|
918
|
+
case "reactivate-suppliers": {
|
|
919
|
+
const idsRaw = getFlag(flags, "ids");
|
|
920
|
+
if (idsRaw === undefined) {
|
|
921
|
+
throw new Error("reactivate-suppliers requires --ids <csv>");
|
|
922
|
+
}
|
|
923
|
+
const ids = parseCommaSeparatedIds(String(idsRaw));
|
|
924
|
+
if (!ids?.length) {
|
|
925
|
+
throw new Error("reactivate-suppliers requires non-empty --ids");
|
|
926
|
+
}
|
|
927
|
+
await reactivateSuppliersByIds(ids);
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
872
930
|
case "create-certification": {
|
|
873
931
|
const name = getFlag(flags, "name");
|
|
874
932
|
if (!name)
|
package/dist/parse-cli-enums.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BudgetRole, Deals, ExtendedBillStatus, ExtendedErrorSeverity, ExtendedErrorStatus, ExtendedProjectStatus, TimeFrame, } from "./filter-enums.js";
|
|
1
|
+
import { BudgetRole, Deals, ExtendedApprovalStatus, ExtendedBillStatus, ExtendedErrorSeverity, ExtendedErrorStatus, ExtendedProjectStatus, TimeFrame, } from "./filter-enums.js";
|
|
2
2
|
import { BillStatus, BudgetStatus, ProjectStatus } from "./prisma-enums.js";
|
|
3
3
|
const BUDGET_STATUS_VALUES = new Set(Object.values(BudgetStatus));
|
|
4
4
|
const BILL_STATUS_VALUES = new Set(Object.values(BillStatus));
|
|
@@ -20,6 +20,7 @@ const SUPPLIER_ANALYTICS_TF = new Set([
|
|
|
20
20
|
"LAST_3_MONTHS",
|
|
21
21
|
"CUSTOM",
|
|
22
22
|
]);
|
|
23
|
+
const SUPPLIER_APPROVAL_STATUS_VALUES = new Set(Object.values(ExtendedApprovalStatus));
|
|
23
24
|
const EXT_ERROR_SEVERITY_VALUES = new Set(Object.values(ExtendedErrorSeverity));
|
|
24
25
|
const EXT_ERROR_STATUS_VALUES = new Set(Object.values(ExtendedErrorStatus));
|
|
25
26
|
export function isBudgetStatusListToken(s) {
|
|
@@ -144,6 +145,24 @@ export function parseCommaSeparatedBudgetStatuses(raw) {
|
|
|
144
145
|
}
|
|
145
146
|
return out;
|
|
146
147
|
}
|
|
148
|
+
export function parseCommaSeparatedSupplierStatuses(raw) {
|
|
149
|
+
if (raw === undefined)
|
|
150
|
+
return undefined;
|
|
151
|
+
const parts = raw
|
|
152
|
+
.split(",")
|
|
153
|
+
.map((x) => x.trim())
|
|
154
|
+
.filter(Boolean);
|
|
155
|
+
if (parts.length === 0)
|
|
156
|
+
return undefined;
|
|
157
|
+
const out = [];
|
|
158
|
+
for (const p of parts) {
|
|
159
|
+
if (!SUPPLIER_APPROVAL_STATUS_VALUES.has(p)) {
|
|
160
|
+
throw new Error(`Invalid supplier approval status "${p}". Use one of: ${[...SUPPLIER_APPROVAL_STATUS_VALUES].join(", ")}.`);
|
|
161
|
+
}
|
|
162
|
+
out.push(p);
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
147
166
|
export function parseBillStatusFlag(raw) {
|
|
148
167
|
if (raw === undefined)
|
|
149
168
|
return undefined;
|
|
@@ -214,7 +214,7 @@ export function parseUpdateProjectPayload(raw) {
|
|
|
214
214
|
insideSalesId: requiredString(o.insideSalesId, "update-project payload.insideSalesId"),
|
|
215
215
|
businessDevelopmentId: requiredString(o.businessDevelopmentId, "update-project payload.businessDevelopmentId"),
|
|
216
216
|
venue: requiredString(o.venue, "update-project payload.venue"),
|
|
217
|
-
pax:
|
|
217
|
+
pax: optionalString(o.pax),
|
|
218
218
|
dateRange: {
|
|
219
219
|
from: toDate(range.from, "dateRange.from"),
|
|
220
220
|
to: range.to === undefined || range.to === null
|
package/dist/prisma-enums.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-labs-sg/bb",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Budget Builder CLI —
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Budget Builder CLI for AI agents — manage budgets, bills, and claims; bill records use isClaimable=false for bills and isClaimable=true for claims.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|