Skip to content

Commit eaff269

Browse files
json
1 parent dc0fd68 commit eaff269

19 files changed

Lines changed: 555 additions & 108 deletions

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ You are Codex, based on GPT-5. You are running as a coding agent in the Codex CL
1010
- When multiple tool calls can be parallelized (e.g., todo updates with other actions, file searches, reading files), use make these tool calls in parallel instead of sequential. Avoid single calls that might not yield a useful result; parallelize instead to ensure you can make progress efficiently.
1111
- Code chunks that you receive (via tool calls or from user) may include inline line numbers in the form "Lxxx:LINE_CONTENT", e.g. "L123:LINE_CONTENT". Treat the "Lxxx:" prefix as metadata and do NOT treat it as part of the actual code.
1212
- Default expectation: deliver working code, not just a plan. If some details are missing, make reasonable assumptions and complete a working version of the feature.
13+
- Prefer machine-readable CLI output when available: `cb ... --json` and `hdb ... --json` should be the default inspection path for agent workflows, with `--json-file <path>` used when a command supports writing the same payload to disk.
1314

1415

1516
# Autonomy and Persistence

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ TypeScript ESM monorepo (`module: NodeNext`). Three CLIs share a `src/shared/` l
4242
- `test/setup/no-network.ts` — global outbound-network block active in `vitest.config.ts`
4343

4444
**Use explicit relative imports with `.js` specifiers** (NodeNext resolution).
45+
Prefer machine-readable CLI output for local inspection and automation when available: `cb ... --json` and `hdb ... --json`, plus `--json-file <path>` on commands that support file output.
4546

4647
### cb app boundaries
4748

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ From source:
5757
- `npm run dev -- --help` (runs `cb`)
5858
- `npm run dev:hdb -- --help` (runs `hdb`)
5959

60+
Machine-readable CLI output:
61+
62+
- prefer `cb ... --json` and `hdb ... --json` when you want structured output for automation or agent workflows
63+
- use `--json-file <path>` on commands that expose it when you want the same payload written to disk
64+
6065
## Documentation
6166

6267
- [`cb CLI README`](src/apps/cb/README.md)

src/apps/cb/README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,16 @@ Notes:
9090

9191
- `[product]` defaults to `BTC` when omitted.
9292
- Use `cb <command> --help` for command-specific options.
93+
- Prefer `cb ... --json` when you want machine-readable output for automation or agent workflows.
9394

9495
## Commands
9596

9697
### Accounts
9798

98-
- `cb accounts [product] [--crypto] [--cash] [--json [filepath]] [--raw] [--value]` (alias: `account`)
99+
- `cb accounts [product] [--crypto] [--cash] [--json] [--json-file <path>] [--raw] [--value]` (alias: `account`)
99100
- non-zero balances by default; hold/available are formatted to each currency's base increment using cached product metadata when present unless `--raw` is used
100-
- `--json` writes all fetched accounts to `./accounts.json` by default, or to the provided filepath; export rows include currency, type, hold, and available, and honor `--crypto`, `--cash`, and `--raw`
101+
- `--json` prints `{ rows, filters, meta }` for machine reading
102+
- `--json-file <path>` writes the same structured payload to disk and still prints JSON to stdout
101103
- `--value` adds a USD value column based on current product price (forced product refresh for supported products)
102104
- when `[product]` is provided, matching accounts are shown with price-based USD values
103105
- `cb balance` (alias: `usd`)
@@ -156,6 +158,18 @@ Notes:
156158
- `--baseSize`, `--limitPrice`, `--stopPrice` are supported
157159
- `--takeProfitPrice` is rejected
158160

161+
## Troubleshooting Workflow
162+
163+
Prefer `cb accounts --json` when you want a stable machine-readable account snapshot for automation or agent-assisted inspection.
164+
165+
Examples:
166+
167+
```bash
168+
cb accounts --json
169+
cb accounts btc-usd --json
170+
cb accounts --crypto --raw --json-file tmp/accounts.json
171+
```
172+
159173
## Development
160174

161175
- `npm run lint`

src/apps/cb/commands/account-handlers.ts

Lines changed: 128 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { writeFileSync } from "node:fs";
1+
import { mkdirSync, writeFileSync } from "node:fs";
22
import path from "node:path";
33
import process from "node:process";
44
import type { AccountsOptions } from "./schemas/command-options.js";
@@ -31,6 +31,25 @@ type AccountExportRow = {
3131
type: CoinbaseAccount["type"];
3232
hold: string;
3333
available: string;
34+
price?: string | null;
35+
holdValueUsd?: string | null;
36+
availableValueUsd?: string | null;
37+
totalValueUsd?: string | null;
38+
};
39+
40+
type AccountsJsonPayload = {
41+
rows: AccountExportRow[];
42+
filters: {
43+
product: string | null;
44+
currency: string | null;
45+
crypto: boolean;
46+
cash: boolean;
47+
raw: boolean;
48+
value: boolean;
49+
};
50+
meta: {
51+
rowCount: number;
52+
};
3453
};
3554

3655
function formatAccountSize(value: string, baseIncrement: string, raw: boolean | undefined): string {
@@ -94,15 +113,89 @@ function toAccountBalanceRow(
94113
};
95114
}
96115

97-
function writeAccountsJson(accounts: AccountExportRow[], jsonOption: boolean | string | undefined) {
98-
if (!jsonOption) {
99-
return;
100-
}
101-
const outputPath = typeof jsonOption === "string" && jsonOption.length > 0
102-
? path.resolve(process.cwd(), jsonOption)
103-
: path.join(process.cwd(), "accounts.json");
104-
writeFileSync(outputPath, `${JSON.stringify(accounts, null, 2)}\n`);
105-
console.log(`Wrote accounts JSON to ${outputPath}`);
116+
function buildAccountsJsonPayload(
117+
rows: AccountExportRow[],
118+
options: AccountsOptions,
119+
product: string | null,
120+
): AccountsJsonPayload {
121+
return {
122+
rows,
123+
filters: {
124+
product,
125+
currency: product ? product.split("-")[0] ?? null : null,
126+
crypto: Boolean(options.crypto),
127+
cash: Boolean(options.cash),
128+
raw: Boolean(options.raw),
129+
value: Boolean(options.value),
130+
},
131+
meta: {
132+
rowCount: rows.length,
133+
},
134+
};
135+
}
136+
137+
function printAccountsJson(payload: AccountsJsonPayload): void {
138+
console.log(JSON.stringify(payload, null, 2));
139+
}
140+
141+
function writeAccountsJsonFile(payload: AccountsJsonPayload, jsonFile: string): void {
142+
const outputPath = path.resolve(process.cwd(), jsonFile);
143+
mkdirSync(path.dirname(outputPath), { recursive: true });
144+
writeFileSync(outputPath, `${JSON.stringify(payload, null, 2)}\n`);
145+
}
146+
147+
function buildProductJsonRows(
148+
accounts: CoinbaseAccount[],
149+
price: string,
150+
priceIncrement: string,
151+
): AccountExportRow[] {
152+
return accounts.map((acc) => {
153+
const hold = acc.hold.value;
154+
const available = acc.available_balance.value;
155+
const holdValueUsd = toIncrement(priceIncrement, parseFloat(hold) * parseFloat(price));
156+
const availableValueUsd = toIncrement(priceIncrement, parseFloat(available) * parseFloat(price));
157+
return {
158+
currency: acc.currency,
159+
type: acc.type,
160+
price,
161+
hold,
162+
available,
163+
holdValueUsd,
164+
availableValueUsd,
165+
totalValueUsd: toIncrement(
166+
priceIncrement,
167+
parseFloat(holdValueUsd) + parseFloat(availableValueUsd),
168+
),
169+
};
170+
});
171+
}
172+
173+
function buildAccountJsonRows(
174+
accounts: CoinbaseAccount[],
175+
metadataByCurrency: Map<string, AccountDisplayMetadata>,
176+
options: AccountsOptions,
177+
): AccountExportRow[] {
178+
return accounts.map((account) => {
179+
const metadata = getAccountMetadata(metadataByCurrency, account.currency);
180+
const row = toAccountBalanceRow(account, metadataByCurrency, options.raw);
181+
const totalSize = parseFloat(account.hold.value) + parseFloat(account.available_balance.value);
182+
return {
183+
currency: account.currency,
184+
type: account.type,
185+
hold: row.Hold,
186+
available: row.Available,
187+
price: options.value ? metadata.price : null,
188+
holdValueUsd: options.value && metadata.price !== null
189+
? toIncrement(metadata.priceIncrement, parseFloat(account.hold.value) * parseFloat(metadata.price))
190+
: null,
191+
availableValueUsd: options.value && metadata.price !== null
192+
? toIncrement(metadata.priceIncrement, parseFloat(account.available_balance.value) * parseFloat(metadata.price))
193+
: null,
194+
totalValueUsd: options.value && metadata.price !== null
195+
? toIncrement(metadata.priceIncrement, totalSize * parseFloat(metadata.price))
196+
: null,
197+
};
198+
});
106199
}
107200

108201
async function getSupportedProduct(
@@ -167,22 +260,7 @@ export async function handleAccountsAction(
167260
): Promise<void> {
168261
const allAccounts = await requestAccounts();
169262
const filteredAccounts = filterAccounts(allAccounts, options);
170-
171-
if (options.json) {
172-
const metadataByCurrency = await buildMetadataByCurrency(filteredAccounts, false);
173-
writeAccountsJson(
174-
filteredAccounts.map((account) => {
175-
const row = toAccountBalanceRow(account, metadataByCurrency, options.raw);
176-
return {
177-
currency: account.currency,
178-
type: account.type,
179-
hold: row.Hold,
180-
available: row.Available,
181-
};
182-
}),
183-
options.json,
184-
);
185-
}
263+
const wantsJson = options.json || Boolean(options.jsonFile);
186264

187265
let accounts = filteredAccounts;
188266

@@ -193,6 +271,18 @@ export async function handleAccountsAction(
193271
}
194272
accounts = accounts.filter((acc) => acc.currency.toUpperCase() === currency);
195273
const { price, price_increment } = await getProductInfo(product);
274+
if (wantsJson) {
275+
const payload = buildAccountsJsonPayload(
276+
buildProductJsonRows(accounts, price, price_increment),
277+
options,
278+
product,
279+
);
280+
if (options.jsonFile) {
281+
writeAccountsJsonFile(payload, options.jsonFile);
282+
}
283+
printAccountsJson(payload);
284+
return;
285+
}
196286
console.table(
197287
accounts.map((acc) => {
198288
const hold = acc.hold.value;
@@ -215,6 +305,18 @@ export async function handleAccountsAction(
215305
return acc.available_balance.value !== "0" || acc.hold.value !== "0";
216306
});
217307
const metadataByCurrency = await buildMetadataByCurrency(accounts, options.value === true);
308+
if (wantsJson) {
309+
const payload = buildAccountsJsonPayload(
310+
buildAccountJsonRows(accounts, metadataByCurrency, options),
311+
options,
312+
product,
313+
);
314+
if (options.jsonFile) {
315+
writeAccountsJsonFile(payload, options.jsonFile);
316+
}
317+
printAccountsJson(payload);
318+
return;
319+
}
218320

219321
console.table(
220322
accounts.map((acc) => {

src/apps/cb/commands/register/register-accounts.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ export function registerAccountsCommands(program: Command) {
1515
.description("List account balances (non-zero only unless [product] is provided)")
1616
.option("--crypto", "Show only crypto accounts", false)
1717
.option("--cash", "Show only fiat (cash) accounts; ignored if --crypto is also set", false)
18-
.option("--json [filepath]", "Write account balances JSON to a file (defaults to ./accounts.json)")
18+
.option("--json", "Print machine-readable JSON output", false)
19+
.option("--json-file <path>", "Write machine-readable JSON output to <path>")
1920
.option("--raw", "Show hold and available sizes without increment-based rounding", false)
2021
.option("--value", "Show estimated USD value using current product prices", false)
2122
.action(

src/apps/cb/commands/schemas/command-options.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ export const AccountsOptionsSchema = z
55
.object({
66
crypto: z.boolean().optional(),
77
cash: z.boolean().optional(),
8-
json: z.union([z.boolean(), z.string()]).optional(),
8+
json: z.boolean().optional(),
9+
jsonFile: z.string().trim().min(1).optional(),
910
raw: z.boolean().optional(),
1011
value: z.boolean().optional(),
1112
})

src/apps/hdb/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,15 @@ Install binary in your shell:
7575
- `hdb coinbase balances list <asset>`
7676
- `asset` supports colon-separated values
7777
- `--json` prints structured troubleshooting output
78+
- `--json-file <path>` writes the same structured payload to disk
7879
- `--current` live check requires `--remote`
7980
- `hdb coinbase balances snapshot`
8081
- `--json` prints structured troubleshooting output
82+
- `--json-file <path>` writes the same structured payload to disk
8183
- `--current` live check requires `--remote`
8284
- `hdb coinbase balances trace <asset>`
8385
- `--json` prints structured troubleshooting output
86+
- `--json-file <path>` writes the same structured payload to disk
8487
- `hdb coinbase balances rebuild`
8588
- `hdb coinbase lots analyze <asset>`
8689
- `hdb coinbase lots analyze-all`
@@ -120,6 +123,8 @@ Coinbase migration status and next slices are tracked in:
120123
### CoinTracker
121124

122125
- `hdb cointracker balances list [currency]`
126+
- `--json` prints `{ rows, filters, meta }`
127+
- `--json-file <path>` writes the same structured payload to disk
123128
- `hdb cointracker balances rebuild`
124129
- `hdb cointracker gains list [assets]`
125130
- export flags: `--csv`, `--f8949`, `--headers`, `--pages`

0 commit comments

Comments
 (0)