@go-labs-sg/bb 1.2.0 → 1.2.2
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 +6 -4
- package/dist/api-client.js +12 -1
- package/dist/cli-trace.js +51 -0
- package/dist/commands.js +76 -6
- package/dist/filter-enums.js +1 -1
- package/dist/index.js +109 -7
- package/dist/parse-args.js +3 -0
- package/dist/parse-cli-enums.js +2 -2
- package/dist/prisma-enums.js +55 -0
- package/package.json +5 -2
- package/dist/api-client.d.ts +0 -5
- package/dist/api-client.d.ts.map +0 -1
- package/dist/commands.d.ts +0 -28
- package/dist/commands.d.ts.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/load-env.d.ts +0 -2
- package/dist/load-env.d.ts.map +0 -1
- package/dist/parse-args.d.ts +0 -11
- package/dist/parse-args.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -56,8 +56,8 @@ Global options and flags use `--key=value` or `--key value` (see `bb help`).
|
|
|
56
56
|
|
|
57
57
|
| Area | Commands (non-exhaustive) |
|
|
58
58
|
| --- | --- |
|
|
59
|
-
| **Budgets** | `list-budgets
|
|
60
|
-
| **Bills** | `list-bills`, `list-claims`, `create-bill` (`--payload`), `update-bill` (`--payload`), `delete-bill`, `create-bill-approval`, `update-bill-status`, `patch-bill-payment`, `patch-bill-invoice-number`, `get-bill-attachments`, `get-bill-details` |
|
|
59
|
+
| **Budgets** | `list-budgets` (full payload by default; `--summary` or `--includeDetails false` for slim list), `get-budget`, `get-budget-items`, `get-budget-details`, `get-budget-categories`, `get-budget-versions`, `update-budget-status`, `create-budget` / `update-budget` (`--payload`), `delete-budget`, `create-budget-approval`, `add-budget-items`, `update-budget-item`, `remove-budget-item`, `reorder-budget-items`, `update-budget-item-supplier`, `create-budget-category`, `update-budget-category`, `delete-budget-category`, `update-budget-commission`, `update-budget-discount` (`--payload` where noted), `upload-budget-attachment` (`<budgetId>` + local file path; uses `attachment.requestBudgetAttachmentUpload` + PUT + `attachment.confirmBudgetAttachment`) |
|
|
60
|
+
| **Bills** | `list-bills`, `list-claims`, `create-bill` (`--payload`), `update-bill` (`--payload`), `delete-bill`, `create-bill-approval`, `update-bill-status`, `patch-bill-payment` (PAID bills: `--paymentTrackingUrl`, `--paymentReference`, `--quickbooksBillId`, `--paymentDate` ISO; clear with `--clearPaymentTrackingUrl` / `--clearPaymentReference` / `--clearQuickbooksBillId` / `--clearPaymentDate` `true`), `patch-bill-invoice-number`, `get-bill-attachments`, `upload-bill-attachment` (`<billId>` + local path), `get-bill-details` |
|
|
61
61
|
| **Approvals** | `list-approvals` / `get-pending-approvals`, `approve-bill`, `reject-bill`, `approve-budget`, `reject-budget`, `approve-supplier`, `reject-supplier` |
|
|
62
62
|
| **Companies & projects** | `list-companies`, `get-company`, `create-company`, `update-company` (`--payload`), `delete-company`, `list-projects`, `get-project`, `create-project` (optional `--requestQboAccountantNotification false` to skip QBO accountant emails), `update-project` (`--payload`, optional `requestQboAccountantNotification` in JSON), `delete-project`, `update-project-status` |
|
|
63
63
|
| **Contacts** | `list-contacts`, `create-contact-person` (`--payload`), `update-contact-person` (`--payload`) |
|
|
@@ -70,6 +70,7 @@ Global options and flags use `--key=value` or `--key value` (see `bb help`).
|
|
|
70
70
|
|
|
71
71
|
- **Stdout:** JSON (pretty-printed)
|
|
72
72
|
- **Stderr:** Error objects as JSON on failure
|
|
73
|
+
- **Action logs (default on):** Each run logs to **stderr**: (1) JSON lines for command start, then `ok` or `error` with `durationMs` (`--payload` and similar flags are redacted); (2) tRPC lines for every API procedure (path, timing). **Stdout** stays JSON-only for piping (e.g. `| jq`). Suppress with `--quiet`, `-q`, or `BB_CLI_QUIET=1`.
|
|
73
74
|
- **Exit code:** `0` on success, `1` on error
|
|
74
75
|
|
|
75
76
|
A `.env` file in the **current working directory** is loaded automatically (for `BB_API_KEY`, etc.).
|
|
@@ -93,8 +94,9 @@ bun run src/index.ts list-budgets
|
|
|
93
94
|
|
|
94
95
|
### Layout
|
|
95
96
|
|
|
96
|
-
- **`src/
|
|
97
|
+
- **`src/prisma-enums.ts`** — Prisma enum string values for runtime; mirror `packages/db/src/generated/prisma/enums.ts` after schema changes.
|
|
98
|
+
- **`src/filter-enums.ts`** — “extended” filter enums (ALL + Prisma enums). Keep in line with `packages/utils/src/filter-enums.ts` when those values change.
|
|
97
99
|
|
|
98
100
|
### Publish
|
|
99
101
|
|
|
100
|
-
`prepublishOnly` strips `workspace:` / `catalog:` entries from this package’s `package.json`, runs `tsc`, then `postpack` restores the file — so the npm tarball does not contain monorepo protocol dependencies. Types from `@bb/api` are compile-time only (`import type`).
|
|
102
|
+
`prepublishOnly` strips `workspace:` / `catalog:` entries from this package’s `package.json`, runs `tsc`, then `postpack` restores the file — so the npm tarball does not contain monorepo protocol dependencies. Types from `@bb/api` are compile-time only (`import type`). Prisma enum **values** used at runtime live in `src/prisma-enums.ts` (kept in sync with `packages/db` generated enums) so npm installs do not need `@bb/db`.
|
package/dist/api-client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
|
|
1
|
+
import { createTRPCProxyClient, httpBatchLink, loggerLink } from "@trpc/client";
|
|
2
2
|
import SuperJSON from "superjson";
|
|
3
|
+
import { shouldLogCliActions } from "./cli-trace.js";
|
|
3
4
|
const BASE_URL = "https://budget-builder.getout.events";
|
|
4
5
|
function getAuthHeader() {
|
|
5
6
|
const key = process.env.BB_API_KEY;
|
|
@@ -9,6 +10,16 @@ function getAuthHeader() {
|
|
|
9
10
|
}
|
|
10
11
|
export const api = createTRPCProxyClient({
|
|
11
12
|
links: [
|
|
13
|
+
loggerLink({
|
|
14
|
+
enabled: () => shouldLogCliActions(),
|
|
15
|
+
colorMode: "ansi",
|
|
16
|
+
withContext: false,
|
|
17
|
+
// tRPC defaults use console.log for requests; stderr keeps stdout JSON-safe for pipes.
|
|
18
|
+
console: {
|
|
19
|
+
log: (...args) => console.error(...args),
|
|
20
|
+
error: (...args) => console.error(...args),
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
12
23
|
httpBatchLink({
|
|
13
24
|
url: `${BASE_URL}/api/trpc`,
|
|
14
25
|
transformer: SuperJSON,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Action logging for the CLI (default on).
|
|
3
|
+
* - Lifecycle: `logCliAction` in index (command + timing).
|
|
4
|
+
* - Per API call: tRPC `loggerLink` in api-client when `shouldLogCliActions()`.
|
|
5
|
+
* Logs go to stderr so stdout stays JSON for piping. Use `--quiet` / `-q` / BB_CLI_QUIET=1 to silence.
|
|
6
|
+
*/
|
|
7
|
+
let cliQuietSession = false;
|
|
8
|
+
/** Call from main after `consumeCliQuietFlags`. */
|
|
9
|
+
export const setCliQuiet = (quiet) => {
|
|
10
|
+
cliQuietSession = quiet;
|
|
11
|
+
};
|
|
12
|
+
export const isCliQuiet = () => cliQuietSession || process.env.BB_CLI_QUIET === "1";
|
|
13
|
+
export const shouldLogCliActions = () => !isCliQuiet();
|
|
14
|
+
const SENSITIVE_FLAG = new Set([
|
|
15
|
+
"payload",
|
|
16
|
+
"apikey",
|
|
17
|
+
"bb_api_key",
|
|
18
|
+
"token",
|
|
19
|
+
"password",
|
|
20
|
+
"secret",
|
|
21
|
+
"authorization",
|
|
22
|
+
]);
|
|
23
|
+
export const sanitizeFlagsForTrace = (flags) => {
|
|
24
|
+
const out = {};
|
|
25
|
+
for (const [k, v] of Object.entries(flags)) {
|
|
26
|
+
const lower = k.toLowerCase();
|
|
27
|
+
if (SENSITIVE_FLAG.has(lower) ||
|
|
28
|
+
lower.includes("secret") ||
|
|
29
|
+
lower.includes("password")) {
|
|
30
|
+
out[k] =
|
|
31
|
+
typeof v === "string" ? `[redacted ${v.length} chars]` : "[redacted]";
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
out[k] = v;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
};
|
|
39
|
+
export const consumeCliQuietFlags = (flags) => {
|
|
40
|
+
const fromEnv = process.env.BB_CLI_QUIET === "1";
|
|
41
|
+
const fromFlags = flags.quiet === true || flags.q === true;
|
|
42
|
+
delete flags.quiet;
|
|
43
|
+
delete flags.q;
|
|
44
|
+
return fromEnv || fromFlags;
|
|
45
|
+
};
|
|
46
|
+
/** stderr so stdout remains JSON-only for pipes (same intent as “log what the CLI is doing”). */
|
|
47
|
+
export const logCliAction = (record) => {
|
|
48
|
+
if (!shouldLogCliActions())
|
|
49
|
+
return;
|
|
50
|
+
console.error("[bb]", JSON.stringify({ ...record, ts: new Date().toISOString() }));
|
|
51
|
+
};
|
package/dist/commands.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { contentType } from "mime-types";
|
|
1
4
|
import { api } from "./api-client.js";
|
|
2
5
|
import { BudgetRole, Deals, ExtendedApprovalStatus, ExtendedApprovalType, ExtendedBudgetStatus, TimeFrame, } from "./filter-enums.js";
|
|
3
6
|
import { billStatusesForApi, } from "./parse-cli-enums.js";
|
|
@@ -11,6 +14,11 @@ function isPendingRequest(n) {
|
|
|
11
14
|
const out = (data) => {
|
|
12
15
|
console.log(JSON.stringify(data, null, 2));
|
|
13
16
|
};
|
|
17
|
+
/** Extension-based MIME, like browser `File.type` when the OS provides it */
|
|
18
|
+
const contentTypeHeaderForFileName = (fileName) => {
|
|
19
|
+
const ct = contentType(fileName);
|
|
20
|
+
return typeof ct === "string" ? ct : "application/octet-stream";
|
|
21
|
+
};
|
|
14
22
|
export async function listBudgets(opts) {
|
|
15
23
|
const input = {
|
|
16
24
|
projectId: opts.projectId,
|
|
@@ -23,6 +31,7 @@ export async function listBudgets(opts) {
|
|
|
23
31
|
sortDir: opts.sortDir,
|
|
24
32
|
page: opts.page ?? 1,
|
|
25
33
|
perPage: opts.perPage ?? 20,
|
|
34
|
+
includeDetails: opts.includeDetails ?? true,
|
|
26
35
|
};
|
|
27
36
|
const result = await api.budget.getAllBudgets.query(input);
|
|
28
37
|
out(result);
|
|
@@ -141,12 +150,22 @@ export async function updateBillStatus(opts) {
|
|
|
141
150
|
out(result);
|
|
142
151
|
}
|
|
143
152
|
export async function patchBillPayment(opts) {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
paymentTrackingUrl
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
const input = { id: opts.id };
|
|
154
|
+
if (opts.paymentTrackingUrl !== undefined)
|
|
155
|
+
input.paymentTrackingUrl = opts.paymentTrackingUrl;
|
|
156
|
+
if (opts.paymentReference !== undefined)
|
|
157
|
+
input.paymentReference = opts.paymentReference;
|
|
158
|
+
if (opts.quickbooksBillId !== undefined)
|
|
159
|
+
input.quickbooksBillId = opts.quickbooksBillId;
|
|
160
|
+
if (opts.paymentDate !== undefined) {
|
|
161
|
+
input.paymentDate =
|
|
162
|
+
opts.paymentDate === null
|
|
163
|
+
? null
|
|
164
|
+
: typeof opts.paymentDate === "string"
|
|
165
|
+
? new Date(opts.paymentDate)
|
|
166
|
+
: opts.paymentDate;
|
|
167
|
+
}
|
|
168
|
+
const result = await api.bill.patchPayment.mutate(input);
|
|
150
169
|
out(result);
|
|
151
170
|
}
|
|
152
171
|
export async function patchBillInvoiceNumber(id, invoiceNumber) {
|
|
@@ -160,6 +179,56 @@ export async function getBillAttachments(id) {
|
|
|
160
179
|
const result = await api.bill.getAttachments.query({ id });
|
|
161
180
|
out(result);
|
|
162
181
|
}
|
|
182
|
+
export async function uploadBillAttachmentFromPath(billId, filePath) {
|
|
183
|
+
const buf = await readFile(filePath);
|
|
184
|
+
const fileName = basename(filePath);
|
|
185
|
+
const size = buf.byteLength;
|
|
186
|
+
const { uploadUrl, key } = await api.attachment.requestBillAttachmentUpload.mutate({
|
|
187
|
+
billId,
|
|
188
|
+
fileName,
|
|
189
|
+
size,
|
|
190
|
+
});
|
|
191
|
+
const res = await fetch(uploadUrl, {
|
|
192
|
+
method: "PUT",
|
|
193
|
+
body: buf,
|
|
194
|
+
headers: { "Content-Type": contentTypeHeaderForFileName(fileName) },
|
|
195
|
+
});
|
|
196
|
+
if (!res.ok) {
|
|
197
|
+
throw new Error(`S3 upload failed: HTTP ${res.status} ${(await res.text()).slice(0, 500)}`);
|
|
198
|
+
}
|
|
199
|
+
const confirmed = await api.attachment.confirmBillAttachment.mutate({
|
|
200
|
+
billId,
|
|
201
|
+
key,
|
|
202
|
+
name: fileName,
|
|
203
|
+
size,
|
|
204
|
+
});
|
|
205
|
+
out(confirmed);
|
|
206
|
+
}
|
|
207
|
+
export async function uploadBudgetAttachmentFromPath(budgetId, filePath) {
|
|
208
|
+
const buf = await readFile(filePath);
|
|
209
|
+
const fileName = basename(filePath);
|
|
210
|
+
const size = buf.byteLength;
|
|
211
|
+
const { uploadUrl, key } = await api.attachment.requestBudgetAttachmentUpload.mutate({
|
|
212
|
+
budgetId,
|
|
213
|
+
fileName,
|
|
214
|
+
size,
|
|
215
|
+
});
|
|
216
|
+
const res = await fetch(uploadUrl, {
|
|
217
|
+
method: "PUT",
|
|
218
|
+
body: buf,
|
|
219
|
+
headers: { "Content-Type": contentTypeHeaderForFileName(fileName) },
|
|
220
|
+
});
|
|
221
|
+
if (!res.ok) {
|
|
222
|
+
throw new Error(`S3 upload failed: HTTP ${res.status} ${(await res.text()).slice(0, 500)}`);
|
|
223
|
+
}
|
|
224
|
+
const confirmed = await api.attachment.confirmBudgetAttachment.mutate({
|
|
225
|
+
budgetId,
|
|
226
|
+
key,
|
|
227
|
+
name: fileName,
|
|
228
|
+
size,
|
|
229
|
+
});
|
|
230
|
+
out(confirmed);
|
|
231
|
+
}
|
|
163
232
|
export async function getBillDetails(id) {
|
|
164
233
|
const result = await api.bill.getById.query({ id });
|
|
165
234
|
out(result);
|
|
@@ -619,6 +688,7 @@ export async function getApprovedBudgets(opts) {
|
|
|
619
688
|
],
|
|
620
689
|
page: 1,
|
|
621
690
|
perPage,
|
|
691
|
+
includeDetails: false,
|
|
622
692
|
});
|
|
623
693
|
const items = (budgets ?? []).map((b) => {
|
|
624
694
|
const calc = b;
|
package/dist/filter-enums.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApprovalStatus, ApprovalType, BillStatus, BudgetStatus, ErrorSeverity, ErrorStatus, EstimationMode, ProjectStatus, } from "
|
|
1
|
+
import { ApprovalStatus, ApprovalType, BillStatus, BudgetStatus, ErrorSeverity, ErrorStatus, EstimationMode, ProjectStatus, } from "./prisma-enums.js";
|
|
2
2
|
export const ExtendedApprovalStatus = {
|
|
3
3
|
...ApprovalStatus,
|
|
4
4
|
ALL: "ALL",
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./load-env.js";
|
|
3
3
|
import { requireApiKey } from "./api-client.js";
|
|
4
|
-
import {
|
|
4
|
+
import { consumeCliQuietFlags, logCliAction, sanitizeFlagsForTrace, setCliQuiet, shouldLogCliActions, } from "./cli-trace.js";
|
|
5
|
+
import { addBudgetItems, approveBill, approveBudget, approveSupplier, createBillApproval, createBillFromPayload, createBudgetApproval, createBudgetCategory, createBudgetFromPayload, createCompany, createContactPersonFromPayload, 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, 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
6
|
import { getFlag, parseArgs } from "./parse-args.js";
|
|
6
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
8
|
import { parseJsonFlag, parseOptionalNumber as parseOptNum, } from "./parse-json-flag.js";
|
|
@@ -62,10 +63,12 @@ bb — Budget Builder CLI (parity with MCP tools)
|
|
|
62
63
|
|
|
63
64
|
Usage: bb <command> [options] [args]
|
|
64
65
|
|
|
66
|
+
Global: --quiet | -q | BB_CLI_QUIET=1 — hide action logs (default: log command + each tRPC call to stderr; stdout JSON unchanged).
|
|
67
|
+
|
|
65
68
|
Auth: Set BB_API_KEY in the environment (same API key as MCP / Goracle).
|
|
66
69
|
|
|
67
70
|
Budgets
|
|
68
|
-
list-budgets [--projectId] [--name] [--status CSV] [--createdBy] [--dateFrom] [--dateTo] [--sortBy] [--sortDir] [--page] [--perPage]
|
|
71
|
+
list-budgets [--projectId] [--name] [--status CSV] [--createdBy] [--dateFrom] [--dateTo] [--sortBy] [--sortDir] [--page] [--perPage] [--summary]
|
|
69
72
|
get-budget <id> Budget + line items
|
|
70
73
|
get-budget-items <id> Line items only
|
|
71
74
|
get-budget-details <id> Full detail (margins, suppliers, etc.)
|
|
@@ -87,19 +90,22 @@ Budgets
|
|
|
87
90
|
delete-budget-category <id>
|
|
88
91
|
update-budget-commission --payload '<json>' (budget.updateBudgetCommission)
|
|
89
92
|
update-budget-discount --payload '<json>' (budget.updateBudgetDiscount; type percentage|amount)
|
|
93
|
+
upload-budget-attachment <budgetId> <filePath> Presigned S3 upload + confirm
|
|
90
94
|
|
|
91
95
|
Bills
|
|
92
96
|
list-bills [--projectId] [--budgetId] [--status CSV] [--search <text>] [--isClaimable true|false] [--createdByIds <csv>] [--sortBy createdAt|amount|status] [--sortDir asc|desc] [--page] [--pageSize]
|
|
93
97
|
list-claims same flags as list-bills; only reimbursable claims (ignores --isClaimable)
|
|
94
98
|
create-bill-approval <billId>
|
|
95
|
-
create-bill --payload '<json>' (bill.create;
|
|
99
|
+
create-bill --payload '<json>' (bill.create; use upload-bill-attachment after create to add files)
|
|
96
100
|
update-bill --payload '<json>' (bill.update; must include id)
|
|
97
101
|
delete-bill <billId>
|
|
98
102
|
update-bill-status <id> <status> [--rejectionReason] [--paymentTrackingUrl] [--paymentReference]
|
|
99
103
|
status (${billStatusesForUpdateHelp.join(", ")}) — not PENDING_APPROVAL; use create-bill-approval
|
|
100
|
-
patch-bill-payment <billId> [--paymentTrackingUrl] [--paymentReference] [--quickbooksBillId]
|
|
104
|
+
patch-bill-payment <billId> [--paymentTrackingUrl] [--paymentReference] [--quickbooksBillId] [--paymentDate <ISO>]
|
|
105
|
+
Clear a field: --clearPaymentTrackingUrl true | --clearPaymentReference true | --clearQuickbooksBillId true | --clearPaymentDate true
|
|
101
106
|
patch-bill-invoice-number <billId> <invoiceNumber>
|
|
102
107
|
get-bill-attachments <billId>
|
|
108
|
+
upload-bill-attachment <billId> <filePath> Presigned S3 upload + confirm (same flow as web UI)
|
|
103
109
|
get-bill-details <billId>
|
|
104
110
|
|
|
105
111
|
Approvals
|
|
@@ -188,12 +194,23 @@ function normalizeCliCommand(raw) {
|
|
|
188
194
|
}
|
|
189
195
|
async function main() {
|
|
190
196
|
const { command, positional, flags } = parseArgs(process.argv);
|
|
197
|
+
setCliQuiet(consumeCliQuietFlags(flags));
|
|
191
198
|
const cmd = normalizeCliCommand(command ?? "");
|
|
192
199
|
if (!cmd || cmd === "help" || flags.help === true) {
|
|
193
200
|
printHelp();
|
|
194
201
|
process.exit(0);
|
|
195
202
|
}
|
|
196
203
|
requireApiKey();
|
|
204
|
+
const t0 = Date.now();
|
|
205
|
+
if (shouldLogCliActions()) {
|
|
206
|
+
logCliAction({
|
|
207
|
+
event: "cli.command",
|
|
208
|
+
phase: "start",
|
|
209
|
+
command: cmd,
|
|
210
|
+
positional,
|
|
211
|
+
flags: sanitizeFlagsForTrace(flags),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
197
214
|
try {
|
|
198
215
|
switch (cmd) {
|
|
199
216
|
case "list-budgets": {
|
|
@@ -210,6 +227,9 @@ async function main() {
|
|
|
210
227
|
sortDir: getFlag(flags, "sortDir"),
|
|
211
228
|
page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
|
|
212
229
|
perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
|
|
230
|
+
includeDetails: flags.summary === true
|
|
231
|
+
? false
|
|
232
|
+
: parseOptionalBoolFlag(flags, "includeDetails") ?? true,
|
|
213
233
|
});
|
|
214
234
|
break;
|
|
215
235
|
}
|
|
@@ -220,6 +240,14 @@ async function main() {
|
|
|
220
240
|
await getBudget(id);
|
|
221
241
|
break;
|
|
222
242
|
}
|
|
243
|
+
case "upload-budget-attachment": {
|
|
244
|
+
const [budgetId, filePath] = positional;
|
|
245
|
+
if (!budgetId || !filePath) {
|
|
246
|
+
throw new Error("upload-budget-attachment requires <budgetId> <filePath>");
|
|
247
|
+
}
|
|
248
|
+
await uploadBudgetAttachmentFromPath(budgetId, filePath);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
223
251
|
case "get-budget-items": {
|
|
224
252
|
const id = positional[0];
|
|
225
253
|
if (!id)
|
|
@@ -450,11 +478,60 @@ async function main() {
|
|
|
450
478
|
const id = positional[0];
|
|
451
479
|
if (!id)
|
|
452
480
|
throw new Error("patch-bill-payment requires <billId>");
|
|
481
|
+
const paymentTrackingUrl = (() => {
|
|
482
|
+
const clear = parseOptionalBoolFlag(flags, "clearPaymentTrackingUrl");
|
|
483
|
+
const raw = getFlag(flags, "paymentTrackingUrl");
|
|
484
|
+
if (clear === true && raw !== undefined) {
|
|
485
|
+
throw new Error("Use either --paymentTrackingUrl or --clearPaymentTrackingUrl true, not both");
|
|
486
|
+
}
|
|
487
|
+
if (clear === true)
|
|
488
|
+
return null;
|
|
489
|
+
if (raw !== undefined)
|
|
490
|
+
return String(raw);
|
|
491
|
+
return undefined;
|
|
492
|
+
})();
|
|
493
|
+
const paymentReference = (() => {
|
|
494
|
+
const clear = parseOptionalBoolFlag(flags, "clearPaymentReference");
|
|
495
|
+
const raw = getFlag(flags, "paymentReference");
|
|
496
|
+
if (clear === true && raw !== undefined) {
|
|
497
|
+
throw new Error("Use either --paymentReference or --clearPaymentReference true, not both");
|
|
498
|
+
}
|
|
499
|
+
if (clear === true)
|
|
500
|
+
return null;
|
|
501
|
+
if (raw !== undefined)
|
|
502
|
+
return String(raw);
|
|
503
|
+
return undefined;
|
|
504
|
+
})();
|
|
505
|
+
const quickbooksBillId = (() => {
|
|
506
|
+
const clear = parseOptionalBoolFlag(flags, "clearQuickbooksBillId");
|
|
507
|
+
const raw = getFlag(flags, "quickbooksBillId");
|
|
508
|
+
if (clear === true && raw !== undefined) {
|
|
509
|
+
throw new Error("Use either --quickbooksBillId or --clearQuickbooksBillId true, not both");
|
|
510
|
+
}
|
|
511
|
+
if (clear === true)
|
|
512
|
+
return null;
|
|
513
|
+
if (raw !== undefined)
|
|
514
|
+
return String(raw);
|
|
515
|
+
return undefined;
|
|
516
|
+
})();
|
|
517
|
+
const paymentDate = (() => {
|
|
518
|
+
const clear = parseOptionalBoolFlag(flags, "clearPaymentDate");
|
|
519
|
+
const raw = getFlag(flags, "paymentDate");
|
|
520
|
+
if (clear === true && raw !== undefined) {
|
|
521
|
+
throw new Error("Use either --paymentDate or --clearPaymentDate true, not both");
|
|
522
|
+
}
|
|
523
|
+
if (clear === true)
|
|
524
|
+
return null;
|
|
525
|
+
if (raw !== undefined)
|
|
526
|
+
return String(raw);
|
|
527
|
+
return undefined;
|
|
528
|
+
})();
|
|
453
529
|
await patchBillPayment({
|
|
454
530
|
id,
|
|
455
|
-
paymentTrackingUrl
|
|
456
|
-
paymentReference
|
|
457
|
-
quickbooksBillId
|
|
531
|
+
paymentTrackingUrl,
|
|
532
|
+
paymentReference,
|
|
533
|
+
quickbooksBillId,
|
|
534
|
+
paymentDate,
|
|
458
535
|
});
|
|
459
536
|
break;
|
|
460
537
|
}
|
|
@@ -473,6 +550,14 @@ async function main() {
|
|
|
473
550
|
await getBillAttachments(id);
|
|
474
551
|
break;
|
|
475
552
|
}
|
|
553
|
+
case "upload-bill-attachment": {
|
|
554
|
+
const [billId, filePath] = positional;
|
|
555
|
+
if (!billId || !filePath) {
|
|
556
|
+
throw new Error("upload-bill-attachment requires <billId> <filePath>");
|
|
557
|
+
}
|
|
558
|
+
await uploadBillAttachmentFromPath(billId, filePath);
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
476
561
|
case "get-bill-details": {
|
|
477
562
|
const id = positional[0];
|
|
478
563
|
if (!id)
|
|
@@ -914,9 +999,26 @@ async function main() {
|
|
|
914
999
|
throw new Error(`Unknown command: ${cmd}. Run 'bb help'.`);
|
|
915
1000
|
}
|
|
916
1001
|
}
|
|
1002
|
+
if (shouldLogCliActions()) {
|
|
1003
|
+
logCliAction({
|
|
1004
|
+
event: "cli.command",
|
|
1005
|
+
phase: "ok",
|
|
1006
|
+
command: cmd,
|
|
1007
|
+
durationMs: Date.now() - t0,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
917
1010
|
}
|
|
918
1011
|
catch (err) {
|
|
919
1012
|
const message = err instanceof Error ? err.message : String(err);
|
|
1013
|
+
if (shouldLogCliActions()) {
|
|
1014
|
+
logCliAction({
|
|
1015
|
+
event: "cli.command",
|
|
1016
|
+
phase: "error",
|
|
1017
|
+
command: cmd,
|
|
1018
|
+
durationMs: Date.now() - t0,
|
|
1019
|
+
error: message,
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
920
1022
|
console.error(JSON.stringify({ error: message }));
|
|
921
1023
|
process.exit(1);
|
|
922
1024
|
}
|
package/dist/parse-args.js
CHANGED
package/dist/parse-cli-enums.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BillStatus, BudgetStatus, ProjectStatus } from "@bb/db/enums";
|
|
2
1
|
import { BudgetRole, Deals, ExtendedBillStatus, ExtendedErrorSeverity, ExtendedErrorStatus, ExtendedProjectStatus, TimeFrame, } from "./filter-enums.js";
|
|
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));
|
|
5
5
|
const PROJECT_STATUS_VALUES = new Set(Object.values(ProjectStatus));
|
|
@@ -209,7 +209,7 @@ export function parseApprovalTypeFlag(raw) {
|
|
|
209
209
|
}
|
|
210
210
|
return u;
|
|
211
211
|
}
|
|
212
|
-
/** For help text / error messages (
|
|
212
|
+
/** For help text / error messages (values from `./prisma-enums.js`, synced with Prisma). */
|
|
213
213
|
export const budgetStatusesForHelp = Object.values(BudgetStatus);
|
|
214
214
|
export const billStatusesForHelp = Object.values(BillStatus);
|
|
215
215
|
export const billStatusesForUpdateHelp = Object.values(BillStatus).filter((s) => s !== BillStatus.PENDING_APPROVAL);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Prisma enum values the CLI needs. Mirrors
|
|
3
|
+
* `packages/db/src/generated/prisma/enums.ts` — keep in sync when the schema changes.
|
|
4
|
+
*/
|
|
5
|
+
export const EstimationMode = {
|
|
6
|
+
MICRO: "MICRO",
|
|
7
|
+
MANPOWER_SPECIFIED: "MANPOWER_SPECIFIED",
|
|
8
|
+
};
|
|
9
|
+
export const ProjectStatus = {
|
|
10
|
+
NOT_STARTED: "NOT_STARTED",
|
|
11
|
+
IN_PROGRESS: "IN_PROGRESS",
|
|
12
|
+
COMPLETED: "COMPLETED",
|
|
13
|
+
CANCELLED: "CANCELLED",
|
|
14
|
+
};
|
|
15
|
+
export const BudgetStatus = {
|
|
16
|
+
DRAFT: "DRAFT",
|
|
17
|
+
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
18
|
+
APPROVED: "APPROVED",
|
|
19
|
+
REJECTED: "REJECTED",
|
|
20
|
+
ESTIMATE_CREATED: "ESTIMATE_CREATED",
|
|
21
|
+
ESTIMATE_SENT: "ESTIMATE_SENT",
|
|
22
|
+
ESTIMATE_ACCEPTED: "ESTIMATE_ACCEPTED",
|
|
23
|
+
ESTIMATE_REJECTED: "ESTIMATE_REJECTED",
|
|
24
|
+
ESTIMATE_CLOSED: "ESTIMATE_CLOSED",
|
|
25
|
+
};
|
|
26
|
+
export const ApprovalStatus = {
|
|
27
|
+
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
28
|
+
APPROVED: "APPROVED",
|
|
29
|
+
REJECTED: "REJECTED",
|
|
30
|
+
};
|
|
31
|
+
export const ApprovalType = {
|
|
32
|
+
BUDGET: "BUDGET",
|
|
33
|
+
SUPPLIER: "SUPPLIER",
|
|
34
|
+
BILL: "BILL",
|
|
35
|
+
};
|
|
36
|
+
export const ErrorSeverity = {
|
|
37
|
+
LOW: "LOW",
|
|
38
|
+
MEDIUM: "MEDIUM",
|
|
39
|
+
HIGH: "HIGH",
|
|
40
|
+
CRITICAL: "CRITICAL",
|
|
41
|
+
};
|
|
42
|
+
export const ErrorStatus = {
|
|
43
|
+
UNRESOLVED: "UNRESOLVED",
|
|
44
|
+
INVESTIGATING: "INVESTIGATING",
|
|
45
|
+
RESOLVED: "RESOLVED",
|
|
46
|
+
IGNORED: "IGNORED",
|
|
47
|
+
};
|
|
48
|
+
export const BillStatus = {
|
|
49
|
+
DRAFT: "DRAFT",
|
|
50
|
+
PENDING_APPROVAL: "PENDING_APPROVAL",
|
|
51
|
+
CHECKED: "CHECKED",
|
|
52
|
+
APPROVED: "APPROVED",
|
|
53
|
+
PAID: "PAID",
|
|
54
|
+
REJECTED: "REJECTED",
|
|
55
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-labs-sg/bb",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Budget Builder CLI — list budgets, bills, approvals, suppliers; approve bills; change status. For AI agents (e.g. Chuck/OpenClaw).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,9 +23,12 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@trpc/client": "^11.16.0",
|
|
25
25
|
"dotenv": "^17.2.4",
|
|
26
|
+
"mime-types": "^3.0.2",
|
|
26
27
|
"superjson": "^2.2.6"
|
|
27
28
|
},
|
|
28
|
-
"devDependencies": {
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/mime-types": "^3.0.1"
|
|
31
|
+
},
|
|
29
32
|
"engines": {
|
|
30
33
|
"node": ">=18"
|
|
31
34
|
},
|
package/dist/api-client.d.ts
DELETED
package/dist/api-client.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAGN,KAAK,qBAAqB,EAC1B,MAAM,cAAc,CAAC;AAetB,eAAO,MAAM,GAAG,EAAE,qBAAqB,CAAC,SAAS,CAgB/C,CAAC;AAEH,wBAAgB,aAAa,IAAI,MAAM,CAOtC"}
|
package/dist/commands.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { BudgetStatus } from "@bb/db/enums";
|
|
2
|
-
import type { ExtendedBudgetStatus } from "@bb/utils/filter-enums";
|
|
3
|
-
export declare function listBudgets(opts: {
|
|
4
|
-
projectId?: string;
|
|
5
|
-
name?: string;
|
|
6
|
-
statuses?: ExtendedBudgetStatus[];
|
|
7
|
-
page?: number;
|
|
8
|
-
perPage?: number;
|
|
9
|
-
}): Promise<void>;
|
|
10
|
-
export declare function getBudget(id: string): Promise<void>;
|
|
11
|
-
export declare function updateBudgetStatus(budgetId: string, status: BudgetStatus): Promise<void>;
|
|
12
|
-
export declare function listBills(opts: {
|
|
13
|
-
projectId?: string;
|
|
14
|
-
budgetId?: string;
|
|
15
|
-
status?: string;
|
|
16
|
-
page?: number;
|
|
17
|
-
pageSize?: number;
|
|
18
|
-
}): Promise<void>;
|
|
19
|
-
export declare function approveBill(billId: string): Promise<void>;
|
|
20
|
-
export declare function listApprovals(opts: {
|
|
21
|
-
type?: string;
|
|
22
|
-
}): Promise<void>;
|
|
23
|
-
export declare function listSuppliers(opts: {
|
|
24
|
-
name?: string;
|
|
25
|
-
page?: number;
|
|
26
|
-
perPage?: number;
|
|
27
|
-
}): Promise<void>;
|
|
28
|
-
//# sourceMappingURL=commands.d.ts.map
|
package/dist/commands.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAWnE,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMzD;AAED,wBAAsB,kBAAkB,CACvC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAehB;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB/D;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA6C1E;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB"}
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC"}
|
package/dist/load-env.d.ts
DELETED
package/dist/load-env.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"load-env.d.ts","sourceRoot":"","sources":["../src/load-env.ts"],"names":[],"mappings":""}
|
package/dist/parse-args.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal argv parser for bb CLI.
|
|
3
|
-
* Supports: command [positional...] [--key=value] [--key value]
|
|
4
|
-
*/
|
|
5
|
-
export declare function parseArgs(argv: string[]): {
|
|
6
|
-
command: string;
|
|
7
|
-
positional: string[];
|
|
8
|
-
flags: Record<string, string | true>;
|
|
9
|
-
};
|
|
10
|
-
export declare function getFlag<T extends string | number>(flags: Record<string, string | true>, key: string, parse?: (s: string) => T): T | undefined;
|
|
11
|
-
//# sourceMappingURL=parse-args.d.ts.map
|
package/dist/parse-args.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parse-args.d.ts","sourceRoot":"","sources":["../src/parse-args.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACrC,CA8BA;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAChD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EACpC,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,CAAC,GACtB,CAAC,GAAG,SAAS,CAIf"}
|