@go-labs-sg/bb 1.0.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 ADDED
@@ -0,0 +1,114 @@
1
+ # bb — Budget Builder CLI
2
+
3
+ Lightweight CLI that wraps the same tRPC API used by the MCP server. For AI agents (e.g. Chuck/OpenClaw) that don’t support MCP: run `bb` from the shell and get JSON back.
4
+
5
+ **Why no workspace refs in the published package?**
6
+ `workspace:*` only resolves inside your monorepo. When someone installs from npm (`npx @go-labs-sg/bb` or `npm i -g @go-labs-sg/bb`), their environment has no workspace, so the CLI is built with no runtime dependency on `@bb/api`, `@bb/db`, or `@bb/utils`. In the repo we keep `@bb/typescript-config` as a devDependency for typechecking; it is stripped from the tarball when publishing (or use a publish flow that omits workspace devDeps).
7
+
8
+ ## Auth
9
+
10
+ - Set **`BB_API_KEY`** in the environment (same API key as MCP / Goracle > API Keys).
11
+ - The CLI targets the production API at `https://budget-builder.getout.events`.
12
+
13
+ ## Usage
14
+
15
+ From the monorepo root:
16
+
17
+ ```bash
18
+ export BB_API_KEY=your_api_key
19
+ bun run bb -- list-budgets
20
+ bun run bb -- get-budget <id>
21
+ bun run bb -- update-budget-status <id> <status>
22
+ bun run bb -- list-bills
23
+ bun run bb -- approve-bill <id>
24
+ bun run bb -- list-approvals
25
+ bun run bb -- list-suppliers
26
+ ```
27
+
28
+ Or from `packages/cli`:
29
+
30
+ ```bash
31
+ bun run src/index.ts list-budgets
32
+ ```
33
+
34
+ ## Commands
35
+
36
+ | Command | Description |
37
+ |--------|-------------|
38
+ | `list-budgets` | List budgets. Optional: `--projectId`, `--name`, `--status` (comma-separated), `--page`, `--perPage` |
39
+ | `get-budget <id>` | Budget details + line items |
40
+ | `update-budget-status <id> <status>` | Set status (e.g. `APPROVED`, `REJECTED`) |
41
+ | `list-bills` | List bills. Optional: `--projectId`, `--budgetId`, `--status`, `--page`, `--pageSize` |
42
+ | `approve-bill <id>` | Approve a pending bill |
43
+ | `list-approvals` | Pending approvals. Optional: `--type` (budget \| supplier \| bill \| all) |
44
+ | `list-suppliers` | List suppliers. Optional: `--name`, `--page`, `--perPage` |
45
+
46
+ Output is JSON to stdout. Errors go to stderr and exit code 1.
47
+
48
+ ## Install from npm (for agents without the repo)
49
+
50
+ ```bash
51
+ npx @go-labs-sg/bb list-budgets
52
+ # or
53
+ npm install -g @go-labs-sg/bb
54
+ bb list-budgets
55
+ ```
56
+
57
+ Set `BB_API_KEY` in the environment before running.
58
+
59
+ ## Testing (local)
60
+
61
+ 1. **Create an API key** in the web app: Goracle → API Keys → Create MCP API Key. Copy the key (shown once).
62
+
63
+ 2. **Start the web app** (or use a deployed URL):
64
+ ```bash
65
+ bun run web:dev
66
+ ```
67
+
68
+ 3. **Set env and run** from monorepo root:
69
+ ```bash
70
+ export BB_API_KEY=your_copied_key
71
+ bun run bb -- list-budgets
72
+ bun run bb -- list-suppliers
73
+ ```
74
+
75
+ 4. **Verify JSON output** — you should see structured JSON on stdout. Errors go to stderr with exit code 1.
76
+
77
+ If you see `{"error":"Unable to connect..."}` — the production API may be unreachable or your network may be blocking the request.
78
+
79
+ ## Private publishing (npm)
80
+
81
+ To publish privately:
82
+
83
+ 1. **Use a scoped package** (already `@go-labs-sg/bb`).
84
+
85
+ 2. **Set `publishConfig`** in `package.json`:
86
+ ```json
87
+ "publishConfig": {
88
+ "access": "restricted"
89
+ }
90
+ ```
91
+ (`restricted` = private for scoped packages; `public` = public)
92
+
93
+ 3. **Ensure you're logged in** to npm and have access to the `@go-labs-sg` org:
94
+ ```bash
95
+ npm login
96
+ npm org ls go-labs-sg # verify access
97
+ ```
98
+
99
+ 4. **Publish** from the CLI package:
100
+ ```bash
101
+ cd packages/cli
102
+ npm publish
103
+ ```
104
+
105
+ 5. **For Chuck/OpenClaw** — install from your private registry:
106
+ ```bash
107
+ npm install -g @go-labs-sg/bb
108
+ # or
109
+ npx @go-labs-sg/bb list-budgets
110
+ ```
111
+
112
+ Set `BB_API_KEY` in the agent's environment.
113
+
114
+ **Alternative: GitHub Packages** — use `@go-labs-sg/bb` with `registry: https://npm.pkg.github.com` and a `GITHUB_TOKEN` with `read:packages` / `write:packages`.
@@ -0,0 +1,5 @@
1
+ import type { AppRouter } from "@bb/api";
2
+ import { type CreateTRPCProxyClient } from "@trpc/client";
3
+ export declare const api: CreateTRPCProxyClient<AppRouter>;
4
+ export declare function requireApiKey(): string;
5
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,36 @@
1
+ import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
2
+ import SuperJSON from "superjson";
3
+ const BASE_URL = "https://budget-builder.getout.events";
4
+ function getAuthHeader() {
5
+ const key = process.env.BB_API_KEY;
6
+ if (!key?.trim())
7
+ return undefined;
8
+ return `Bearer ${key.trim()}`;
9
+ }
10
+ export const api = createTRPCProxyClient({
11
+ links: [
12
+ httpBatchLink({
13
+ url: `${BASE_URL}/api/trpc`,
14
+ transformer: SuperJSON,
15
+ headers: () => {
16
+ const headers = {
17
+ "x-trpc-source": "bb-cli",
18
+ };
19
+ const auth = getAuthHeader();
20
+ if (auth)
21
+ headers.Authorization = auth;
22
+ return headers;
23
+ },
24
+ }),
25
+ ],
26
+ });
27
+ export function requireApiKey() {
28
+ const key = process.env.BB_API_KEY?.trim();
29
+ if (!key) {
30
+ console.error(JSON.stringify({
31
+ error: "BB_API_KEY is required. Set it in the environment.",
32
+ }));
33
+ process.exit(1);
34
+ }
35
+ return key;
36
+ }
@@ -0,0 +1,28 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,111 @@
1
+ import { api } from "./api-client.js";
2
+ const BUDGET = "BUDGET";
3
+ const BILL = "BILL";
4
+ const SUPPLIER = "SUPPLIER";
5
+ const out = (data) => {
6
+ console.log(JSON.stringify(data, null, 2));
7
+ };
8
+ export async function listBudgets(opts) {
9
+ const input = {
10
+ projectId: opts.projectId,
11
+ name: opts.name,
12
+ statuses: opts.statuses,
13
+ page: opts.page ?? 1,
14
+ perPage: opts.perPage ?? 20,
15
+ };
16
+ const result = await api.budget.getAllBudgets.query(input);
17
+ out(result);
18
+ }
19
+ export async function getBudget(id) {
20
+ const [budget, items] = await Promise.all([
21
+ api.budget.getBudget.query({ id }),
22
+ api.budget.getBudgetItems.query({ budgetId: id }),
23
+ ]);
24
+ out({ budget, items });
25
+ }
26
+ export async function updateBudgetStatus(budgetId, status) {
27
+ const input = {
28
+ id: budgetId,
29
+ status: status,
30
+ };
31
+ const result = await api.budget.updateBudgetStatus.mutate(input);
32
+ out(result);
33
+ }
34
+ export async function listBills(opts) {
35
+ const input = {
36
+ projectId: opts.projectId,
37
+ budgetId: opts.budgetId,
38
+ status: opts.status,
39
+ page: opts.page ?? 1,
40
+ pageSize: opts.pageSize ?? 20,
41
+ };
42
+ const result = await api.bill.getAll.query(input);
43
+ out(result);
44
+ }
45
+ export async function approveBill(billId) {
46
+ const notifications = (await api.budget.getNotifications.query());
47
+ const pending = notifications.filter((n) => n.notificationType === "request" &&
48
+ n.status === "PENDING_APPROVAL" &&
49
+ n.type === BILL &&
50
+ (n.billId === billId || n.bill?.id === billId));
51
+ const approval = pending[0];
52
+ if (!approval) {
53
+ out({ error: "No pending bill approval found for this bill." });
54
+ process.exit(1);
55
+ }
56
+ const result = await api.bill.updateApproval.mutate({
57
+ billApprovalId: approval.id,
58
+ status: "APPROVED",
59
+ });
60
+ out({ ...result, success: true, message: "Bill approved" });
61
+ }
62
+ export async function listApprovals(opts) {
63
+ const notifications = (await api.budget.getNotifications.query());
64
+ const pending = notifications.filter((n) => n.notificationType === "request" && n.status === "PENDING_APPROVAL");
65
+ const typeUpper = opts.type?.toUpperCase();
66
+ const typeFilter = !opts.type || opts.type.toLowerCase() === "all"
67
+ ? () => true
68
+ : (n) => n.type === typeUpper;
69
+ const filtered = pending.filter(typeFilter);
70
+ const items = filtered.map((n) => {
71
+ let id;
72
+ let name;
73
+ if (n.type === BUDGET && n.budget) {
74
+ id = n.budgetId ?? n.id;
75
+ name = n.budget.name;
76
+ }
77
+ else if (n.type === BILL && n.bill) {
78
+ id = n.bill.id;
79
+ name = `Bill ${n.bill.invoiceNumber ?? n.bill.id.slice(0, 8)}`;
80
+ }
81
+ else if (n.type === SUPPLIER && n.supplier) {
82
+ id = n.supplier.id;
83
+ name = n.supplier.name;
84
+ }
85
+ else {
86
+ id = n.id;
87
+ name = "Unknown";
88
+ }
89
+ return {
90
+ approvalId: n.id,
91
+ type: n.type,
92
+ id,
93
+ name,
94
+ requestedBy: n.requester?.name ?? n.requester?.email ?? "Unknown",
95
+ requestedAt: typeof n.createdAt === "string"
96
+ ? n.createdAt
97
+ : n.createdAt instanceof Date
98
+ ? n.createdAt.toISOString()
99
+ : String(n.createdAt),
100
+ };
101
+ });
102
+ out({ items });
103
+ }
104
+ export async function listSuppliers(opts) {
105
+ const result = await api.supplier.getSuppliers.query({
106
+ page: opts.page ?? 1,
107
+ perPage: opts.perPage ?? 20,
108
+ name: opts.name,
109
+ });
110
+ out(result);
111
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import "./load-env.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,146 @@
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";
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
+ ];
17
+ function printHelp() {
18
+ const help = `
19
+ bb — Budget Builder CLI (for AI agents / shell)
20
+
21
+ Usage: bb <command> [options] [args]
22
+
23
+ Auth: Set BB_API_KEY in the environment (same API key as MCP / Goracle).
24
+
25
+ Commands:
26
+ list-budgets List budgets (optional filters)
27
+ --projectId, --name, --status (comma-separated), --page, --perPage
28
+
29
+ get-budget <id> Get budget details + line items
30
+
31
+ update-budget-status <id> <status> Set budget status (e.g. APPROVED, REJECTED)
32
+ status: ${BUDGET_STATUSES.join(", ")}
33
+
34
+ list-bills List bills (optional filters)
35
+ --projectId, --budgetId, --status, --page, --pageSize
36
+
37
+ approve-bill <id> Approve a pending bill
38
+
39
+ list-approvals List pending approvals
40
+ --type budget | supplier | bill | all
41
+
42
+ list-suppliers List suppliers
43
+ --name, --page, --perPage
44
+
45
+ Output: JSON to stdout. Errors to stderr and exit code 1.
46
+ `;
47
+ console.log(help.trim());
48
+ }
49
+ async function main() {
50
+ const { command, positional, flags } = parseArgs(process.argv);
51
+ if (!command || command === "help" || flags["help"] === true) {
52
+ printHelp();
53
+ process.exit(0);
54
+ }
55
+ requireApiKey();
56
+ try {
57
+ switch (command) {
58
+ case "list-budgets": {
59
+ const statusStr = getFlag(flags, "status");
60
+ await listBudgets({
61
+ projectId: getFlag(flags, "projectId"),
62
+ 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)),
70
+ });
71
+ break;
72
+ }
73
+ case "get-budget": {
74
+ const id = positional[0];
75
+ if (!id) {
76
+ console.error(JSON.stringify({ error: "get-budget requires <id>" }));
77
+ process.exit(1);
78
+ }
79
+ await getBudget(id);
80
+ break;
81
+ }
82
+ case "update-budget-status": {
83
+ const [id, status] = positional;
84
+ if (!id || !status) {
85
+ console.error(JSON.stringify({
86
+ error: "update-budget-status requires <id> <status>",
87
+ }));
88
+ process.exit(1);
89
+ }
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);
95
+ }
96
+ await updateBudgetStatus(id, status);
97
+ break;
98
+ }
99
+ case "list-bills": {
100
+ 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)),
106
+ });
107
+ break;
108
+ }
109
+ case "approve-bill": {
110
+ const id = positional[0];
111
+ if (!id) {
112
+ console.error(JSON.stringify({ error: "approve-bill requires <id>" }));
113
+ process.exit(1);
114
+ }
115
+ await approveBill(id);
116
+ break;
117
+ }
118
+ case "list-approvals": {
119
+ await listApprovals({
120
+ type: getFlag(flags, "type"),
121
+ });
122
+ break;
123
+ }
124
+ case "list-suppliers": {
125
+ await listSuppliers({
126
+ name: getFlag(flags, "name"),
127
+ page: getFlag(flags, "page", (s) => Number.parseInt(s, 10)),
128
+ perPage: getFlag(flags, "perPage", (s) => Number.parseInt(s, 10)),
129
+ });
130
+ break;
131
+ }
132
+ default: {
133
+ console.error(JSON.stringify({
134
+ error: `Unknown command: ${command}. Run 'bb help'.`,
135
+ }));
136
+ process.exit(1);
137
+ }
138
+ }
139
+ }
140
+ catch (err) {
141
+ const message = err instanceof Error ? err.message : String(err);
142
+ console.error(JSON.stringify({ error: message }));
143
+ process.exit(1);
144
+ }
145
+ }
146
+ main();
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=load-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-env.d.ts","sourceRoot":"","sources":["../src/load-env.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Load .env from current working directory so BB_API_KEY is available.
3
+ */
4
+ import path from "node:path";
5
+ import { config } from "dotenv";
6
+ config({ path: path.resolve(process.cwd(), ".env") });
@@ -0,0 +1,11 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Minimal argv parser for bb CLI.
3
+ * Supports: command [positional...] [--key=value] [--key value]
4
+ */
5
+ export function parseArgs(argv) {
6
+ const args = argv.slice(2); // drop node and script
7
+ const positional = [];
8
+ const flags = {};
9
+ for (let i = 0; i < args.length; i++) {
10
+ const arg = args[i];
11
+ if (arg === undefined || arg === "--" || arg === "")
12
+ continue;
13
+ if (arg.startsWith("--")) {
14
+ const eq = arg.indexOf("=");
15
+ if (eq > 0) {
16
+ flags[arg.slice(2, eq)] = arg.slice(eq + 1);
17
+ }
18
+ else {
19
+ // --flag or --flag value
20
+ const key = arg.slice(2);
21
+ const next = args[i + 1];
22
+ if (next !== undefined && !next.startsWith("--")) {
23
+ flags[key] = next;
24
+ i++;
25
+ }
26
+ else {
27
+ flags[key] = true;
28
+ }
29
+ }
30
+ }
31
+ else {
32
+ positional.push(arg);
33
+ }
34
+ }
35
+ const command = positional[0] ?? "";
36
+ return { command, positional: positional.slice(1), flags };
37
+ }
38
+ export function getFlag(flags, key, parse) {
39
+ const v = flags[key];
40
+ if (v === undefined || v === true)
41
+ return undefined;
42
+ return parse ? parse(v) : v;
43
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@go-labs-sg/bb",
3
+ "version": "1.0.0",
4
+ "description": "Budget Builder CLI — list budgets, bills, approvals, suppliers; approve bills; change status. For AI agents (e.g. Chuck/OpenClaw).",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "bb": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.build.json",
16
+ "typecheck": "tsc --noEmit",
17
+ "check": "biome check .",
18
+ "check:write": "biome check --write .",
19
+ "run": "bun run src/index.ts",
20
+ "prepublishOnly": "node scripts/strip-workspace-deps.js && bun run build",
21
+ "postpublish": "node scripts/restore-package-json.js"
22
+ },
23
+ "dependencies": {
24
+ "@trpc/client": "^11.10.0",
25
+ "dotenv": "^17.2.4",
26
+ "superjson": "^2.2.6"
27
+ },
28
+ "devDependencies": {},
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/go-labs-sg/budget-builder",
38
+ "directory": "packages/cli"
39
+ }
40
+ }