@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/dist/index.js CHANGED
@@ -1,139 +1,584 @@
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";
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 (for AI agents / shell)
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
- Commands:
26
- list-budgets List budgets (optional filters)
27
- --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>
28
79
 
29
- get-budget <id> Get budget details + line items
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
- update-budget-status <id> <status> Set budget status (e.g. APPROVED, REJECTED)
32
- status: ${BUDGET_STATUSES.join(", ")}
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
- list-bills List bills (optional filters)
35
- --projectId, --budgetId, --status, --page, --pageSize
99
+ Contacts
100
+ list-contacts <companyId>
36
101
 
37
- approve-bill <id> Approve a pending bill
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
- list-approvals List pending approvals
40
- --type budget | supplier | bill | all
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
- list-suppliers List suppliers
43
- --name, --page, --perPage
116
+ Errors
117
+ get-recent-errors [--page] [--perPage] [--severity] [--status]
118
+ get-error-metrics
44
119
 
45
- Output: JSON to stdout. Errors to stderr and exit code 1.
46
- `;
47
- console.log(help.trim());
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
- if (!command || command === "help" || flags.help === true) {
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 (command) {
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: statusStr !== undefined
64
- ? String(statusStr)
65
- .split(",")
66
- .map((s) => s.trim())
67
- : undefined,
68
- page: getFlag(flags, "page", (s) => Number.parseInt(s, 10)),
69
- perPage: getFlag(flags, "perPage", (s) => Number.parseInt(s, 10)),
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
- console.error(JSON.stringify({ error: "get-budget requires <id>" }));
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
- console.error(JSON.stringify({
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 (!BUDGET_STATUSES.includes(status)) {
91
- console.error(JSON.stringify({
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
- projectId: getFlag(flags, "projectId"),
102
- budgetId: getFlag(flags, "budgetId"),
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 "approve-bill": {
261
+ case "create-bill-approval": {
110
262
  const id = positional[0];
111
- if (!id) {
112
- console.error(JSON.stringify({ error: "approve-bill requires <id>" }));
113
- 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]");
114
272
  }
115
- 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
+ });
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 "list-approvals": {
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: getFlag(flags, "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", (s) => Number.parseInt(s, 10)),
128
- perPage: getFlag(flags, "perPage", (s) => Number.parseInt(s, 10)),
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
- console.error(JSON.stringify({
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
  }