@go-labs-sg/bb 1.0.4 → 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/dist/index.js CHANGED
@@ -1,19 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import "./load-env.js";
3
- import { requireApiKey } from "./api-client.js";
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
- const BUDGET_STATUSES = [
7
- "DRAFT",
8
- "PENDING_APPROVAL",
9
- "APPROVED",
10
- "REJECTED",
11
- "ESTIMATE_CREATED",
12
- "ESTIMATE_SENT",
13
- "ESTIMATE_ACCEPTED",
14
- "ESTIMATE_REJECTED",
15
- "ESTIMATE_CLOSED",
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";
17
8
  function parsePositiveIntFlag(value, flagName) {
18
9
  if (value === undefined)
19
10
  return undefined;
@@ -23,59 +14,152 @@ function parsePositiveIntFlag(value, flagName) {
23
14
  }
24
15
  return parsed;
25
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
+ }
26
47
  function printHelp() {
27
48
  const help = `
28
- bb — Budget Builder CLI (for AI agents / shell)
49
+ bb — Budget Builder CLI (parity with MCP tools)
29
50
 
30
51
  Usage: bb <command> [options] [args]
31
52
 
32
53
  Auth: Set BB_API_KEY in the environment (same API key as MCP / Goracle).
33
54
 
34
- Commands:
35
- list-budgets List budgets (optional filters)
36
- --projectId, --name, --status (comma-separated), --page, --perPage
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>
79
+
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>
88
+
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(", ")}
37
98
 
38
- get-budget <id> Get budget details + line items
99
+ Contacts
100
+ list-contacts <companyId>
39
101
 
40
- update-budget-status <id> <status> Set budget status (e.g. APPROVED, REJECTED)
41
- status: ${BUDGET_STATUSES.join(", ")}
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]
42
109
 
43
- list-bills List bills (optional filters)
44
- --projectId, --budgetId, --status, --page, --pageSize
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]
45
115
 
46
- approve-bill <id> Approve a pending bill
116
+ Errors
117
+ get-recent-errors [--page] [--perPage] [--severity] [--status]
118
+ get-error-metrics
47
119
 
48
- list-approvals List pending approvals
49
- --type budget | supplier | bill | all
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]
50
125
 
51
- list-suppliers List suppliers
52
- --name, --page, --perPage
126
+ Output: JSON to stdout. Errors to stderr; exit 1 on failure.
53
127
 
54
- Output: JSON to stdout. Errors to stderr and exit code 1.
55
- `;
56
- console.log(help.trim());
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, "-");
57
140
  }
58
141
  async function main() {
59
142
  const { command, positional, flags } = parseArgs(process.argv);
60
- if (!command || command === "help" || flags.help === true) {
143
+ const cmd = normalizeCliCommand(command ?? "");
144
+ if (!cmd || cmd === "help" || flags.help === true) {
61
145
  printHelp();
62
146
  process.exit(0);
63
147
  }
64
148
  requireApiKey();
65
149
  try {
66
- switch (command) {
150
+ switch (cmd) {
67
151
  case "list-budgets": {
68
152
  const statusStr = getFlag(flags, "status");
69
- const statuses = statusStr !== undefined
70
- ? String(statusStr)
71
- .split(",")
72
- .map((s) => s.trim())
73
- .filter((s) => s.length > 0)
74
- : undefined;
153
+ const statuses = parseCommaSeparatedBudgetStatuses(statusStr !== undefined ? String(statusStr) : undefined);
75
154
  await listBudgets({
76
155
  projectId: getFlag(flags, "projectId"),
77
156
  name: getFlag(flags, "name"),
78
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"),
79
163
  page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
80
164
  perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
81
165
  });
@@ -83,55 +167,292 @@ async function main() {
83
167
  }
84
168
  case "get-budget": {
85
169
  const id = positional[0];
86
- if (!id) {
87
- console.error(JSON.stringify({ error: "get-budget requires <id>" }));
88
- process.exit(1);
89
- }
170
+ if (!id)
171
+ throw new Error("get-budget requires <id>");
90
172
  await getBudget(id);
91
173
  break;
92
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
+ }
93
200
  case "update-budget-status": {
94
201
  const [id, status] = positional;
95
202
  if (!id || !status) {
96
- console.error(JSON.stringify({
97
- error: "update-budget-status requires <id> <status>",
98
- }));
99
- process.exit(1);
203
+ throw new Error("update-budget-status requires <id> <status>");
100
204
  }
101
- if (!BUDGET_STATUSES.includes(status)) {
102
- console.error(JSON.stringify({
103
- error: `Invalid status. Use one of: ${BUDGET_STATUSES.join(", ")}`,
104
- }));
105
- process.exit(1);
205
+ if (!isBudgetStatusUpdate(status)) {
206
+ throw new Error(`Invalid status. Use one of: ${budgetStatusesForHelp.join(", ")}`);
106
207
  }
107
208
  await updateBudgetStatus(id, status);
108
209
  break;
109
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
+ }
110
250
  case "list-bills": {
251
+ await listBills(parseListBillsFlags(flags));
252
+ break;
253
+ }
254
+ case "list-claims": {
111
255
  await listBills({
112
- projectId: getFlag(flags, "projectId"),
113
- budgetId: getFlag(flags, "budgetId"),
114
- status: getFlag(flags, "status"),
115
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
116
- pageSize: parsePositiveIntFlag(getFlag(flags, "pageSize"), "--pageSize"),
256
+ ...parseListBillsFlags(flags),
257
+ isClaimable: true,
117
258
  });
118
259
  break;
119
260
  }
120
- case "approve-bill": {
261
+ case "create-bill-approval": {
121
262
  const id = positional[0];
122
- if (!id) {
123
- console.error(JSON.stringify({ error: "approve-bill requires <id>" }));
124
- process.exit(1);
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]");
125
272
  }
126
- await approveBill(id);
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
+ });
127
281
  break;
128
282
  }
129
- case "list-approvals": {
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);
301
+ break;
302
+ }
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");
130
320
  await listApprovals({
131
- type: getFlag(flags, "type"),
321
+ type: parseApprovalTypeFlag(typeRaw !== undefined ? String(typeRaw) : undefined),
132
322
  });
133
323
  break;
134
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
+ }
135
456
  case "list-suppliers": {
136
457
  await listSuppliers({
137
458
  name: getFlag(flags, "name"),
@@ -140,11 +461,124 @@ async function main() {
140
461
  });
141
462
  break;
142
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"),
577
+ });
578
+ break;
579
+ }
143
580
  default: {
144
- console.error(JSON.stringify({
145
- error: `Unknown command: ${command}. Run 'bb help'.`,
146
- }));
147
- process.exit(1);
581
+ throw new Error(`Unknown command: ${cmd}. Run 'bb help'.`);
148
582
  }
149
583
  }
150
584
  }
@@ -46,5 +46,7 @@ export function getFlag(flags, key, parse) {
46
46
  const v = flags[key];
47
47
  if (v === undefined || v === true)
48
48
  return undefined;
49
- return parse ? parse(v) : v;
49
+ if (parse)
50
+ return parse(v);
51
+ return v;
50
52
  }