@go-labs-sg/bb 1.0.3 → 1.1.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 +2 -1
- package/dist/commands.js +399 -14
- package/dist/index.js +516 -71
- package/dist/parse-args.js +11 -2
- package/dist/parse-cli-enums.js +216 -0
- package/dist/parse-json-flag.js +20 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,139 +1,584 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./load-env
|
|
3
|
-
import { requireApiKey } from "./api-client
|
|
4
|
-
import { approveBill, getBudget, listApprovals, listBills, listBudgets, listSuppliers, updateBudgetStatus, } from "./commands.js";
|
|
2
|
+
import "./load-env";
|
|
3
|
+
import { requireApiKey } from "./api-client";
|
|
4
|
+
import { addBudgetItems, approveBill, approveBudget, approveSupplier, createBillApproval, createBudgetApproval, createCompany, createProject, getApprovedBudgets, getBillAttachments, getBillDetails, getBudget, getBudgetCategories, getBudgetCategoryBenchmarks, getBudgetDetails, getBudgetItemsOnly, getBudgetVersions, getCompany, getDashboard, getErrorMetrics, getFinancialOverview, getItem, getItemPricingHistory, getProject, getRecentErrors, getSupplierAnalytics, getSupplierDetails, getSupplierPricingHistory, getUserPerformance, listApprovals, listBills, listBudgets, listCompanies, listContacts, listItemCategories, listItems, listProjects, listSuppliers, listUsers, patchBillInvoiceNumber, patchBillPayment, rejectBill, rejectBudget, rejectSupplier, removeBudgetItem, updateBillStatus, updateBudgetItem, updateBudgetStatus, updateProjectStatus, } from "./commands.js";
|
|
5
5
|
import { getFlag, parseArgs } from "./parse-args.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
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 { parseJsonFlag, parseOptionalNumber as parseOptNum, } from "./parse-json-flag.js";
|
|
8
|
+
function parsePositiveIntFlag(value, flagName) {
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
12
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
13
|
+
throw new Error(`${flagName} must be a positive integer.`);
|
|
14
|
+
}
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
function parseOptionalBoolFlag(flags, key) {
|
|
18
|
+
const v = flags[key];
|
|
19
|
+
if (v === undefined || v === true)
|
|
20
|
+
return undefined;
|
|
21
|
+
const s = String(v).toLowerCase();
|
|
22
|
+
if (s === "true" || s === "1")
|
|
23
|
+
return true;
|
|
24
|
+
if (s === "false" || s === "0")
|
|
25
|
+
return false;
|
|
26
|
+
throw new Error(`--${key} must be true or false.`);
|
|
27
|
+
}
|
|
28
|
+
function parseListBillsFlags(flags) {
|
|
29
|
+
const statusRaw = getFlag(flags, "status");
|
|
30
|
+
const createdByIdsRaw = getFlag(flags, "createdByIds");
|
|
31
|
+
const searchRaw = getFlag(flags, "search");
|
|
32
|
+
const sortByRaw = getFlag(flags, "sortBy");
|
|
33
|
+
const sortDirRaw = getFlag(flags, "sortDir");
|
|
34
|
+
return {
|
|
35
|
+
projectId: getFlag(flags, "projectId"),
|
|
36
|
+
budgetId: getFlag(flags, "budgetId"),
|
|
37
|
+
statuses: parseCommaSeparatedBillStatuses(statusRaw !== undefined ? String(statusRaw) : undefined),
|
|
38
|
+
search: searchRaw !== undefined ? String(searchRaw) : undefined,
|
|
39
|
+
isClaimable: parseOptionalBoolFlag(flags, "isClaimable"),
|
|
40
|
+
createdByIds: parseCommaSeparatedIds(createdByIdsRaw !== undefined ? String(createdByIdsRaw) : undefined),
|
|
41
|
+
sortBy: parseOptionalBillListSortBy(sortByRaw !== undefined ? String(sortByRaw) : undefined),
|
|
42
|
+
sortDir: parseOptionalBillListSortDir(sortDirRaw !== undefined ? String(sortDirRaw) : undefined),
|
|
43
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
44
|
+
pageSize: parsePositiveIntFlag(getFlag(flags, "pageSize"), "--pageSize"),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
17
47
|
function printHelp() {
|
|
18
48
|
const help = `
|
|
19
|
-
bb — Budget Builder CLI (
|
|
49
|
+
bb — Budget Builder CLI (parity with MCP tools)
|
|
20
50
|
|
|
21
51
|
Usage: bb <command> [options] [args]
|
|
22
52
|
|
|
23
53
|
Auth: Set BB_API_KEY in the environment (same API key as MCP / Goracle).
|
|
24
54
|
|
|
25
|
-
|
|
26
|
-
list-budgets
|
|
27
|
-
|
|
55
|
+
Budgets
|
|
56
|
+
list-budgets [--projectId] [--name] [--status CSV] [--createdBy] [--dateFrom] [--dateTo] [--sortBy] [--sortDir] [--page] [--perPage]
|
|
57
|
+
get-budget <id> Budget + line items
|
|
58
|
+
get-budget-items <id> Line items only
|
|
59
|
+
get-budget-details <id> Full detail (margins, suppliers, etc.)
|
|
60
|
+
get-budget-categories
|
|
61
|
+
get-budget-versions <budgetId>
|
|
62
|
+
update-budget-status <id> <status>
|
|
63
|
+
status: ${budgetStatusesForHelp.join(", ")}
|
|
64
|
+
create-budget-approval <budgetId>
|
|
65
|
+
add-budget-items --budgetId --items '[{"itemId":"…","quantity":1,"markup":30},…]'
|
|
66
|
+
update-budget-item --id <budgetItemId> [--description] [--note] [--quantity] [--markup] [--cost] [--unitPrice] [--isFreeOfCharge true|false] [--gstInclusive true|false]
|
|
67
|
+
remove-budget-item <budgetItemId>
|
|
68
|
+
|
|
69
|
+
Bills
|
|
70
|
+
list-bills [--projectId] [--budgetId] [--status CSV] [--search <text>] [--isClaimable true|false] [--createdByIds <csv>] [--sortBy createdAt|amount|status] [--sortDir asc|desc] [--page] [--pageSize]
|
|
71
|
+
list-claims same flags as list-bills; only reimbursable claims (ignores --isClaimable)
|
|
72
|
+
create-bill-approval <billId>
|
|
73
|
+
update-bill-status <id> <status> [--rejectionReason] [--paymentTrackingUrl] [--paymentReference]
|
|
74
|
+
status (${billStatusesForUpdateHelp.join(", ")}) — not PENDING_APPROVAL; use create-bill-approval
|
|
75
|
+
patch-bill-payment <billId> [--paymentTrackingUrl] [--paymentReference] [--quickbooksBillId]
|
|
76
|
+
patch-bill-invoice-number <billId> <invoiceNumber>
|
|
77
|
+
get-bill-attachments <billId>
|
|
78
|
+
get-bill-details <billId>
|
|
28
79
|
|
|
29
|
-
|
|
80
|
+
Approvals
|
|
81
|
+
list-approvals | get-pending-approvals [--type budget|supplier|bill|all]
|
|
82
|
+
approve-bill <billId>
|
|
83
|
+
reject-bill <billId> --reason <text>
|
|
84
|
+
approve-budget <budgetId>
|
|
85
|
+
reject-budget <budgetId> --reason <text>
|
|
86
|
+
approve-supplier <supplierId>
|
|
87
|
+
reject-supplier <supplierId> --reason <text>
|
|
30
88
|
|
|
31
|
-
|
|
32
|
-
|
|
89
|
+
Companies & projects
|
|
90
|
+
list-companies [--name] [--createdBy] [--page] [--perPage]
|
|
91
|
+
get-company <id>
|
|
92
|
+
create-company --name --website <url>
|
|
93
|
+
list-projects [--companyId] [--name] [--status] [--page] [--perPage]
|
|
94
|
+
get-project <id>
|
|
95
|
+
create-project --name --companyId --contactPersonId --startDate <ISO> [--endDate] [--description]
|
|
96
|
+
update-project-status <id> <status>
|
|
97
|
+
status: ${projectStatusesForHelp.join(", ")}
|
|
33
98
|
|
|
34
|
-
|
|
35
|
-
|
|
99
|
+
Contacts
|
|
100
|
+
list-contacts <companyId>
|
|
36
101
|
|
|
37
|
-
|
|
102
|
+
Suppliers & items
|
|
103
|
+
list-suppliers [--name] [--page] [--perPage]
|
|
104
|
+
get-supplier-details <supplierId>
|
|
105
|
+
get-supplier-analytics [--name] [--page] [--perPage] [--timeFrame ALL|LAST_YEAR|…]
|
|
106
|
+
list-items [--name] [--page] [--perPage]
|
|
107
|
+
get-item <id>
|
|
108
|
+
list-item-categories [--page] [--perPage]
|
|
38
109
|
|
|
39
|
-
|
|
40
|
-
|
|
110
|
+
Dashboard & users
|
|
111
|
+
list-users
|
|
112
|
+
get-user-performance [--userId]
|
|
113
|
+
get-dashboard [--userId] [--role BD|CREATOR|INSIDE_SALES|ALL] [--deals ALL|SUCCESSFUL|LOST] [--timeFrame] [--startDate] [--endDate]
|
|
114
|
+
get-financial-overview [same optional flags as dashboard; role uses BudgetRole enum]
|
|
41
115
|
|
|
42
|
-
|
|
43
|
-
|
|
116
|
+
Errors
|
|
117
|
+
get-recent-errors [--page] [--perPage] [--severity] [--status]
|
|
118
|
+
get-error-metrics
|
|
44
119
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
120
|
+
Historical / benchmarks
|
|
121
|
+
get-approved-budgets [--projectId] [--limit] [--minRevenue] [--maxRevenue]
|
|
122
|
+
get-budget-category-benchmarks <categoryId> [--limit]
|
|
123
|
+
get-item-pricing-history [--itemName] [--categoryId] [--supplierId] [--limit]
|
|
124
|
+
get-supplier-pricing-history <supplierId> [--itemCategoryId] [--limit]
|
|
125
|
+
|
|
126
|
+
Output: JSON to stdout. Errors to stderr; exit 1 on failure.
|
|
127
|
+
|
|
128
|
+
MCP parity: every MCP tool maps to one command above. You may use MCP-style
|
|
129
|
+
snake_case (e.g. bb list_bills) — it is normalized to kebab-case.
|
|
130
|
+
|
|
131
|
+
Note: MCP also exposes a Prisma schema resource; the CLI has no equivalent.
|
|
132
|
+
|
|
133
|
+
Not covered vs MCP get_budget: "get-budget" also fetches line items in one call.
|
|
134
|
+
`.trim();
|
|
135
|
+
console.log(help);
|
|
136
|
+
}
|
|
137
|
+
/** MCP registers tools with snake_case; CLI commands are kebab-case. */
|
|
138
|
+
function normalizeCliCommand(raw) {
|
|
139
|
+
return raw.replace(/_/g, "-");
|
|
48
140
|
}
|
|
49
141
|
async function main() {
|
|
50
142
|
const { command, positional, flags } = parseArgs(process.argv);
|
|
51
|
-
|
|
143
|
+
const cmd = normalizeCliCommand(command ?? "");
|
|
144
|
+
if (!cmd || cmd === "help" || flags.help === true) {
|
|
52
145
|
printHelp();
|
|
53
146
|
process.exit(0);
|
|
54
147
|
}
|
|
55
148
|
requireApiKey();
|
|
56
149
|
try {
|
|
57
|
-
switch (
|
|
150
|
+
switch (cmd) {
|
|
58
151
|
case "list-budgets": {
|
|
59
152
|
const statusStr = getFlag(flags, "status");
|
|
153
|
+
const statuses = parseCommaSeparatedBudgetStatuses(statusStr !== undefined ? String(statusStr) : undefined);
|
|
60
154
|
await listBudgets({
|
|
61
155
|
projectId: getFlag(flags, "projectId"),
|
|
62
156
|
name: getFlag(flags, "name"),
|
|
63
|
-
statuses
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
157
|
+
statuses,
|
|
158
|
+
createdBy: getFlag(flags, "createdBy"),
|
|
159
|
+
dateFrom: getFlag(flags, "dateFrom"),
|
|
160
|
+
dateTo: getFlag(flags, "dateTo"),
|
|
161
|
+
sortBy: getFlag(flags, "sortBy"),
|
|
162
|
+
sortDir: getFlag(flags, "sortDir"),
|
|
163
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
164
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
70
165
|
});
|
|
71
166
|
break;
|
|
72
167
|
}
|
|
73
168
|
case "get-budget": {
|
|
74
169
|
const id = positional[0];
|
|
75
|
-
if (!id)
|
|
76
|
-
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
170
|
+
if (!id)
|
|
171
|
+
throw new Error("get-budget requires <id>");
|
|
79
172
|
await getBudget(id);
|
|
80
173
|
break;
|
|
81
174
|
}
|
|
175
|
+
case "get-budget-items": {
|
|
176
|
+
const id = positional[0];
|
|
177
|
+
if (!id)
|
|
178
|
+
throw new Error("get-budget-items requires <budgetId>");
|
|
179
|
+
await getBudgetItemsOnly(id);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case "get-budget-details": {
|
|
183
|
+
const id = positional[0];
|
|
184
|
+
if (!id)
|
|
185
|
+
throw new Error("get-budget-details requires <budgetId>");
|
|
186
|
+
await getBudgetDetails(id);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case "get-budget-categories": {
|
|
190
|
+
await getBudgetCategories();
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case "get-budget-versions": {
|
|
194
|
+
const id = positional[0];
|
|
195
|
+
if (!id)
|
|
196
|
+
throw new Error("get-budget-versions requires <budgetId>");
|
|
197
|
+
await getBudgetVersions(id);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
82
200
|
case "update-budget-status": {
|
|
83
201
|
const [id, status] = positional;
|
|
84
202
|
if (!id || !status) {
|
|
85
|
-
|
|
86
|
-
error: "update-budget-status requires <id> <status>",
|
|
87
|
-
}));
|
|
88
|
-
process.exit(1);
|
|
203
|
+
throw new Error("update-budget-status requires <id> <status>");
|
|
89
204
|
}
|
|
90
|
-
if (!
|
|
91
|
-
|
|
92
|
-
error: `Invalid status. Use one of: ${BUDGET_STATUSES.join(", ")}`,
|
|
93
|
-
}));
|
|
94
|
-
process.exit(1);
|
|
205
|
+
if (!isBudgetStatusUpdate(status)) {
|
|
206
|
+
throw new Error(`Invalid status. Use one of: ${budgetStatusesForHelp.join(", ")}`);
|
|
95
207
|
}
|
|
96
208
|
await updateBudgetStatus(id, status);
|
|
97
209
|
break;
|
|
98
210
|
}
|
|
211
|
+
case "create-budget-approval": {
|
|
212
|
+
const id = positional[0];
|
|
213
|
+
if (!id)
|
|
214
|
+
throw new Error("create-budget-approval requires <budgetId>");
|
|
215
|
+
await createBudgetApproval(id);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case "add-budget-items": {
|
|
219
|
+
const budgetId = getFlag(flags, "budgetId");
|
|
220
|
+
if (!budgetId)
|
|
221
|
+
throw new Error("add-budget-items requires --budgetId");
|
|
222
|
+
const items = parseJsonFlag(String(getFlag(flags, "items")), "--items");
|
|
223
|
+
await addBudgetItems({ budgetId: String(budgetId), items });
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case "update-budget-item": {
|
|
227
|
+
const id = getFlag(flags, "id");
|
|
228
|
+
if (!id)
|
|
229
|
+
throw new Error("update-budget-item requires --id");
|
|
230
|
+
await updateBudgetItem({
|
|
231
|
+
id: String(id),
|
|
232
|
+
description: getFlag(flags, "description"),
|
|
233
|
+
note: getFlag(flags, "note"),
|
|
234
|
+
quantity: parseOptNum(getFlag(flags, "quantity"), "--quantity"),
|
|
235
|
+
markup: parseOptNum(getFlag(flags, "markup"), "--markup"),
|
|
236
|
+
cost: parseOptNum(getFlag(flags, "cost"), "--cost"),
|
|
237
|
+
unitPrice: parseOptNum(getFlag(flags, "unitPrice"), "--unitPrice"),
|
|
238
|
+
isFreeOfCharge: parseOptionalBoolFlag(flags, "isFreeOfCharge"),
|
|
239
|
+
gstInclusive: parseOptionalBoolFlag(flags, "gstInclusive"),
|
|
240
|
+
});
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case "remove-budget-item": {
|
|
244
|
+
const id = positional[0];
|
|
245
|
+
if (!id)
|
|
246
|
+
throw new Error("remove-budget-item requires <budgetItemId>");
|
|
247
|
+
await removeBudgetItem(id);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
99
250
|
case "list-bills": {
|
|
251
|
+
await listBills(parseListBillsFlags(flags));
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
case "list-claims": {
|
|
100
255
|
await listBills({
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
status: getFlag(flags, "status"),
|
|
104
|
-
page: getFlag(flags, "page", (s) => Number.parseInt(s, 10)),
|
|
105
|
-
pageSize: getFlag(flags, "pageSize", (s) => Number.parseInt(s, 10)),
|
|
256
|
+
...parseListBillsFlags(flags),
|
|
257
|
+
isClaimable: true,
|
|
106
258
|
});
|
|
107
259
|
break;
|
|
108
260
|
}
|
|
109
|
-
case "
|
|
261
|
+
case "create-bill-approval": {
|
|
110
262
|
const id = positional[0];
|
|
111
|
-
if (!id)
|
|
112
|
-
|
|
113
|
-
|
|
263
|
+
if (!id)
|
|
264
|
+
throw new Error("create-bill-approval requires <billId>");
|
|
265
|
+
await createBillApproval(id);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case "update-bill-status": {
|
|
269
|
+
const [id, statusRaw] = positional;
|
|
270
|
+
if (!id || !statusRaw) {
|
|
271
|
+
throw new Error("update-bill-status requires <billId> <status> [--rejectionReason] [--paymentTrackingUrl] [--paymentReference]");
|
|
114
272
|
}
|
|
115
|
-
|
|
273
|
+
const status = parseBillStatusForUpdate(statusRaw);
|
|
274
|
+
await updateBillStatus({
|
|
275
|
+
id,
|
|
276
|
+
status,
|
|
277
|
+
rejectionReason: getFlag(flags, "rejectionReason"),
|
|
278
|
+
paymentTrackingUrl: getFlag(flags, "paymentTrackingUrl"),
|
|
279
|
+
paymentReference: getFlag(flags, "paymentReference"),
|
|
280
|
+
});
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case "patch-bill-payment": {
|
|
284
|
+
const id = positional[0];
|
|
285
|
+
if (!id)
|
|
286
|
+
throw new Error("patch-bill-payment requires <billId>");
|
|
287
|
+
await patchBillPayment({
|
|
288
|
+
id,
|
|
289
|
+
paymentTrackingUrl: getFlag(flags, "paymentTrackingUrl"),
|
|
290
|
+
paymentReference: getFlag(flags, "paymentReference"),
|
|
291
|
+
quickbooksBillId: getFlag(flags, "quickbooksBillId"),
|
|
292
|
+
});
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
case "patch-bill-invoice-number": {
|
|
296
|
+
const [id, inv] = positional;
|
|
297
|
+
if (!id || !inv) {
|
|
298
|
+
throw new Error("patch-bill-invoice-number requires <billId> <invoiceNumber>");
|
|
299
|
+
}
|
|
300
|
+
await patchBillInvoiceNumber(id, inv);
|
|
116
301
|
break;
|
|
117
302
|
}
|
|
118
|
-
case "
|
|
303
|
+
case "get-bill-attachments": {
|
|
304
|
+
const id = positional[0];
|
|
305
|
+
if (!id)
|
|
306
|
+
throw new Error("get-bill-attachments requires <billId>");
|
|
307
|
+
await getBillAttachments(id);
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case "get-bill-details": {
|
|
311
|
+
const id = positional[0];
|
|
312
|
+
if (!id)
|
|
313
|
+
throw new Error("get-bill-details requires <billId>");
|
|
314
|
+
await getBillDetails(id);
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case "list-approvals":
|
|
318
|
+
case "get-pending-approvals": {
|
|
319
|
+
const typeRaw = getFlag(flags, "type");
|
|
119
320
|
await listApprovals({
|
|
120
|
-
type:
|
|
321
|
+
type: parseApprovalTypeFlag(typeRaw !== undefined ? String(typeRaw) : undefined),
|
|
121
322
|
});
|
|
122
323
|
break;
|
|
123
324
|
}
|
|
325
|
+
case "approve-bill": {
|
|
326
|
+
const id = positional[0];
|
|
327
|
+
if (!id)
|
|
328
|
+
throw new Error("approve-bill requires <billId>");
|
|
329
|
+
await approveBill(id);
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
case "reject-bill": {
|
|
333
|
+
const id = positional[0];
|
|
334
|
+
const reason = getFlag(flags, "reason");
|
|
335
|
+
if (!id || reason === undefined) {
|
|
336
|
+
throw new Error("reject-bill requires <billId> --reason <text>");
|
|
337
|
+
}
|
|
338
|
+
await rejectBill(id, reason);
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
case "approve-budget": {
|
|
342
|
+
const id = positional[0];
|
|
343
|
+
if (!id)
|
|
344
|
+
throw new Error("approve-budget requires <budgetId>");
|
|
345
|
+
await approveBudget(id);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
case "reject-budget": {
|
|
349
|
+
const id = positional[0];
|
|
350
|
+
const reason = getFlag(flags, "reason");
|
|
351
|
+
if (!id || reason === undefined) {
|
|
352
|
+
throw new Error("reject-budget requires <budgetId> --reason <text>");
|
|
353
|
+
}
|
|
354
|
+
await rejectBudget(id, reason);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
case "approve-supplier": {
|
|
358
|
+
const id = positional[0];
|
|
359
|
+
if (!id)
|
|
360
|
+
throw new Error("approve-supplier requires <supplierId>");
|
|
361
|
+
await approveSupplier(id);
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
case "reject-supplier": {
|
|
365
|
+
const id = positional[0];
|
|
366
|
+
const reason = getFlag(flags, "reason");
|
|
367
|
+
if (!id || reason === undefined) {
|
|
368
|
+
throw new Error("reject-supplier requires <supplierId> --reason <text>");
|
|
369
|
+
}
|
|
370
|
+
await rejectSupplier(id, reason);
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
case "get-supplier-details": {
|
|
374
|
+
const id = positional[0];
|
|
375
|
+
if (!id)
|
|
376
|
+
throw new Error("get-supplier-details requires <supplierId>");
|
|
377
|
+
await getSupplierDetails(id);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case "list-companies": {
|
|
381
|
+
await listCompanies({
|
|
382
|
+
name: getFlag(flags, "name"),
|
|
383
|
+
createdBy: getFlag(flags, "createdBy"),
|
|
384
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
385
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
386
|
+
});
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
case "get-company": {
|
|
390
|
+
const id = positional[0];
|
|
391
|
+
if (!id)
|
|
392
|
+
throw new Error("get-company requires <id>");
|
|
393
|
+
await getCompany(id);
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case "create-company": {
|
|
397
|
+
const name = getFlag(flags, "name");
|
|
398
|
+
const website = getFlag(flags, "website");
|
|
399
|
+
if (!name || !website) {
|
|
400
|
+
throw new Error("create-company requires --name and --website <url>");
|
|
401
|
+
}
|
|
402
|
+
await createCompany({ name, website });
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
case "list-projects": {
|
|
406
|
+
const statusRaw = getFlag(flags, "status");
|
|
407
|
+
await listProjects({
|
|
408
|
+
companyId: getFlag(flags, "companyId"),
|
|
409
|
+
name: getFlag(flags, "name"),
|
|
410
|
+
status: parseOptionalExtendedProjectStatus(statusRaw !== undefined ? String(statusRaw) : undefined),
|
|
411
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
412
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
413
|
+
});
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
case "get-project": {
|
|
417
|
+
const id = positional[0];
|
|
418
|
+
if (!id)
|
|
419
|
+
throw new Error("get-project requires <id>");
|
|
420
|
+
await getProject(id);
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
case "create-project": {
|
|
424
|
+
const name = getFlag(flags, "name");
|
|
425
|
+
const companyId = getFlag(flags, "companyId");
|
|
426
|
+
const contactPersonId = getFlag(flags, "contactPersonId");
|
|
427
|
+
const startDate = getFlag(flags, "startDate");
|
|
428
|
+
if (!name || !companyId || !contactPersonId || !startDate) {
|
|
429
|
+
throw new Error("create-project requires --name --companyId --contactPersonId --startDate (ISO)");
|
|
430
|
+
}
|
|
431
|
+
await createProject({
|
|
432
|
+
name,
|
|
433
|
+
companyId,
|
|
434
|
+
contactPersonId,
|
|
435
|
+
startDate,
|
|
436
|
+
endDate: getFlag(flags, "endDate"),
|
|
437
|
+
description: getFlag(flags, "description"),
|
|
438
|
+
});
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
case "update-project-status": {
|
|
442
|
+
const [id, statusRaw] = positional;
|
|
443
|
+
if (!id || !statusRaw) {
|
|
444
|
+
throw new Error("update-project-status requires <id> <status>");
|
|
445
|
+
}
|
|
446
|
+
await updateProjectStatus(id, parseProjectStatusForUpdate(statusRaw));
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
case "list-contacts": {
|
|
450
|
+
const id = positional[0];
|
|
451
|
+
if (!id)
|
|
452
|
+
throw new Error("list-contacts requires <companyId>");
|
|
453
|
+
await listContacts(id);
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
124
456
|
case "list-suppliers": {
|
|
125
457
|
await listSuppliers({
|
|
126
458
|
name: getFlag(flags, "name"),
|
|
127
|
-
page: getFlag(flags, "page"
|
|
128
|
-
perPage: getFlag(flags, "perPage"
|
|
459
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
460
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
461
|
+
});
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
case "get-supplier-analytics": {
|
|
465
|
+
const tfRaw = getFlag(flags, "timeFrame");
|
|
466
|
+
await getSupplierAnalytics({
|
|
467
|
+
name: getFlag(flags, "name"),
|
|
468
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
469
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
470
|
+
timeFrame: parseOptionalSupplierAnalyticsTimeFrame(tfRaw !== undefined ? String(tfRaw) : undefined),
|
|
471
|
+
});
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
case "list-items": {
|
|
475
|
+
await listItems({
|
|
476
|
+
name: getFlag(flags, "name"),
|
|
477
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
478
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
479
|
+
});
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
case "get-item": {
|
|
483
|
+
const id = positional[0];
|
|
484
|
+
if (!id)
|
|
485
|
+
throw new Error("get-item requires <id>");
|
|
486
|
+
await getItem(id);
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
case "list-item-categories": {
|
|
490
|
+
await listItemCategories({
|
|
491
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
492
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
493
|
+
});
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
case "list-users": {
|
|
497
|
+
await listUsers();
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
case "get-user-performance": {
|
|
501
|
+
await getUserPerformance(getFlag(flags, "userId"));
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
case "get-dashboard": {
|
|
505
|
+
await getDashboard({
|
|
506
|
+
userId: getFlag(flags, "userId"),
|
|
507
|
+
role: parseOptionalDashboardRole(getFlag(flags, "role")),
|
|
508
|
+
deals: parseOptionalDeals(getFlag(flags, "deals")),
|
|
509
|
+
timeFrame: parseOptionalTimeFrame(getFlag(flags, "timeFrame")),
|
|
510
|
+
startDate: getFlag(flags, "startDate"),
|
|
511
|
+
endDate: getFlag(flags, "endDate"),
|
|
512
|
+
});
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
case "get-financial-overview": {
|
|
516
|
+
await getFinancialOverview({
|
|
517
|
+
userId: getFlag(flags, "userId"),
|
|
518
|
+
role: parseOptionalFinancialRole(getFlag(flags, "role")),
|
|
519
|
+
deals: parseOptionalDeals(getFlag(flags, "deals")),
|
|
520
|
+
timeFrame: parseOptionalTimeFrame(getFlag(flags, "timeFrame")),
|
|
521
|
+
startDate: getFlag(flags, "startDate"),
|
|
522
|
+
endDate: getFlag(flags, "endDate"),
|
|
523
|
+
});
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
case "get-recent-errors": {
|
|
527
|
+
await getRecentErrors({
|
|
528
|
+
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
529
|
+
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
530
|
+
severity: parseOptionalErrorSeverity(getFlag(flags, "severity")),
|
|
531
|
+
status: parseOptionalErrorStatus(getFlag(flags, "status")),
|
|
532
|
+
});
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
case "get-error-metrics": {
|
|
536
|
+
await getErrorMetrics();
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
case "get-approved-budgets": {
|
|
540
|
+
await getApprovedBudgets({
|
|
541
|
+
projectId: getFlag(flags, "projectId"),
|
|
542
|
+
limit: parsePositiveIntFlag(getFlag(flags, "limit"), "--limit"),
|
|
543
|
+
minRevenue: parseOptNum(getFlag(flags, "minRevenue"), "--minRevenue"),
|
|
544
|
+
maxRevenue: parseOptNum(getFlag(flags, "maxRevenue"), "--maxRevenue"),
|
|
545
|
+
});
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case "get-budget-category-benchmarks": {
|
|
549
|
+
const id = positional[0];
|
|
550
|
+
if (!id) {
|
|
551
|
+
throw new Error("get-budget-category-benchmarks requires <categoryId> [--limit]");
|
|
552
|
+
}
|
|
553
|
+
await getBudgetCategoryBenchmarks({
|
|
554
|
+
categoryId: id,
|
|
555
|
+
limit: parsePositiveIntFlag(getFlag(flags, "limit"), "--limit"),
|
|
556
|
+
});
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
case "get-item-pricing-history": {
|
|
560
|
+
await getItemPricingHistory({
|
|
561
|
+
itemName: getFlag(flags, "itemName"),
|
|
562
|
+
categoryId: getFlag(flags, "categoryId"),
|
|
563
|
+
supplierId: getFlag(flags, "supplierId"),
|
|
564
|
+
limit: parsePositiveIntFlag(getFlag(flags, "limit"), "--limit"),
|
|
565
|
+
});
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case "get-supplier-pricing-history": {
|
|
569
|
+
const id = positional[0];
|
|
570
|
+
if (!id) {
|
|
571
|
+
throw new Error("get-supplier-pricing-history requires <supplierId> [--itemCategoryId] [--limit]");
|
|
572
|
+
}
|
|
573
|
+
await getSupplierPricingHistory({
|
|
574
|
+
supplierId: id,
|
|
575
|
+
itemCategoryId: getFlag(flags, "itemCategoryId"),
|
|
576
|
+
limit: parsePositiveIntFlag(getFlag(flags, "limit"), "--limit"),
|
|
129
577
|
});
|
|
130
578
|
break;
|
|
131
579
|
}
|
|
132
580
|
default: {
|
|
133
|
-
|
|
134
|
-
error: `Unknown command: ${command}. Run 'bb help'.`,
|
|
135
|
-
}));
|
|
136
|
-
process.exit(1);
|
|
581
|
+
throw new Error(`Unknown command: ${cmd}. Run 'bb help'.`);
|
|
137
582
|
}
|
|
138
583
|
}
|
|
139
584
|
}
|