suzi-cli 0.1.26 → 0.1.28

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.
@@ -11,6 +11,8 @@ const config_1 = require("../lib/config");
11
11
  const api_1 = require("../lib/api");
12
12
  const ui_1 = require("../utils/ui");
13
13
  const tty_1 = require("../utils/tty");
14
+ const agent_picker_1 = require("../utils/agent-picker");
15
+ const PORTFOLIO_ORDERS_INCLUDES = new Set(['fills']);
14
16
  function formatPnlPercent(pct) {
15
17
  if (pct == null)
16
18
  return chalk_1.default.gray('—');
@@ -35,17 +37,230 @@ function exposureLabel(pos) {
35
37
  return '—';
36
38
  }
37
39
  }
40
+ function formatPortfolioOrderStatus(status) {
41
+ if (status === 'filled')
42
+ return chalk_1.default.green('Filled');
43
+ if (status === 'partial')
44
+ return chalk_1.default.yellow('Partial');
45
+ if (status === 'cancelled')
46
+ return chalk_1.default.red('Cancelled');
47
+ if (status === 'expired')
48
+ return chalk_1.default.gray('Expired');
49
+ return chalk_1.default.cyan('Open');
50
+ }
51
+ function renderPortfolioPositionsSection(positions) {
52
+ console.log(chalk_1.default.bold(' Positions'));
53
+ const byProtocol = new Map();
54
+ for (const pos of positions) {
55
+ const existing = byProtocol.get(pos.protocol) ?? [];
56
+ existing.push(pos);
57
+ byProtocol.set(pos.protocol, existing);
58
+ }
59
+ for (const [protocol, protocolPositions] of byProtocol) {
60
+ console.log();
61
+ console.log(ui_1.colors.secondary(` ${protocol}`));
62
+ const hasPerps = protocolPositions.some((p) => p.detail?.kind === 'perps');
63
+ const posHead = [
64
+ chalk_1.default.gray('Instrument'),
65
+ chalk_1.default.gray('Type'),
66
+ chalk_1.default.gray('Exposure'),
67
+ chalk_1.default.gray('Value'),
68
+ chalk_1.default.gray('PnL%'),
69
+ ...(hasPerps ? [chalk_1.default.gray('Funding')] : []),
70
+ chalk_1.default.gray('Status'),
71
+ ];
72
+ const posTable = new cli_table3_1.default({
73
+ head: posHead,
74
+ style: { head: [], border: chalk_1.default.level > 0 ? ['gray'] : [] },
75
+ });
76
+ for (const pos of protocolPositions) {
77
+ const fundingCell = pos.detail?.kind === 'perps' && pos.detail.fundingRate != null
78
+ ? (pos.detail.fundingRate >= 0
79
+ ? chalk_1.default.green(`${(pos.detail.fundingRate * 100).toFixed(4)}%`)
80
+ : chalk_1.default.red(`${(pos.detail.fundingRate * 100).toFixed(4)}%`))
81
+ : chalk_1.default.gray('—');
82
+ posTable.push([
83
+ chalk_1.default.white(pos.instrument),
84
+ pos.type,
85
+ exposureLabel(pos),
86
+ (0, ui_1.formatUsd)(pos.valueUsd),
87
+ formatPnlPercent(pos.pnlPercent),
88
+ ...(hasPerps ? [fundingCell] : []),
89
+ pos.status,
90
+ ]);
91
+ }
92
+ console.log(posTable.toString());
93
+ }
94
+ }
95
+ function renderPortfolioOrdersSection(orders) {
96
+ console.log(chalk_1.default.bold(' Orders'));
97
+ const ordersTable = new cli_table3_1.default({
98
+ head: [
99
+ chalk_1.default.gray('Instrument'),
100
+ chalk_1.default.gray('Side'),
101
+ chalk_1.default.gray('Type'),
102
+ chalk_1.default.gray('Amount'),
103
+ chalk_1.default.gray('Price'),
104
+ chalk_1.default.gray('Status'),
105
+ chalk_1.default.gray('Protocol'),
106
+ ],
107
+ style: { head: [], border: chalk_1.default.level > 0 ? ['gray'] : [] },
108
+ });
109
+ for (const o of orders) {
110
+ const sideColor = o.side === 'buy' || o.side === 'long' ? chalk_1.default.green : chalk_1.default.red;
111
+ const filledInfo = o.amountFilled != null && o.amountFilled > 0
112
+ ? ` (${o.amountFilled.toFixed(2)} filled)`
113
+ : '';
114
+ ordersTable.push([
115
+ chalk_1.default.white(o.instrument),
116
+ sideColor(o.side.toUpperCase()),
117
+ o.type,
118
+ `${(o.amount ?? 0).toFixed(4)} ${o.amountSymbol}${filledInfo}`,
119
+ o.price,
120
+ formatPortfolioOrderStatus(o.status),
121
+ ui_1.colors.muted(o.protocol),
122
+ ]);
123
+ }
124
+ console.log(ordersTable.toString());
125
+ }
126
+ function attributedOrderStatus(status) {
127
+ if (status === 'filled')
128
+ return chalk_1.default.green(status);
129
+ if (status === 'open' || status === 'partially_filled')
130
+ return chalk_1.default.yellow(status);
131
+ if (status === 'cancelled')
132
+ return chalk_1.default.gray(status);
133
+ return status;
134
+ }
135
+ function renderAttributedOrdersSection(orders) {
136
+ console.log(chalk_1.default.bold(' Orders'));
137
+ const table = new cli_table3_1.default({
138
+ head: [
139
+ chalk_1.default.gray('Order ID'),
140
+ chalk_1.default.gray('Proto'),
141
+ chalk_1.default.gray('Status'),
142
+ chalk_1.default.gray('Side'),
143
+ chalk_1.default.gray('Asset'),
144
+ chalk_1.default.gray('Size'),
145
+ chalk_1.default.gray('Filled'),
146
+ chalk_1.default.gray('Price'),
147
+ chalk_1.default.gray('Time'),
148
+ ],
149
+ style: { head: [], border: chalk_1.default.level > 0 ? ['gray'] : [] },
150
+ });
151
+ for (const order of orders) {
152
+ table.push([
153
+ order.orderId.length > 14 ? `${order.orderId.slice(0, 13)}…` : order.orderId,
154
+ order.protocol === 'hyperliquid' ? 'HL' : order.protocol === 'polymarket' ? 'PM' : order.protocol,
155
+ attributedOrderStatus(order.status),
156
+ order.side === 'buy' ? chalk_1.default.green(order.side) : chalk_1.default.red(order.side),
157
+ order.asset.length > 10 ? `${order.asset.slice(0, 6)}…` : order.asset,
158
+ order.size,
159
+ order.filledSize,
160
+ order.price ?? '-',
161
+ order.createdAt ? new Date(order.createdAt).toLocaleString() : '-',
162
+ ]);
163
+ }
164
+ console.log(table.toString());
165
+ }
166
+ function renderAttributedPositionsSection(positions) {
167
+ console.log(chalk_1.default.bold(' Positions'));
168
+ const table = new cli_table3_1.default({
169
+ head: [
170
+ chalk_1.default.gray('Proto'),
171
+ chalk_1.default.gray('Asset'),
172
+ chalk_1.default.gray('Net Size'),
173
+ chalk_1.default.gray('Bought'),
174
+ chalk_1.default.gray('Sold'),
175
+ chalk_1.default.gray('Status'),
176
+ ],
177
+ style: { head: [], border: chalk_1.default.level > 0 ? ['gray'] : [] },
178
+ });
179
+ for (const position of positions) {
180
+ const keyTail = position.positionKey.split(':').pop() || position.positionKey;
181
+ table.push([
182
+ position.protocol === 'hyperliquid' ? 'HL' : position.protocol === 'polymarket' ? 'PM' : position.protocol,
183
+ keyTail.length > 10 ? `${keyTail.slice(0, 6)}…` : keyTail,
184
+ position.netSize,
185
+ position.totalBought,
186
+ position.totalSold,
187
+ position.status,
188
+ ]);
189
+ }
190
+ console.log(table.toString());
191
+ }
192
+ function renderPortfolioFillsSection(fills) {
193
+ console.log(chalk_1.default.bold(' Fills'));
194
+ const table = new cli_table3_1.default({
195
+ head: [
196
+ chalk_1.default.gray('Proto'),
197
+ chalk_1.default.gray('Side'),
198
+ chalk_1.default.gray('Asset'),
199
+ chalk_1.default.gray('Size'),
200
+ chalk_1.default.gray('Price'),
201
+ chalk_1.default.gray('Fee'),
202
+ chalk_1.default.gray('Role'),
203
+ chalk_1.default.gray('Time'),
204
+ ],
205
+ style: { head: [], border: chalk_1.default.level > 0 ? ['gray'] : [] },
206
+ });
207
+ for (const fill of fills) {
208
+ const protocol = fill.protocol === 'hyperliquid' ? 'HL' : fill.protocol === 'polymarket' ? 'PM' : fill.protocol;
209
+ const side = fill.side === 'buy' ? chalk_1.default.green(fill.side) : chalk_1.default.red(fill.side);
210
+ const asset = fill.asset.length > 10 ? `${fill.asset.slice(0, 6)}…` : fill.asset;
211
+ table.push([
212
+ protocol,
213
+ side,
214
+ asset,
215
+ fill.fillSize,
216
+ fill.fillPrice,
217
+ fill.fee ?? '-',
218
+ fill.liquidityRole ?? '-',
219
+ new Date(fill.filledAt).toLocaleString(),
220
+ ]);
221
+ }
222
+ console.log(table.toString());
223
+ }
224
+ function renderPaginationHint(meta, shownCount, labelText) {
225
+ if (meta.total <= meta.offset + shownCount)
226
+ return;
227
+ (0, ui_1.info)(`Showing ${meta.offset + 1}-${meta.offset + shownCount} of ${meta.total} ${labelText}. Use --limit/--offset to page.`);
228
+ }
229
+ function renderAgentPortfolioDisplay(agentId, positions, orders, protocol) {
230
+ (0, ui_1.header)('Portfolio');
231
+ console.log();
232
+ (0, ui_1.label)('Agent', agentId);
233
+ if (protocol) {
234
+ (0, ui_1.label)('Protocol', protocol);
235
+ }
236
+ console.log();
237
+ if (positions.length > 0) {
238
+ (0, ui_1.divider)();
239
+ renderAttributedPositionsSection(positions);
240
+ }
241
+ else {
242
+ (0, ui_1.info)('No attributed positions found.');
243
+ }
244
+ console.log();
245
+ if (orders.length > 0) {
246
+ (0, ui_1.divider)();
247
+ renderAttributedOrdersSection(orders);
248
+ }
249
+ else {
250
+ (0, ui_1.info)('No attributed orders found.');
251
+ }
252
+ console.log();
253
+ (0, ui_1.divider)();
254
+ }
38
255
  function renderPortfolioDisplay(snapshot) {
39
256
  (0, ui_1.header)('Portfolio');
40
257
  console.log();
41
- // === Totals ===
42
258
  const t = snapshot.totals;
43
259
  (0, ui_1.label)('Total Value', (0, ui_1.formatUsd)(t.totalValueUsd));
44
260
  (0, ui_1.label)('Exposure', (0, ui_1.formatUsd)(t.totalExposure));
45
261
  (0, ui_1.label)('Spot', (0, ui_1.formatUsd)(t.totalSpot));
46
262
  (0, ui_1.label)('Limit Orders', (0, ui_1.formatUsd)(t.totalLimitOrders));
47
263
  console.log();
48
- // === Holdings (dust already filtered server-side) ===
49
264
  if (snapshot.holdings && snapshot.holdings.length > 0) {
50
265
  (0, ui_1.divider)();
51
266
  console.log(chalk_1.default.bold(' Holdings'));
@@ -67,102 +282,19 @@ function renderPortfolioDisplay(snapshot) {
67
282
  else if (snapshot.holdings !== null) {
68
283
  (0, ui_1.info)('No spot balances found.');
69
284
  }
70
- // === Positions ===
71
285
  if (snapshot.positions && snapshot.positions.length > 0) {
72
286
  console.log();
73
287
  (0, ui_1.divider)();
74
- console.log(chalk_1.default.bold(' Positions'));
75
- // Group by protocol
76
- const byProtocol = new Map();
77
- for (const pos of snapshot.positions) {
78
- const existing = byProtocol.get(pos.protocol) ?? [];
79
- existing.push(pos);
80
- byProtocol.set(pos.protocol, existing);
81
- }
82
- for (const [protocol, positions] of byProtocol) {
83
- console.log();
84
- console.log(ui_1.colors.secondary(` ${protocol}`));
85
- const hasPerps = positions.some((p) => p.detail?.kind === 'perps');
86
- const posHead = [
87
- chalk_1.default.gray('Instrument'),
88
- chalk_1.default.gray('Type'),
89
- chalk_1.default.gray('Exposure'),
90
- chalk_1.default.gray('Value'),
91
- chalk_1.default.gray('PnL%'),
92
- ...(hasPerps ? [chalk_1.default.gray('Funding')] : []),
93
- chalk_1.default.gray('Status'),
94
- ];
95
- const posTable = new cli_table3_1.default({
96
- head: posHead,
97
- style: { head: [], border: chalk_1.default.level > 0 ? ['gray'] : [] },
98
- });
99
- for (const pos of positions) {
100
- const fundingCell = pos.detail?.kind === 'perps' && pos.detail.fundingRate != null
101
- ? (pos.detail.fundingRate >= 0
102
- ? chalk_1.default.green(`${(pos.detail.fundingRate * 100).toFixed(4)}%`)
103
- : chalk_1.default.red(`${(pos.detail.fundingRate * 100).toFixed(4)}%`))
104
- : chalk_1.default.gray('—');
105
- posTable.push([
106
- chalk_1.default.white(pos.instrument),
107
- pos.type,
108
- exposureLabel(pos),
109
- (0, ui_1.formatUsd)(pos.valueUsd),
110
- formatPnlPercent(pos.pnlPercent),
111
- ...(hasPerps ? [fundingCell] : []),
112
- pos.status,
113
- ]);
114
- }
115
- console.log(posTable.toString());
116
- }
288
+ renderPortfolioPositionsSection(snapshot.positions);
117
289
  }
118
290
  else if (snapshot.positions !== null) {
119
291
  console.log();
120
292
  (0, ui_1.info)('No open positions.');
121
293
  }
122
- // === Orders ===
123
294
  if (snapshot.orders && snapshot.orders.length > 0) {
124
295
  console.log();
125
296
  (0, ui_1.divider)();
126
- console.log(chalk_1.default.bold(' Orders'));
127
- const ordersTable = new cli_table3_1.default({
128
- head: [
129
- chalk_1.default.gray('Instrument'),
130
- chalk_1.default.gray('Side'),
131
- chalk_1.default.gray('Type'),
132
- chalk_1.default.gray('Amount'),
133
- chalk_1.default.gray('Price'),
134
- chalk_1.default.gray('Status'),
135
- chalk_1.default.gray('Protocol'),
136
- ],
137
- style: { head: [], border: chalk_1.default.level > 0 ? ['gray'] : [] },
138
- });
139
- for (const o of snapshot.orders) {
140
- const sideColor = o.side === 'buy' || o.side === 'long' ? chalk_1.default.green : chalk_1.default.red;
141
- let statusLabel;
142
- if (o.status === 'filled')
143
- statusLabel = chalk_1.default.green('Filled');
144
- else if (o.status === 'partial')
145
- statusLabel = chalk_1.default.yellow('Partial');
146
- else if (o.status === 'cancelled')
147
- statusLabel = chalk_1.default.red('Cancelled');
148
- else if (o.status === 'expired')
149
- statusLabel = chalk_1.default.gray('Expired');
150
- else
151
- statusLabel = chalk_1.default.cyan('Open');
152
- const filledInfo = o.amountFilled != null && o.amountFilled > 0
153
- ? ` (${o.amountFilled.toFixed(2)} filled)`
154
- : '';
155
- ordersTable.push([
156
- chalk_1.default.white(o.instrument),
157
- sideColor(o.side.toUpperCase()),
158
- o.type,
159
- `${(o.amount ?? 0).toFixed(4)} ${o.amountSymbol}${filledInfo}`,
160
- o.price,
161
- statusLabel,
162
- ui_1.colors.muted(o.protocol),
163
- ]);
164
- }
165
- console.log(ordersTable.toString());
297
+ renderPortfolioOrdersSection(snapshot.orders);
166
298
  }
167
299
  else if (snapshot.orders !== null) {
168
300
  console.log();
@@ -171,34 +303,183 @@ function renderPortfolioDisplay(snapshot) {
171
303
  console.log();
172
304
  (0, ui_1.divider)();
173
305
  }
306
+ function parseSectionLimit(limit, fallback) {
307
+ if (!limit)
308
+ return fallback;
309
+ const parsed = parseInt(limit, 10);
310
+ return Number.isNaN(parsed) || parsed < 1 ? fallback : Math.min(parsed, 200);
311
+ }
312
+ function parseSectionOffset(offset) {
313
+ if (!offset)
314
+ return 0;
315
+ const parsed = parseInt(offset, 10);
316
+ return Number.isNaN(parsed) || parsed < 0 ? 0 : parsed;
317
+ }
318
+ function buildSectionQuery(opts, defaults) {
319
+ const params = new URLSearchParams();
320
+ if (opts.agent)
321
+ params.set('agentId', opts.agent);
322
+ if (opts.protocol)
323
+ params.set('protocol', opts.protocol);
324
+ if (opts.status)
325
+ params.set('status', opts.status);
326
+ if (opts.include)
327
+ params.set('include', opts.include);
328
+ params.set('limit', String(parseSectionLimit(opts.limit, defaults.limit)));
329
+ params.set('offset', String(parseSectionOffset(opts.offset)));
330
+ return params;
331
+ }
332
+ function parseOrdersIncludeOption(include) {
333
+ if (!include)
334
+ return { ok: true };
335
+ const includes = include
336
+ .split(',')
337
+ .map((token) => token.trim())
338
+ .filter((token) => token.length > 0);
339
+ if (includes.length === 0)
340
+ return { ok: true };
341
+ const invalid = includes.find((token) => !PORTFOLIO_ORDERS_INCLUDES.has(token));
342
+ if (invalid) {
343
+ return { ok: false, invalid };
344
+ }
345
+ return { ok: true, value: Array.from(new Set(includes)).join(',') };
346
+ }
347
+ function getApiErrorBody(resp) {
348
+ if (resp == null ||
349
+ typeof resp !== 'object' ||
350
+ !Object.prototype.hasOwnProperty.call(resp, 'data')) {
351
+ return undefined;
352
+ }
353
+ const tmp = resp;
354
+ return tmp.data;
355
+ }
356
+ function formatProtocolFlag(protocol) {
357
+ return protocol ? ` --protocol ${protocol}` : '';
358
+ }
359
+ async function fetchPortfolioSection(path, labelText, jsonMode) {
360
+ const spinner = (0, tty_1.createSpinner)(`Loading ${labelText}...`, jsonMode);
361
+ spinner?.start();
362
+ try {
363
+ const resp = await (0, api_1.get)(path);
364
+ spinner?.stop();
365
+ if (!resp.ok) {
366
+ const errorBody = getApiErrorBody(resp);
367
+ const msg = `Failed to load ${labelText}: ${errorBody?.message || errorBody?.error || 'Unknown error'}`;
368
+ if (jsonMode)
369
+ (0, tty_1.outputJson)({ success: false, error: msg });
370
+ else
371
+ (0, ui_1.error)(msg);
372
+ return null;
373
+ }
374
+ return resp.data;
375
+ }
376
+ catch (err) {
377
+ spinner?.stop();
378
+ const msg = `Failed to load ${labelText}: ${err instanceof Error ? err.message : 'Unknown error'}`;
379
+ if (jsonMode)
380
+ (0, tty_1.outputJson)({ success: false, error: msg });
381
+ else
382
+ (0, ui_1.error)(msg);
383
+ return null;
384
+ }
385
+ }
174
386
  function registerPortfolioCommand(program) {
175
387
  const portfolioCmd = program
176
388
  .command('portfolio')
177
389
  .description('View your complete portfolio — balances, positions, P&L across all protocols')
178
390
  .enablePositionalOptions()
179
391
  .passThroughOptions();
392
+ const ensureActiveAccount = (jsonMode) => {
393
+ const account = (0, config_1.getActiveAccount)();
394
+ if (account)
395
+ return true;
396
+ const msg = 'No active account.';
397
+ if (jsonMode)
398
+ (0, tty_1.outputJson)({ success: false, error: msg });
399
+ else
400
+ (0, ui_1.error)(msg);
401
+ return false;
402
+ };
403
+ const resolveAgentOption = async (agentOpt, jsonMode) => {
404
+ if (agentOpt == null)
405
+ return null;
406
+ return (0, agent_picker_1.fetchAndPickAgent)(typeof agentOpt === 'string' ? agentOpt : undefined, jsonMode);
407
+ };
180
408
  // Default action — runs when no subcommand is given (existing behavior)
181
409
  portfolioCmd
182
410
  .option('--json', 'Output as JSON')
183
411
  .option('--all-balances', 'Show low/dust spot balances (default hides tiny balances)')
184
412
  .option('--svm-wallet <address>', 'Override SVM wallet address')
185
413
  .option('--evm-wallet <address>', 'Override EVM wallet address')
414
+ .option('--agent [agentIdOrName]', 'Filter to a specific agent; omit value to pick interactively')
415
+ .option('-p, --protocol <protocol>', 'Filter positions/orders by protocol in agent summary mode')
186
416
  .action(async (opts) => {
187
417
  const jsonMode = opts?.json || false;
188
418
  if (!(0, ui_1.requireAuth)(jsonMode))
189
419
  return;
190
- const showAllBalances = opts?.allBalances || false;
191
- const account = (0, config_1.getActiveAccount)();
192
- if (!account) {
193
- const msg = 'No active account.';
194
- if (jsonMode) {
195
- (0, tty_1.outputJson)({ success: false, error: msg });
420
+ if (!ensureActiveAccount(jsonMode))
421
+ return;
422
+ const protocol = opts?.protocol;
423
+ const resolvedAgent = await resolveAgentOption(opts?.agent, jsonMode);
424
+ if (opts?.agent && !resolvedAgent)
425
+ return;
426
+ if (resolvedAgent) {
427
+ const spinner = (0, tty_1.createSpinner)('Loading attributed portfolio...', jsonMode);
428
+ spinner?.start();
429
+ try {
430
+ const params = new URLSearchParams();
431
+ params.set('agentId', resolvedAgent.id);
432
+ params.set('limit', '20');
433
+ params.set('offset', '0');
434
+ if (protocol)
435
+ params.set('protocol', protocol);
436
+ const qs = params.toString();
437
+ const [ordersResp, positionsResp] = await Promise.all([
438
+ (0, api_1.get)(`/api/portfolio/agent-orders?${qs}`),
439
+ (0, api_1.get)(`/api/portfolio/agent-positions?${qs}`),
440
+ ]);
441
+ spinner?.stop();
442
+ if (!ordersResp.ok || !positionsResp.ok) {
443
+ const errorBody = getApiErrorBody(!ordersResp.ok ? ordersResp : positionsResp);
444
+ const msg = `Failed to load attributed portfolio: ${errorBody?.message || errorBody?.error || 'Unknown error'}`;
445
+ if (jsonMode)
446
+ (0, tty_1.outputJson)({ success: false, error: msg });
447
+ else
448
+ (0, ui_1.error)(msg);
449
+ return;
450
+ }
451
+ if (jsonMode) {
452
+ (0, tty_1.outputJson)({
453
+ success: true,
454
+ data: {
455
+ agentId: resolvedAgent.id,
456
+ protocol: protocol ?? null,
457
+ orders: ordersResp.data,
458
+ positions: positionsResp.data,
459
+ },
460
+ });
461
+ return;
462
+ }
463
+ renderAgentPortfolioDisplay(resolvedAgent.title || resolvedAgent.id, positionsResp.data.positions, ordersResp.data.orders, protocol);
464
+ if (positionsResp.data.pagination.total > positionsResp.data.positions.length) {
465
+ (0, ui_1.info)(`Use ${chalk_1.default.cyan(`suzi portfolio positions --agent ${resolvedAgent.id}${formatProtocolFlag(protocol)}`)} for full position pagination.`);
466
+ }
467
+ if (ordersResp.data.pagination.total > ordersResp.data.orders.length) {
468
+ (0, ui_1.info)(`Use ${chalk_1.default.cyan(`suzi portfolio orders --agent ${resolvedAgent.id}${formatProtocolFlag(protocol)}`)} for full order pagination.`);
469
+ }
470
+ return;
196
471
  }
197
- else {
198
- (0, ui_1.error)(msg);
472
+ catch (err) {
473
+ spinner?.stop();
474
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
475
+ if (jsonMode)
476
+ (0, tty_1.outputJson)({ success: false, error: `Failed to load attributed portfolio: ${errorMessage}` });
477
+ else
478
+ (0, ui_1.error)(`Failed to load attributed portfolio: ${errorMessage}`);
479
+ return;
199
480
  }
200
- return;
201
481
  }
482
+ const showAllBalances = opts?.allBalances || false;
202
483
  const spinner = (0, tty_1.createSpinner)('Loading portfolio...', jsonMode);
203
484
  spinner?.start();
204
485
  try {
@@ -214,14 +495,12 @@ function registerPortfolioCommand(program) {
214
495
  const resp = await (0, api_1.get)(endpoint);
215
496
  if (!resp.ok) {
216
497
  spinner?.stop();
217
- const errorBody = resp.data;
498
+ const errorBody = getApiErrorBody(resp);
218
499
  const msg = `Failed to load portfolio: ${errorBody?.message || errorBody?.error || 'Unknown error'}`;
219
- if (jsonMode) {
500
+ if (jsonMode)
220
501
  (0, tty_1.outputJson)({ success: false, error: msg });
221
- }
222
- else {
502
+ else
223
503
  (0, ui_1.error)(msg);
224
- }
225
504
  return;
226
505
  }
227
506
  spinner?.stop();
@@ -238,6 +517,150 @@ function registerPortfolioCommand(program) {
238
517
  (0, ui_1.error)(`Failed to load portfolio: ${errorMessage}`);
239
518
  }
240
519
  });
520
+ portfolioCmd
521
+ .command('orders')
522
+ .description('View account-level or agent-attributed orders')
523
+ .option('--json', 'Output as JSON')
524
+ .option('--agent [agentIdOrName]', 'Filter to a specific agent; omit value to pick interactively')
525
+ .option('-p, --protocol <protocol>', 'Filter by protocol')
526
+ .option('--include <sections>', 'Include extra sections (currently: fills)')
527
+ .option('-s, --status <status>', 'Status filter (supports aliases like active/inactive or exact statuses like filled)')
528
+ .option('-n, --limit <limit>', 'Max rows to return', '50')
529
+ .option('--offset <offset>', 'Pagination offset', '0')
530
+ .action(async (opts) => {
531
+ const jsonMode = opts.json || false;
532
+ if (!(0, ui_1.requireAuth)(jsonMode))
533
+ return;
534
+ if (!ensureActiveAccount(jsonMode))
535
+ return;
536
+ const resolvedAgent = await resolveAgentOption(opts.agent, jsonMode);
537
+ if (opts.agent && !resolvedAgent)
538
+ return;
539
+ const includeResult = parseOrdersIncludeOption(opts.include);
540
+ if (!includeResult.ok) {
541
+ const msg = `Invalid include section: "${includeResult.invalid}". Valid values: fills`;
542
+ if (jsonMode)
543
+ (0, tty_1.outputJson)({ success: false, error: msg });
544
+ else
545
+ (0, ui_1.error)(msg);
546
+ return;
547
+ }
548
+ const params = buildSectionQuery({ ...opts, agent: resolvedAgent?.id, include: includeResult.value }, { limit: 50 });
549
+ const qs = params.toString();
550
+ const path = resolvedAgent
551
+ ? `/api/portfolio/agent-orders?${qs}`
552
+ : `/api/portfolio/orders?${qs}`;
553
+ const result = await fetchPortfolioSection(path, 'portfolio orders', jsonMode);
554
+ if (!result)
555
+ return;
556
+ if (jsonMode) {
557
+ (0, tty_1.outputJson)({ success: true, data: result });
558
+ return;
559
+ }
560
+ (0, ui_1.header)('Portfolio Orders');
561
+ console.log();
562
+ if (resolvedAgent) {
563
+ const data = result;
564
+ if (data.orders.length === 0 && (!data.fills || data.fills.length === 0)) {
565
+ (0, ui_1.info)('No attributed orders found.');
566
+ return;
567
+ }
568
+ if (data.orders.length > 0) {
569
+ renderAttributedOrdersSection(data.orders);
570
+ renderPaginationHint(data.pagination, data.orders.length, 'orders');
571
+ }
572
+ else {
573
+ (0, ui_1.info)('No attributed orders found.');
574
+ }
575
+ if (includeResult.value?.includes('fills')) {
576
+ console.log();
577
+ if (data.fills && data.fills.length > 0) {
578
+ renderPortfolioFillsSection(data.fills);
579
+ if (data.fillsPagination) {
580
+ renderPaginationHint(data.fillsPagination, data.fills.length, 'fills');
581
+ }
582
+ }
583
+ else {
584
+ (0, ui_1.info)('No attributed fills found.');
585
+ }
586
+ }
587
+ return;
588
+ }
589
+ const data = result;
590
+ if (data.orders.length === 0 && (!data.fills || data.fills.length === 0)) {
591
+ (0, ui_1.info)('No orders found.');
592
+ return;
593
+ }
594
+ if (data.orders.length > 0) {
595
+ renderPortfolioOrdersSection(data.orders);
596
+ renderPaginationHint(data.pagination, data.orders.length, 'orders');
597
+ }
598
+ else {
599
+ (0, ui_1.info)('No orders found.');
600
+ }
601
+ if (includeResult.value?.includes('fills')) {
602
+ console.log();
603
+ if (data.fills && data.fills.length > 0) {
604
+ renderPortfolioFillsSection(data.fills);
605
+ if (data.fillsPagination) {
606
+ renderPaginationHint(data.fillsPagination, data.fills.length, 'fills');
607
+ }
608
+ }
609
+ else {
610
+ (0, ui_1.info)('No fills found.');
611
+ }
612
+ }
613
+ });
614
+ portfolioCmd
615
+ .command('positions')
616
+ .description('View account-level or agent-attributed positions')
617
+ .option('--json', 'Output as JSON')
618
+ .option('--agent [agentIdOrName]', 'Filter to a specific agent; omit value to pick interactively')
619
+ .option('-p, --protocol <protocol>', 'Filter by protocol')
620
+ .option('-s, --status <status>', 'Status filter (supports aliases like open/closed or exact statuses like live)')
621
+ .option('-n, --limit <limit>', 'Max rows to return', '50')
622
+ .option('--offset <offset>', 'Pagination offset', '0')
623
+ .action(async (opts) => {
624
+ const jsonMode = opts.json || false;
625
+ if (!(0, ui_1.requireAuth)(jsonMode))
626
+ return;
627
+ if (!ensureActiveAccount(jsonMode))
628
+ return;
629
+ const resolvedAgent = await resolveAgentOption(opts.agent, jsonMode);
630
+ if (opts.agent && !resolvedAgent)
631
+ return;
632
+ const params = buildSectionQuery({ ...opts, agent: resolvedAgent?.id }, { limit: 50 });
633
+ const qs = params.toString();
634
+ const path = resolvedAgent
635
+ ? `/api/portfolio/agent-positions?${qs}`
636
+ : `/api/portfolio/positions?${qs}`;
637
+ const result = await fetchPortfolioSection(path, 'portfolio positions', jsonMode);
638
+ if (!result)
639
+ return;
640
+ if (jsonMode) {
641
+ (0, tty_1.outputJson)({ success: true, data: result });
642
+ return;
643
+ }
644
+ (0, ui_1.header)('Portfolio Positions');
645
+ console.log();
646
+ if (resolvedAgent) {
647
+ const data = result;
648
+ if (data.positions.length === 0) {
649
+ (0, ui_1.info)('No attributed positions found.');
650
+ return;
651
+ }
652
+ renderAttributedPositionsSection(data.positions);
653
+ renderPaginationHint(data.pagination, data.positions.length, 'positions');
654
+ return;
655
+ }
656
+ const data = result;
657
+ if (data.positions.length === 0) {
658
+ (0, ui_1.info)('No positions found.');
659
+ return;
660
+ }
661
+ renderPortfolioPositionsSection(data.positions);
662
+ renderPaginationHint(data.pagination, data.positions.length, 'positions');
663
+ });
241
664
  // Snapshots subcommand
242
665
  portfolioCmd
243
666
  .command('snapshots')
@@ -270,7 +693,7 @@ function registerPortfolioCommand(program) {
270
693
  const resp = await (0, api_1.get)(endpoint);
271
694
  if (!resp.ok) {
272
695
  spinner?.stop();
273
- const errorBody = resp.data;
696
+ const errorBody = getApiErrorBody(resp);
274
697
  const msg = `Failed to load snapshots: ${errorBody?.message || errorBody?.error || 'Unknown error'}`;
275
698
  if (jsonMode) {
276
699
  (0, tty_1.outputJson)({ success: false, error: msg });