Skip to content

Commit a985567

Browse files
more json
1 parent eaff269 commit a985567

34 files changed

Lines changed: 510 additions & 103 deletions

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +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.
13+
- Prefer machine-readable CLI output when available: read-only inspection commands in `cb` and `hdb` should default to `--json`, with `--json-file <path>` used when the same payload should be written to disk.
1414

1515

1616
# Autonomy and Persistence

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +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.
45+
Prefer machine-readable CLI output for local inspection and automation when available: use `cb ... --json` and `hdb ... --json` on read-only inspection commands, plus `--json-file <path>` when the same payload should be written to disk.
4646

4747
### cb app boundaries
4848

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ From source:
6060
Machine-readable CLI output:
6161

6262
- 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
63+
- use `--json-file <path>` on read-only inspection commands that expose it when you want the same payload written to disk
6464

6565
## Documentation
6666

src/apps/cb/README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,19 @@ Notes:
102102
- `--json-file <path>` writes the same structured payload to disk and still prints JSON to stdout
103103
- `--value` adds a USD value column based on current product price (forced product refresh for supported products)
104104
- when `[product]` is provided, matching accounts are shown with price-based USD values
105-
- `cb balance` (alias: `usd`)
106-
- `cb cash`
107-
- `cb fees`
105+
- `cb balance [--json] [--json-file <path>]` (alias: `usd`)
106+
- `--json` prints `{ row, meta }`
107+
- `cb cash [--json] [--json-file <path>] [--raw] [--value]`
108+
- same structured payload and formatting flags as `cb accounts --cash`
109+
- `cb fees [--json] [--json-file <path>]`
110+
- `--json` prints `{ row, meta }`
108111

109112
### Products
110113

111-
- `cb product [product]`
112-
- `cb price [product]`
114+
- `cb product [product] [--json] [--json-file <path>]`
115+
- `--json` prints `{ row, meta }`
116+
- `cb price [product] [--json] [--json-file <path>]`
117+
- `--json` prints `{ row, meta }`
113118

114119
### Market Orders
115120

@@ -133,8 +138,10 @@ Notes:
133138

134139
### Orders
135140

136-
- `cb order get <order_id>`
137-
- `cb order list [product]` (alias: `cb orders [product]`)
141+
- `cb order get <order_id> [--json] [--json-file <path>]`
142+
- `--json` prints `{ row, meta }`
143+
- `cb order list [product] [--json] [--json-file <path>]` (alias: `cb orders [product]`)
144+
- `--json` prints `{ rows, filters, meta }`
138145
- `cb order cancel <order_id>`
139146
- `cb order replace <order_id>` (re-places a cancelled priced order with the same prices and attached TP/SL when present; funding must be available: base asset for sells, USD for buys)
140147
- `cb order modify <order_id> [--baseSize <baseSize>] [--limitPrice <limitPrice>] [--stopPrice <stopPrice>] [--takeProfitPrice <takeProfitPrice>]` (supports limit, stop-limit, bracket, and TP/SL orders)
@@ -168,6 +175,8 @@ Examples:
168175
cb accounts --json
169176
cb accounts btc-usd --json
170177
cb accounts --crypto --raw --json-file tmp/accounts.json
178+
cb price eth --json
179+
cb order list btc-usd --json-file tmp/open-orders.json
171180
```
172181

173182
## Development

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

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { mkdirSync, writeFileSync } from "node:fs";
2-
import path from "node:path";
3-
import process from "node:process";
4-
import type { AccountsOptions } from "./schemas/command-options.js";
1+
import { emitJsonOutput } from "./json-output.js";
2+
import type { AccountsOptions, InspectOptions } from "./schemas/command-options.js";
53
import { toIncrement } from "../../../shared/common/index.js";
64
import {
75
getProductInfo,
@@ -134,16 +132,6 @@ function buildAccountsJsonPayload(
134132
};
135133
}
136134

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-
147135
function buildProductJsonRows(
148136
accounts: CoinbaseAccount[],
149137
price: string,
@@ -277,10 +265,7 @@ export async function handleAccountsAction(
277265
options,
278266
product,
279267
);
280-
if (options.jsonFile) {
281-
writeAccountsJsonFile(payload, options.jsonFile);
282-
}
283-
printAccountsJson(payload);
268+
emitJsonOutput(payload, options);
284269
return;
285270
}
286271
console.table(
@@ -311,10 +296,7 @@ export async function handleAccountsAction(
311296
options,
312297
product,
313298
);
314-
if (options.jsonFile) {
315-
writeAccountsJsonFile(payload, options.jsonFile);
316-
}
317-
printAccountsJson(payload);
299+
emitJsonOutput(payload, options);
318300
return;
319301
}
320302

@@ -338,24 +320,55 @@ export async function handleAccountsAction(
338320
}
339321
}
340322

341-
export async function handleBalanceAction() {
323+
export async function handleBalanceAction(options: InspectOptions = {}): Promise<void> {
342324
const { available, hold, total } = await requestCurrencyAccount("USD", "0.01");
325+
if (options.json || options.jsonFile) {
326+
emitJsonOutput({
327+
row: {
328+
currency: "USD",
329+
available,
330+
hold,
331+
total,
332+
},
333+
meta: {
334+
view: "balance",
335+
},
336+
}, options);
337+
return;
338+
}
343339
console.log("USD ($)");
344340
console.log(`Available: $${available}`);
345341
console.log(`Hold: $${hold}`);
346342
console.log(`Total: $${total}`);
347343
}
348344

349-
export async function handleCashAction() {
345+
export async function handleCashAction(options: AccountsOptions = {}): Promise<void> {
350346
const accountsOptions: AccountsOptions = {
351347
cash: true,
348+
...options,
352349
};
353350
return handleAccountsAction(null, accountsOptions);
354351
}
355352

356-
export async function handleFeesAction() {
353+
export async function handleFeesAction(options: InspectOptions = {}): Promise<void> {
357354
const { total_fees, total_volume, fee_tier, total_balance } = await getTransactionSummary();
358355
const { pricing_tier, taker_fee_rate, maker_fee_rate } = fee_tier;
356+
if (options.json || options.jsonFile) {
357+
emitJsonOutput({
358+
row: {
359+
totalBalance: total_balance,
360+
totalVolume: total_volume,
361+
pricingTier: pricing_tier,
362+
takerFeeRate: taker_fee_rate,
363+
makerFeeRate: maker_fee_rate,
364+
totalFees: total_fees,
365+
},
366+
meta: {
367+
view: "fees",
368+
},
369+
}, options);
370+
return;
371+
}
359372
console.log("Transaction Summary:");
360373
console.log(` Total balance: ${total_balance}`);
361374
console.log(` Total volume: ${total_volume}`);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { mkdirSync, writeFileSync } from "node:fs";
2+
import path from "node:path";
3+
import process from "node:process";
4+
5+
export type JsonObject = Record<string, unknown>;
6+
7+
export function serializeJson(payload: JsonObject): string {
8+
return `${JSON.stringify(payload, null, 2)}\n`;
9+
}
10+
11+
export function printJson(payload: JsonObject): void {
12+
console.log(serializeJson(payload).trimEnd());
13+
}
14+
15+
export function writeJsonFile(filePath: string, payload: JsonObject): string {
16+
const outputPath = path.resolve(process.cwd(), filePath);
17+
mkdirSync(path.dirname(outputPath), { recursive: true });
18+
writeFileSync(outputPath, serializeJson(payload));
19+
return outputPath;
20+
}
21+
22+
export function emitJsonOutput(
23+
payload: JsonObject,
24+
options: {
25+
json?: boolean | undefined;
26+
jsonFile?: string | undefined;
27+
},
28+
): void {
29+
if (options.jsonFile) {
30+
writeJsonFile(options.jsonFile, payload);
31+
}
32+
33+
if (options.json || options.jsonFile) {
34+
printJson(payload);
35+
}
36+
}

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,43 @@ import process from "node:process";
22
import { cancelOrder, getOpenOrders, getOrder } from "../../../shared/coinbase/index.js";
33
import { logger, printOrder } from "../../../shared/log/index.js";
44
import type { ProductId } from "../../../shared/schemas/shared-primitives.js";
5-
import type { BreakEvenStopOptions, ModifyOptions } from "./schemas/command-options.js";
5+
import { emitJsonOutput } from "./json-output.js";
6+
import type { BreakEvenStopOptions, InspectOptions, ModifyOptions } from "./schemas/command-options.js";
67
import {
78
placeBreakEvenStopOrder,
89
placeModifyOrder,
910
replaceCancelledOrder,
1011
} from "../service/order-service.js";
1112

12-
export async function handleOrderAction(orderId: string): Promise<void> {
13+
export async function handleOrderAction(orderId: string, options: InspectOptions = {}): Promise<void> {
1314
const order = await getOrder(orderId);
15+
if (options.json || options.jsonFile) {
16+
emitJsonOutput({
17+
row: order,
18+
meta: {
19+
orderId,
20+
view: "get",
21+
},
22+
}, options);
23+
return;
24+
}
1425
printOrder(order);
1526
}
1627

17-
export async function handleOrdersAction(productId: ProductId | null): Promise<void> {
28+
export async function handleOrdersAction(productId: ProductId | null, options: InspectOptions = {}): Promise<void> {
1829
const openOrders = await getOpenOrders(productId);
30+
if (options.json || options.jsonFile) {
31+
emitJsonOutput({
32+
rows: openOrders,
33+
filters: {
34+
productId,
35+
},
36+
meta: {
37+
rowCount: openOrders.length,
38+
},
39+
}, options);
40+
return;
41+
}
1942
if (openOrders.length === 0) {
2043
logger.info("No open orders found.");
2144
} else {

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

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,53 @@ import {
33
getProductInfo,
44
requestMarketTrades,
55
} from "../../../shared/coinbase/index.js";
6+
import { emitJsonOutput } from "./json-output.js";
7+
import type { InspectOptions } from "./schemas/command-options.js";
68

7-
export async function handleProductAction(product: string = "btc"): Promise<void> {
9+
export async function handleProductAction(product: string = "btc", options: InspectOptions = {}): Promise<void> {
810
const productId = getProductId(product);
911
const productInfo = await getProductInfo(productId);
12+
if (options.json || options.jsonFile) {
13+
emitJsonOutput({
14+
row: productInfo,
15+
meta: {
16+
productId,
17+
view: "product",
18+
},
19+
}, options);
20+
return;
21+
}
1022
console.dir(productInfo);
1123
}
1224

13-
export async function handlePriceAction(product: string = "btc"): Promise<void> {
25+
export async function handlePriceAction(product: string = "btc", options: InspectOptions = {}): Promise<void> {
1426
const productId = getProductId(product);
1527
const { trades, best_bid, best_ask } = await requestMarketTrades(productId, 1);
1628
if (!trades[0]) {
1729
throw new Error("Trades not found");
1830
}
31+
const row = {
32+
productId,
33+
price: trades[0].price,
34+
bid: best_bid,
35+
ask: best_ask,
36+
};
37+
if (options.json || options.jsonFile) {
38+
emitJsonOutput({
39+
row,
40+
meta: {
41+
productId,
42+
view: "price",
43+
},
44+
}, options);
45+
return;
46+
}
1947
console.table([
2048
{
21-
Product: productId,
22-
Price: trades[0].price,
23-
Bid: best_bid,
24-
Ask: best_ask,
49+
Product: row.productId,
50+
Price: row.price,
51+
Bid: row.bid,
52+
Ask: row.ask,
2553
},
2654
]);
2755
}

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { Command } from "commander";
2-
import { AccountsOptionsSchema } from "../schemas/command-options.js";
2+
import { AccountsOptionsSchema, InspectOptionsSchema } from "../schemas/command-options.js";
33
import {
44
handleAccountsAction,
55
handleBalanceAction,
66
handleCashAction,
77
handleFeesAction,
88
} from "../account-handlers.js";
9-
import { parseNone, parseOptionalProductOptions, withAction } from "./register-utils.js";
9+
import { parseOptionalProductOptions, parseOptions, withAction } from "./register-utils.js";
1010

1111
export function registerAccountsCommands(program: Command) {
1212
program
@@ -31,11 +31,22 @@ export function registerAccountsCommands(program: Command) {
3131
.command("balance")
3232
.alias("usd")
3333
.description("Show USD available, hold, and total balances")
34-
.action(withAction("balance", parseNone(), handleBalanceAction));
34+
.option("--json", "Print machine-readable JSON output", false)
35+
.option("--json-file <path>", "Write machine-readable JSON output to <path>")
36+
.action(withAction("balance", parseOptions(InspectOptionsSchema), handleBalanceAction));
3537

36-
program.command("cash").description("List non-zero fiat (cash) account balances").action(handleCashAction);
38+
program
39+
.command("cash")
40+
.description("List non-zero fiat (cash) account balances")
41+
.option("--json", "Print machine-readable JSON output", false)
42+
.option("--json-file <path>", "Write machine-readable JSON output to <path>")
43+
.option("--raw", "Show hold and available sizes without increment-based rounding", false)
44+
.option("--value", "Show estimated USD value using current product prices", false)
45+
.action(withAction("cash", parseOptions(AccountsOptionsSchema), handleCashAction));
3746
program
3847
.command("fees")
3948
.description("Show transaction summary with pricing tier and maker/taker fees")
40-
.action(handleFeesAction);
49+
.option("--json", "Print machine-readable JSON output", false)
50+
.option("--json-file <path>", "Write machine-readable JSON output to <path>")
51+
.action(withAction("fees", parseOptions(InspectOptionsSchema), handleFeesAction));
4152
}

0 commit comments

Comments
 (0)