@quantbrasil/cli 0.1.0-beta.2 → 0.1.0-beta.4

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.
Files changed (35) hide show
  1. package/README.md +14 -11
  2. package/dist/cli/index.d.ts +2 -3
  3. package/dist/cli/index.d.ts.map +1 -1
  4. package/dist/cli/index.js +2 -10
  5. package/dist/commands/portfolios.d.ts +133 -8
  6. package/dist/commands/portfolios.d.ts.map +1 -1
  7. package/dist/commands/portfolios.js +434 -55
  8. package/dist/index.d.ts +0 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +0 -1
  11. package/dist/vendor/core/capabilities/index.d.ts +0 -1
  12. package/dist/vendor/core/capabilities/index.d.ts.map +1 -1
  13. package/dist/vendor/core/capabilities/index.js +0 -1
  14. package/dist/vendor/core/capabilities/portfolios.d.ts +401 -56
  15. package/dist/vendor/core/capabilities/portfolios.d.ts.map +1 -1
  16. package/dist/vendor/core/capabilities/portfolios.js +383 -115
  17. package/dist/vendor/core/capabilities/registry.d.ts +586 -266
  18. package/dist/vendor/core/capabilities/registry.d.ts.map +1 -1
  19. package/dist/vendor/core/capabilities/registry.js +0 -2
  20. package/dist/vendor/core/capabilities/types.d.ts +1 -1
  21. package/dist/vendor/core/capabilities/types.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/skills/quantbrasil/SKILL.md +13 -5
  24. package/skills/quantbrasil/references/cli.md +22 -25
  25. package/skills/quantbrasil/references/costs.md +4 -4
  26. package/skills/quantbrasil/references/errors.md +16 -5
  27. package/skills/quantbrasil/references/portfolios.md +92 -0
  28. package/skills/quantbrasil/references/unsupported.md +2 -2
  29. package/skills/quantbrasil/references/workflows.md +41 -22
  30. package/dist/commands/analytics.d.ts +0 -131
  31. package/dist/commands/analytics.d.ts.map +0 -1
  32. package/dist/commands/analytics.js +0 -291
  33. package/dist/vendor/core/capabilities/analytics.d.ts +0 -187
  34. package/dist/vendor/core/capabilities/analytics.d.ts.map +0 -1
  35. package/dist/vendor/core/capabilities/analytics.js +0 -214
@@ -2,65 +2,146 @@ import { invokeCliCapability } from "../cli/client.js";
2
2
  import { createCliMutationFailedError, createCliValidationError, } from "../cli/errors.js";
3
3
  import { createTerminalTheme } from "../cli/terminal.js";
4
4
  export function registerPortfoliosCommands(program, context = {}) {
5
- const portfoliosCommand = program
6
- .command("portfolios")
7
- .description("Manage saved portfolios");
8
- portfoliosCommand
5
+ const watchlistsCommand = program
6
+ .command("watchlists")
7
+ .description("Manage watchlists for tracking ideas and filters");
8
+ watchlistsCommand
9
9
  .command("list")
10
- .description("List saved portfolios for authenticated user")
10
+ .description("List watchlists for authenticated user")
11
11
  .option("--json", "Show JSON output")
12
12
  .action(async (options) => {
13
- await runPortfolioListCommand(options, context);
13
+ await runWatchlistListCommand(options, context);
14
14
  });
15
- portfoliosCommand
15
+ watchlistsCommand
16
16
  .command("get")
17
- .description("Get one saved portfolio by id")
18
- .argument("<portfolio-id>", "Saved portfolio id", parsePortfolioIdArgument)
17
+ .description("Get one watchlist by id")
18
+ .argument("<watchlist-id>", "Watchlist id", parsePortfolioIdArgument)
19
19
  .option("--json", "Show JSON output")
20
20
  .action(async (portfolioId, options) => {
21
- await runPortfolioGetCommand(portfolioId, options, context);
21
+ await runWatchlistGetCommand(portfolioId, options, context);
22
22
  });
23
- portfoliosCommand
23
+ watchlistsCommand
24
24
  .command("create")
25
- .description("Create a new portfolio")
26
- .argument("<name>", "Portfolio name")
25
+ .description("Create a new watchlist")
26
+ .argument("<name>", "Watchlist name")
27
27
  .option("--json", "Show JSON output")
28
28
  .action(async (name, options) => {
29
- await runPortfolioCreateCommand(name, options, context);
29
+ await runWatchlistCreateCommand(name, options, context);
30
30
  });
31
- portfoliosCommand
31
+ watchlistsCommand
32
32
  .command("rename")
33
- .description("Rename an existing portfolio")
34
- .argument("<portfolio-id>", "Saved portfolio id", parsePortfolioIdArgument)
35
- .argument("<name>", "New portfolio name")
33
+ .description("Rename an existing watchlist")
34
+ .argument("<watchlist-id>", "Watchlist id", parsePortfolioIdArgument)
35
+ .argument("<name>", "New watchlist name")
36
36
  .option("--json", "Show JSON output")
37
37
  .action(async (portfolioId, name, options) => {
38
- await runPortfolioRenameCommand(portfolioId, name, options, context);
38
+ await runWatchlistRenameCommand(portfolioId, name, options, context);
39
39
  });
40
- portfoliosCommand
40
+ watchlistsCommand
41
41
  .command("add-assets")
42
- .description("Add one or more monitored tickers to a portfolio")
43
- .argument("<portfolio-id>", "Saved portfolio id", parsePortfolioIdArgument)
42
+ .description("Add one or more monitored tickers to a watchlist")
43
+ .argument("<watchlist-id>", "Watchlist id", parsePortfolioIdArgument)
44
44
  .argument("<ticker...>", "One or more tickers to add")
45
45
  .option("--json", "Show JSON output")
46
46
  .action(async (portfolioId, symbols, options) => {
47
- await runPortfolioAddAssetsCommand(portfolioId, symbols, options, context);
47
+ await runWatchlistAddAssetsCommand(portfolioId, symbols, options, context);
48
48
  });
49
- portfoliosCommand
49
+ watchlistsCommand
50
50
  .command("remove-assets")
51
- .description("Remove one or more monitored tickers from a portfolio")
52
- .argument("<portfolio-id>", "Saved portfolio id", parsePortfolioIdArgument)
51
+ .description("Remove one or more monitored tickers from a watchlist")
52
+ .argument("<watchlist-id>", "Watchlist id", parsePortfolioIdArgument)
53
53
  .argument("<ticker...>", "One or more tickers to remove")
54
54
  .option("--json", "Show JSON output")
55
55
  .action(async (portfolioId, symbols, options) => {
56
- await runPortfolioRemoveAssetsCommand(portfolioId, symbols, options, context);
56
+ await runWatchlistRemoveAssetsCommand(portfolioId, symbols, options, context);
57
+ });
58
+ const holdingsCommand = program
59
+ .command("holdings")
60
+ .description("Manage holdings for investable metrics");
61
+ holdingsCommand
62
+ .command("list")
63
+ .description("List holdings for authenticated user")
64
+ .option("--json", "Show JSON output")
65
+ .action(async (options) => {
66
+ await runHoldingListCommand(options, context);
67
+ });
68
+ holdingsCommand
69
+ .command("get")
70
+ .description("Get one holding by id")
71
+ .argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
72
+ .option("--json", "Show JSON output")
73
+ .action(async (portfolioId, options) => {
74
+ await runHoldingGetCommand(portfolioId, options, context);
75
+ });
76
+ holdingsCommand
77
+ .command("create")
78
+ .description("Create a new holding")
79
+ .argument("<name>", "Holding name")
80
+ .option("--mode <mode>", "Initial mode: target or position", "target")
81
+ .option("--target <target>", "Initial target weight in TICKER:WEIGHT_PCT form", collectStringOption, [])
82
+ .option("--json", "Show JSON output")
83
+ .action(async (name, options) => {
84
+ await runHoldingCreateCommand(name, options, context);
85
+ });
86
+ holdingsCommand
87
+ .command("rename")
88
+ .description("Rename an existing holding")
89
+ .argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
90
+ .argument("<name>", "New holding name")
91
+ .option("--json", "Show JSON output")
92
+ .action(async (portfolioId, name, options) => {
93
+ await runHoldingRenameCommand(portfolioId, name, options, context);
94
+ });
95
+ holdingsCommand
96
+ .command("set-targets")
97
+ .description("Set target weights for a holding")
98
+ .argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
99
+ .argument("<target...>", "One or more target weights in TICKER:WEIGHT_PCT form")
100
+ .option("--json", "Show JSON output")
101
+ .action(async (portfolioId, targets, options) => {
102
+ await runHoldingSetTargetsCommand(portfolioId, targets, options, context);
103
+ });
104
+ holdingsCommand
105
+ .command("historical-return")
106
+ .description("Calculate historical return for a saved holding")
107
+ .argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
108
+ .requiredOption("--from <date>", "Inclusive start date in ISO format")
109
+ .requiredOption("--to <date>", "Inclusive end date in ISO format")
110
+ .option("--json", "Show JSON output")
111
+ .action(async (portfolioId, options) => {
112
+ await runHoldingHistoricalReturnCommand(portfolioId, options, context);
113
+ });
114
+ holdingsCommand
115
+ .command("beta")
116
+ .description("Calculate holding beta against IBOV")
117
+ .argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
118
+ .option("--years <years>", "Lookback window in years", "1")
119
+ .option("--json", "Show JSON output")
120
+ .action(async (portfolioId, options) => {
121
+ await runHoldingBetaCommand(portfolioId, options, context);
122
+ });
123
+ holdingsCommand
124
+ .command("var")
125
+ .description("Calculate one-day Value-at-Risk for a holding")
126
+ .argument("<holding-id>", "Holding id", parsePortfolioIdArgument)
127
+ .option("--years <years>", "Lookback window in years", "1")
128
+ .option("--confidence <confidence>", "Confidence level in percent", "95")
129
+ .option("--json", "Show JSON output")
130
+ .action(async (portfolioId, options) => {
131
+ await runHoldingVarCommand(portfolioId, options, context);
57
132
  });
58
133
  }
59
- export async function runPortfolioListCommand(options, context = {}) {
134
+ export async function runWatchlistListCommand(options, context = {}) {
135
+ await runPortfolioListLikeCommand("watchlists.list", "Watchlists", options, context);
136
+ }
137
+ export async function runHoldingListCommand(options, context = {}) {
138
+ await runPortfolioListLikeCommand("holdings.list", "Holdings", options, context);
139
+ }
140
+ async function runPortfolioListLikeCommand(capability, title, options, context) {
60
141
  const stdout = context.io?.stdout ?? process.stdout;
61
142
  const theme = createTerminalTheme(stdout, context.env ?? process.env);
62
143
  const response = await invokeCliCapability({
63
- capability: "portfolios.list",
144
+ capability,
64
145
  env: context.env,
65
146
  fetch: context.fetch,
66
147
  });
@@ -68,13 +149,19 @@ export async function runPortfolioListCommand(options, context = {}) {
68
149
  stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
69
150
  return;
70
151
  }
71
- stdout.write(`${formatPortfolioListHuman(response.data, theme)}\n`);
152
+ stdout.write(`${formatPortfolioListHuman(response.data, theme, title)}\n`);
153
+ }
154
+ export async function runWatchlistGetCommand(portfolioId, options, context = {}) {
155
+ await runPortfolioGetLikeCommand("watchlists.get", "Watchlist", portfolioId, options, context);
72
156
  }
73
- export async function runPortfolioGetCommand(portfolioId, options, context = {}) {
157
+ export async function runHoldingGetCommand(portfolioId, options, context = {}) {
158
+ await runPortfolioGetLikeCommand("holdings.get", "Holding", portfolioId, options, context);
159
+ }
160
+ async function runPortfolioGetLikeCommand(capability, title, portfolioId, options, context) {
74
161
  const stdout = context.io?.stdout ?? process.stdout;
75
162
  const theme = createTerminalTheme(stdout, context.env ?? process.env);
76
163
  const response = await invokeCliCapability({
77
- capability: "portfolios.get",
164
+ capability,
78
165
  input: {
79
166
  portfolio_id: portfolioId,
80
167
  },
@@ -85,14 +172,62 @@ export async function runPortfolioGetCommand(portfolioId, options, context = {})
85
172
  stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
86
173
  return;
87
174
  }
88
- stdout.write(`${formatPortfolioGetHuman(response.data, theme)}\n`);
175
+ stdout.write(`${formatPortfolioGetHuman(response.data, theme, title)}\n`);
176
+ }
177
+ export async function runWatchlistCreateCommand(name, options, context = {}) {
178
+ const stdout = context.io?.stdout ?? process.stdout;
179
+ const theme = createTerminalTheme(stdout, context.env ?? process.env);
180
+ const response = await invokeCliCapability({
181
+ capability: "watchlists.create",
182
+ input: {
183
+ name: parsePortfolioNameArgument(name),
184
+ },
185
+ env: context.env,
186
+ fetch: context.fetch,
187
+ });
188
+ writeMutationResponse(response.data, options.json, stdout, theme);
189
+ }
190
+ export async function runHoldingCreateCommand(name, options, context = {}) {
191
+ const stdout = context.io?.stdout ?? process.stdout;
192
+ const theme = createTerminalTheme(stdout, context.env ?? process.env);
193
+ const defaultAllocationInput = parseHoldingModeOption(options.mode ?? "target");
194
+ const targets = parseTargetArguments(options.target ?? []);
195
+ if (targets.length > 0 && defaultAllocationInput === "POSITION") {
196
+ throw createCliValidationError("Initial targets require target mode.");
197
+ }
198
+ const response = await invokeCliCapability({
199
+ capability: "holdings.create",
200
+ input: {
201
+ name: parsePortfolioNameArgument(name),
202
+ default_allocation_input: defaultAllocationInput,
203
+ ...(targets.length > 0 ? { targets } : {}),
204
+ },
205
+ env: context.env,
206
+ fetch: context.fetch,
207
+ });
208
+ writeMutationResponse(response.data, options.json, stdout, theme);
209
+ }
210
+ export async function runHoldingSetTargetsCommand(portfolioId, targets, options, context = {}) {
211
+ const stdout = context.io?.stdout ?? process.stdout;
212
+ const theme = createTerminalTheme(stdout, context.env ?? process.env);
213
+ const response = await invokeCliCapability({
214
+ capability: "holdings.set-targets",
215
+ input: {
216
+ portfolio_id: portfolioId,
217
+ targets: parseTargetArguments(targets),
218
+ },
219
+ env: context.env,
220
+ fetch: context.fetch,
221
+ });
222
+ writeMutationResponse(response.data, options.json, stdout, theme);
89
223
  }
90
- export async function runPortfolioCreateCommand(name, options, context = {}) {
224
+ export async function runWatchlistRenameCommand(portfolioId, name, options, context = {}) {
91
225
  const stdout = context.io?.stdout ?? process.stdout;
92
226
  const theme = createTerminalTheme(stdout, context.env ?? process.env);
93
227
  const response = await invokeCliCapability({
94
- capability: "portfolios.create",
228
+ capability: "watchlists.rename",
95
229
  input: {
230
+ portfolio_id: portfolioId,
96
231
  name: parsePortfolioNameArgument(name),
97
232
  },
98
233
  env: context.env,
@@ -100,11 +235,11 @@ export async function runPortfolioCreateCommand(name, options, context = {}) {
100
235
  });
101
236
  writeMutationResponse(response.data, options.json, stdout, theme);
102
237
  }
103
- export async function runPortfolioRenameCommand(portfolioId, name, options, context = {}) {
238
+ export async function runHoldingRenameCommand(portfolioId, name, options, context = {}) {
104
239
  const stdout = context.io?.stdout ?? process.stdout;
105
240
  const theme = createTerminalTheme(stdout, context.env ?? process.env);
106
241
  const response = await invokeCliCapability({
107
- capability: "portfolios.rename",
242
+ capability: "holdings.rename",
108
243
  input: {
109
244
  portfolio_id: portfolioId,
110
245
  name: parsePortfolioNameArgument(name),
@@ -114,11 +249,11 @@ export async function runPortfolioRenameCommand(portfolioId, name, options, cont
114
249
  });
115
250
  writeMutationResponse(response.data, options.json, stdout, theme);
116
251
  }
117
- export async function runPortfolioAddAssetsCommand(portfolioId, symbols, options, context = {}) {
252
+ export async function runWatchlistAddAssetsCommand(portfolioId, symbols, options, context = {}) {
118
253
  const stdout = context.io?.stdout ?? process.stdout;
119
254
  const theme = createTerminalTheme(stdout, context.env ?? process.env);
120
255
  const response = await invokeCliCapability({
121
- capability: "portfolios.add-assets",
256
+ capability: "watchlists.add-assets",
122
257
  input: {
123
258
  portfolio_id: portfolioId,
124
259
  symbols: parseSymbolArguments(symbols),
@@ -128,11 +263,11 @@ export async function runPortfolioAddAssetsCommand(portfolioId, symbols, options
128
263
  });
129
264
  writeMutationResponse(response.data, options.json, stdout, theme);
130
265
  }
131
- export async function runPortfolioRemoveAssetsCommand(portfolioId, symbols, options, context = {}) {
266
+ export async function runWatchlistRemoveAssetsCommand(portfolioId, symbols, options, context = {}) {
132
267
  const stdout = context.io?.stdout ?? process.stdout;
133
268
  const theme = createTerminalTheme(stdout, context.env ?? process.env);
134
269
  const response = await invokeCliCapability({
135
- capability: "portfolios.remove-assets",
270
+ capability: "watchlists.remove-assets",
136
271
  input: {
137
272
  portfolio_id: portfolioId,
138
273
  symbols: parseSymbolArguments(symbols),
@@ -142,15 +277,71 @@ export async function runPortfolioRemoveAssetsCommand(portfolioId, symbols, opti
142
277
  });
143
278
  writeMutationResponse(response.data, options.json, stdout, theme);
144
279
  }
145
- export function formatPortfolioListHuman(data, theme = createTerminalTheme(process.stdout)) {
280
+ export async function runHoldingHistoricalReturnCommand(portfolioId, options, context = {}) {
281
+ const stdout = context.io?.stdout ?? process.stdout;
282
+ const theme = createTerminalTheme(stdout, context.env ?? process.env);
283
+ const response = await invokeCliCapability({
284
+ capability: "holdings.historical-return",
285
+ input: {
286
+ portfolio_id: portfolioId,
287
+ start_date: options.from,
288
+ end_date: options.to,
289
+ },
290
+ env: context.env,
291
+ fetch: context.fetch,
292
+ });
293
+ if (options.json) {
294
+ stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
295
+ return;
296
+ }
297
+ stdout.write(`${formatHistoricalReturnHuman(response.data, theme)}\n`);
298
+ }
299
+ export async function runHoldingBetaCommand(portfolioId, options, context = {}) {
300
+ const stdout = context.io?.stdout ?? process.stdout;
301
+ const theme = createTerminalTheme(stdout, context.env ?? process.env);
302
+ const response = await invokeCliCapability({
303
+ capability: "holdings.beta",
304
+ input: {
305
+ portfolio_id: portfolioId,
306
+ years: parseYearsOption(options.years ?? "1"),
307
+ },
308
+ env: context.env,
309
+ fetch: context.fetch,
310
+ });
311
+ if (options.json) {
312
+ stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
313
+ return;
314
+ }
315
+ stdout.write(`${formatBetaHuman(response.data, theme)}\n`);
316
+ }
317
+ export async function runHoldingVarCommand(portfolioId, options, context = {}) {
318
+ const stdout = context.io?.stdout ?? process.stdout;
319
+ const theme = createTerminalTheme(stdout, context.env ?? process.env);
320
+ const response = await invokeCliCapability({
321
+ capability: "holdings.var",
322
+ input: {
323
+ portfolio_id: portfolioId,
324
+ years: parsePositiveIntegerOption(options.years ?? "1"),
325
+ confidence_pct: parseNumberOption(options.confidence ?? "95", "confidence"),
326
+ },
327
+ env: context.env,
328
+ fetch: context.fetch,
329
+ });
330
+ if (options.json) {
331
+ stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
332
+ return;
333
+ }
334
+ stdout.write(`${formatVarHuman(response.data, theme)}\n`);
335
+ }
336
+ export function formatPortfolioListHuman(data, theme = createTerminalTheme(process.stdout), title = "Saved items") {
146
337
  const lines = [
147
- theme.label("Portfolios"),
338
+ theme.label(title),
148
339
  "",
149
340
  `${theme.label("Total:")} ${formatInteger(data.total)}`,
150
341
  ];
151
342
  if (data.portfolios.length === 0) {
152
343
  lines.push("");
153
- lines.push("No saved portfolios.");
344
+ lines.push(`No ${title.toLowerCase()} saved.`);
154
345
  return lines.join("\n");
155
346
  }
156
347
  for (const portfolio of data.portfolios) {
@@ -163,17 +354,17 @@ export function formatPortfolioListHuman(data, theme = createTerminalTheme(proce
163
354
  }
164
355
  return lines.join("\n");
165
356
  }
166
- export function formatPortfolioGetHuman(data, theme = createTerminalTheme(process.stdout)) {
357
+ export function formatPortfolioGetHuman(data, theme = createTerminalTheme(process.stdout), title = "Saved item") {
167
358
  const { portfolio } = data;
168
359
  const lines = [
169
- theme.label("Portfolio"),
360
+ theme.label(title),
170
361
  "",
171
362
  `${theme.bold(String(portfolio.id))} ${theme.dim("·")} ${portfolio.name}`,
172
363
  `${theme.label("Assets:")} ${formatInteger(portfolio.symbols.length)}`,
173
364
  ];
174
365
  if (portfolio.symbols.length === 0) {
175
366
  lines.push("");
176
- lines.push("No assets in this portfolio.");
367
+ lines.push("No assets saved.");
177
368
  return lines.join("\n");
178
369
  }
179
370
  lines.push("");
@@ -193,21 +384,24 @@ export function formatPortfolioGetHuman(data, theme = createTerminalTheme(proces
193
384
  return lines.join("\n");
194
385
  }
195
386
  export function formatPortfolioMutationHuman(data, theme = createTerminalTheme(process.stdout)) {
387
+ const label = formatPortfolioKind(data.portfolio);
196
388
  const lines = [
197
389
  data.ok
198
- ? theme.success(theme.bold("Portfolio"))
199
- : theme.danger(theme.bold("Portfolio mutation failed")),
390
+ ? theme.success(theme.bold(label))
391
+ : theme.danger(theme.bold("Mutation failed")),
200
392
  "",
201
393
  data.summary_markdown,
202
394
  "",
203
- `${theme.bold(String(data.portfolio.id))} ${theme.dim("·")} ${data.portfolio.name || "Portfolio"}`,
395
+ `${theme.bold(String(data.portfolio.id))} ${theme.dim("·")} ${data.portfolio.name || label}`,
204
396
  `${theme.label("Assets:")} ${formatInteger(data.portfolio.symbols.length)}`,
205
397
  ];
206
398
  if (data.portfolio.symbols.length > 0) {
207
399
  lines.push("");
208
400
  lines.push(theme.label("Holdings"));
209
401
  for (const symbol of data.portfolio.symbols) {
210
- lines.push(` - ${symbol}`);
402
+ const weight = data.portfolio.weights?.[symbol];
403
+ const suffix = weight === undefined ? "" : `: ${formatNumber(weight)}%`;
404
+ lines.push(` - ${symbol}${suffix}`);
211
405
  }
212
406
  }
213
407
  return lines.join("\n");
@@ -215,24 +409,69 @@ export function formatPortfolioMutationHuman(data, theme = createTerminalTheme(p
215
409
  function parsePortfolioIdArgument(value) {
216
410
  const normalized = value.trim();
217
411
  if (!/^\d+$/.test(normalized)) {
218
- throw createCliValidationError("Portfolio id must be a positive integer.");
412
+ throw createCliValidationError("Id must be a positive integer.");
219
413
  }
220
414
  const portfolioId = Number(normalized);
221
415
  if (!Number.isSafeInteger(portfolioId) || portfolioId <= 0) {
222
- throw createCliValidationError("Portfolio id must be a positive integer.");
416
+ throw createCliValidationError("Id must be a positive integer.");
223
417
  }
224
418
  return portfolioId;
225
419
  }
226
420
  function parsePortfolioNameArgument(value) {
227
421
  const normalized = value.trim();
228
422
  if (!normalized) {
229
- throw createCliValidationError("Portfolio name cannot be empty.");
423
+ throw createCliValidationError("Name cannot be empty.");
230
424
  }
231
425
  if (normalized.length > 100) {
232
- throw createCliValidationError("Portfolio name cannot exceed 100 characters.");
426
+ throw createCliValidationError("Name cannot exceed 100 characters.");
233
427
  }
234
428
  return normalized;
235
429
  }
430
+ function parseHoldingModeOption(value) {
431
+ const normalized = value.trim().toLowerCase();
432
+ if (normalized === "target" || normalized === "target-weight") {
433
+ return "TARGET_WEIGHT";
434
+ }
435
+ if (normalized === "position" || normalized === "positions") {
436
+ return "POSITION";
437
+ }
438
+ throw createCliValidationError("Holding mode must be target or position.");
439
+ }
440
+ function collectStringOption(value, previous) {
441
+ previous.push(value);
442
+ return previous;
443
+ }
444
+ function parseTargetArguments(values) {
445
+ if (values.length === 0) {
446
+ return [];
447
+ }
448
+ const targets = values.map(parseTargetArgument);
449
+ const tickers = targets.map(target => target.ticker);
450
+ if (new Set(tickers).size !== tickers.length) {
451
+ throw createCliValidationError("Do not send duplicated tickers.");
452
+ }
453
+ return targets;
454
+ }
455
+ function parseTargetArgument(value) {
456
+ const normalized = value.trim();
457
+ if (!normalized) {
458
+ throw createCliValidationError("Target input cannot be empty.");
459
+ }
460
+ const parts = normalized.split(":");
461
+ if (parts.length !== 2) {
462
+ throw createCliValidationError(`Invalid target input "${value}". Use TICKER:WEIGHT_PCT.`);
463
+ }
464
+ const [rawTicker, rawWeight] = parts;
465
+ const ticker = rawTicker?.trim().toUpperCase();
466
+ if (!ticker) {
467
+ throw createCliValidationError(`Invalid target input "${value}". Use TICKER:WEIGHT_PCT.`);
468
+ }
469
+ const weight_pct = Number(rawWeight?.trim());
470
+ if (!Number.isFinite(weight_pct) || weight_pct < 0 || weight_pct > 100) {
471
+ throw createCliValidationError(`Invalid target weight in "${value}". Use a number from 0 to 100.`);
472
+ }
473
+ return { ticker, weight_pct };
474
+ }
236
475
  function parseSymbolArguments(values) {
237
476
  const symbols = values.map(symbol => symbol.trim().toUpperCase());
238
477
  if (symbols.length === 0 || symbols.some(symbol => symbol.length === 0)) {
@@ -252,6 +491,31 @@ function dedupeSymbols(symbols) {
252
491
  }
253
492
  return deduped;
254
493
  }
494
+ function parsePositiveIntegerOption(value) {
495
+ const normalized = value.trim();
496
+ if (!/^\d+$/.test(normalized)) {
497
+ throw createCliValidationError("Value must be a positive integer.");
498
+ }
499
+ const parsed = Number(normalized);
500
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
501
+ throw createCliValidationError("Value must be a positive integer.");
502
+ }
503
+ return parsed;
504
+ }
505
+ function parseYearsOption(value) {
506
+ const parsed = parsePositiveIntegerOption(value);
507
+ if (![1, 3, 5].includes(parsed)) {
508
+ throw createCliValidationError("Years must be one of: 1, 3, 5.");
509
+ }
510
+ return parsed;
511
+ }
512
+ function parseNumberOption(value, fieldName) {
513
+ const parsed = Number(value.trim());
514
+ if (!Number.isFinite(parsed)) {
515
+ throw createCliValidationError(`${fieldName} must be a number.`);
516
+ }
517
+ return parsed;
518
+ }
255
519
  function writeMutationResponse(data, json, stdout, theme) {
256
520
  if (!data.ok) {
257
521
  if (!json) {
@@ -283,9 +547,124 @@ function formatWeightsInline(portfolio) {
283
547
  })
284
548
  .join(", ");
285
549
  }
550
+ function formatPortfolioKind(portfolio) {
551
+ if (portfolio.kind === "WATCHLIST") {
552
+ return "Watchlist";
553
+ }
554
+ if (portfolio.kind === "HOLDING") {
555
+ return "Holding";
556
+ }
557
+ return "Saved item";
558
+ }
286
559
  function createCountSuffix(count) {
287
560
  return count === 1 ? "(1 asset)" : `(${count} assets)`;
288
561
  }
562
+ export function formatHistoricalReturnHuman(data, theme = createTerminalTheme(process.stdout)) {
563
+ const lines = [
564
+ theme.label("Historical return"),
565
+ "",
566
+ formatSourceLabel(data, theme),
567
+ `${theme.label("Period:")} ${data.effective_start_date} -> ${data.effective_end_date}`,
568
+ `${theme.label("Requested:")} ${data.requested_start_date} -> ${data.requested_end_date}`,
569
+ "",
570
+ `${theme.label("Holding return:")} ${formatPercentValue(data.total_return)}`,
571
+ `${theme.label("Annualized return:")} ${formatPercentValue(data.annualized_return)}`,
572
+ `${theme.label("Max drawdown:")} ${formatPercentValue(data.max_drawdown)}`,
573
+ `${theme.label("Daily volatility:")} ${formatPercentValue(data.daily_volatility)}`,
574
+ `${theme.label("Annualized volatility:")} ${formatPercentValue(data.annualized_volatility)}`,
575
+ `${theme.label("Sharpe ratio:")} ${formatNumber(data.sharpe_ratio)}`,
576
+ "",
577
+ theme.label("Benchmarks"),
578
+ ` IBOV: ${formatPercentValue(data.ibov_return)}`,
579
+ ` CDI: ${formatPercentValue(data.cdi_return)}`,
580
+ ` IPCA: ${formatPercentValue(data.ipca_return)}`,
581
+ "",
582
+ theme.label("Holdings"),
583
+ ...data.holdings.map(item => ` - ${item.ticker}: weight=${formatPercentDirect(item.weight_pct)}, return=${formatPercentValue(item.total_return)}, contribution=${formatPercentValue(item.contribution)}`),
584
+ ];
585
+ pushNotes(lines, "Assumptions", data.assumptions, theme);
586
+ pushNotes(lines, "Warnings", data.warnings, theme);
587
+ return lines.join("\n");
588
+ }
589
+ export function formatBetaHuman(data, theme = createTerminalTheme(process.stdout)) {
590
+ const lines = [
591
+ theme.label("Holding beta"),
592
+ "",
593
+ formatSourceLabel(data, theme),
594
+ `${theme.label("Benchmark:")} ${data.benchmark}`,
595
+ `${theme.label("Lookback:")} ${data.lookback_years}Y`,
596
+ "",
597
+ `${theme.label("Beta:")} ${formatNumber(data.beta)}`,
598
+ `${theme.label("Correlation:")} ${formatNumber(data.correlation)}`,
599
+ `${theme.label("Daily volatility:")} ${formatPercentValue(data.daily_volatility)}`,
600
+ `${theme.label("Annualized volatility:")} ${formatPercentValue(data.annualized_volatility)}`,
601
+ `${theme.label("Long exposure:")} ${formatPercentDirect(data.long_exposure_pct)}`,
602
+ `${theme.label("Short exposure:")} ${formatPercentDirect(data.short_exposure_pct)}`,
603
+ `${theme.label("Net exposure:")} ${formatPercentDirect(data.total_weight_pct)}`,
604
+ "",
605
+ theme.label("Holdings"),
606
+ ...data.holdings.map(item => ` - ${item.ticker}: weight=${formatPercentDirect(item.weight_pct)}, beta=${formatNullableNumber(item.beta)}, weighted=${formatNullableNumber(item.weighted_beta)}, corr=${formatNullableNumber(item.correlation)}`),
607
+ ];
608
+ pushNotes(lines, "Assumptions", data.assumptions, theme);
609
+ pushNotes(lines, "Warnings", data.warnings, theme);
610
+ return lines.join("\n");
611
+ }
612
+ export function formatVarHuman(data, theme = createTerminalTheme(process.stdout)) {
613
+ const lines = [
614
+ theme.label("Holding VaR"),
615
+ "",
616
+ formatSourceLabel(data, theme),
617
+ `${theme.label("Lookback:")} ${data.lookback_years}Y`,
618
+ `${theme.label("Confidence:")} ${formatPercentDirect(data.confidence_pct)}`,
619
+ `${theme.label("Horizon:")} ${data.time_horizon}`,
620
+ "",
621
+ `${theme.label("VaR:")} ${formatPercentValue(data.var)}`,
622
+ `${theme.label("Long exposure:")} ${formatPercentDirect(data.long_exposure_pct)}`,
623
+ `${theme.label("Short exposure:")} ${formatPercentDirect(data.short_exposure_pct)}`,
624
+ `${theme.label("Net exposure:")} ${formatPercentDirect(data.total_weight_pct)}`,
625
+ "",
626
+ theme.label("Holdings"),
627
+ ...data.holdings.map(item => ` - ${item.ticker}: weight=${formatPercentDirect(item.weight_pct)}`),
628
+ "",
629
+ `${theme.label("Histogram bins:")} ${formatInteger(data.histogram_data.length)}`,
630
+ ];
631
+ pushNotes(lines, "Assumptions", data.assumptions, theme);
632
+ pushNotes(lines, "Warnings", data.warnings, theme);
633
+ return lines.join("\n");
634
+ }
635
+ function formatSourceLabel(data, theme) {
636
+ const id = data.portfolio_id === null ? "n/a" : String(data.portfolio_id);
637
+ const name = data.portfolio_name ? ` · ${data.portfolio_name}` : "";
638
+ return `${theme.bold(id)}${theme.dim(name)}`;
639
+ }
640
+ function pushNotes(lines, title, items, theme) {
641
+ if (items.length === 0) {
642
+ return;
643
+ }
644
+ lines.push("");
645
+ lines.push(theme.label(title));
646
+ for (const item of items) {
647
+ lines.push(` - ${item}`);
648
+ }
649
+ }
650
+ function formatNullableNumber(value) {
651
+ if (value === null) {
652
+ return "n/a";
653
+ }
654
+ return formatNumber(value);
655
+ }
656
+ function formatPercentValue(value) {
657
+ return `${new Intl.NumberFormat("en-US", {
658
+ maximumFractionDigits: 2,
659
+ minimumFractionDigits: 2,
660
+ }).format(value * 100)}%`;
661
+ }
662
+ function formatPercentDirect(value) {
663
+ return `${new Intl.NumberFormat("en-US", {
664
+ maximumFractionDigits: 2,
665
+ minimumFractionDigits: 2,
666
+ }).format(value)}%`;
667
+ }
289
668
  function formatInteger(value) {
290
669
  return new Intl.NumberFormat("en-US", {
291
670
  maximumFractionDigits: 0,
package/dist/index.d.ts CHANGED
@@ -2,7 +2,6 @@ export * from "./cli/auth.js";
2
2
  export * from "./cli/client.js";
3
3
  export * from "./cli/errors.js";
4
4
  export * from "./cli/index.js";
5
- export * from "./commands/analytics.js";
6
5
  export * from "./commands/assets.js";
7
6
  export * from "./commands/auth.js";
8
7
  export * from "./commands/capabilities.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC"}