@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 +114 -0
- package/dist/api-client.d.ts +5 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +36 -0
- package/dist/commands.d.ts +28 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +111 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +146 -0
- package/dist/load-env.d.ts +2 -0
- package/dist/load-env.d.ts.map +1 -0
- package/dist/load-env.js +6 -0
- package/dist/parse-args.d.ts +11 -0
- package/dist/parse-args.d.ts.map +1 -0
- package/dist/parse-args.js +43 -0
- package/package.json +40 -0
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 @@
|
|
|
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"}
|
package/dist/commands.js
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"load-env.d.ts","sourceRoot":"","sources":["../src/load-env.ts"],"names":[],"mappings":""}
|
package/dist/load-env.js
ADDED
|
@@ -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
|
+
}
|