@quantbrasil/cli 0.1.0-beta.6 → 0.1.0-beta.8

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 (40) hide show
  1. package/README.md +41 -0
  2. package/dist/cli/index.d.ts +6 -0
  3. package/dist/cli/index.d.ts.map +1 -1
  4. package/dist/cli/index.js +20 -1
  5. package/dist/cli/prompt.d.ts +1 -0
  6. package/dist/cli/prompt.d.ts.map +1 -1
  7. package/dist/cli/prompt.js +17 -0
  8. package/dist/cli/skills.d.ts +9 -0
  9. package/dist/cli/skills.d.ts.map +1 -1
  10. package/dist/cli/skills.js +68 -4
  11. package/dist/commands/screening.d.ts +120 -0
  12. package/dist/commands/screening.d.ts.map +1 -0
  13. package/dist/commands/screening.js +361 -0
  14. package/dist/commands/skills.js +7 -7
  15. package/dist/commands/update.d.ts +23 -0
  16. package/dist/commands/update.d.ts.map +1 -0
  17. package/dist/commands/update.js +209 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +2 -0
  21. package/dist/vendor/core/capabilities/index.d.ts +1 -0
  22. package/dist/vendor/core/capabilities/index.d.ts.map +1 -1
  23. package/dist/vendor/core/capabilities/index.js +1 -0
  24. package/dist/vendor/core/capabilities/registry.d.ts +266 -0
  25. package/dist/vendor/core/capabilities/registry.d.ts.map +1 -1
  26. package/dist/vendor/core/capabilities/registry.js +2 -0
  27. package/dist/vendor/core/capabilities/screening.d.ts +136 -0
  28. package/dist/vendor/core/capabilities/screening.d.ts.map +1 -0
  29. package/dist/vendor/core/capabilities/screening.js +155 -0
  30. package/dist/vendor/core/capabilities/types.d.ts +1 -1
  31. package/dist/vendor/core/capabilities/types.d.ts.map +1 -1
  32. package/package.json +3 -3
  33. package/skills/quantbrasil/SKILL.md +16 -11
  34. package/skills/quantbrasil/references/cli.md +53 -0
  35. package/skills/quantbrasil/references/costs.md +6 -1
  36. package/skills/quantbrasil/references/portfolios.md +13 -2
  37. package/skills/quantbrasil/references/quality-eval-queries.json +127 -0
  38. package/skills/quantbrasil/references/screening.md +212 -0
  39. package/skills/quantbrasil/references/unsupported.md +2 -0
  40. package/skills/quantbrasil/references/workflows.md +24 -0
@@ -0,0 +1,361 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { screeningSortValues } from "../vendor/core/capabilities/index.js";
3
+ import { invokeCliCapability } from "../cli/client.js";
4
+ import { createCliValidationError } from "../cli/errors.js";
5
+ import { createTerminalTheme } from "../cli/terminal.js";
6
+ export function registerScreeningCommands(program, context = {}) {
7
+ const screeningCommand = program
8
+ .command("screening")
9
+ .description("Executa screening de indicadores em universos salvos");
10
+ screeningCommand
11
+ .command("universes")
12
+ .description("Lista universos salvos disponíveis para screening de indicadores")
13
+ .option("--json", "Exibe saída JSON")
14
+ .action(async (options) => {
15
+ await runScreeningUniversesCommand(options, context);
16
+ });
17
+ screeningCommand
18
+ .command("indicators")
19
+ .description("Lista indicadores disponíveis para screening")
20
+ .option("--json", "Exibe saída JSON")
21
+ .action(async (options) => {
22
+ await runScreeningIndicatorsCommand(options, context);
23
+ });
24
+ screeningCommand
25
+ .command("run")
26
+ .description("Executa screening de indicadores a partir de um ScreenerRequest JSON")
27
+ .option("--system <slug>", "Slug da carteira do sistema")
28
+ .option("--watchlist <id>", "ID da watchlist")
29
+ .option("--holding <id>", "ID da carteira")
30
+ .requiredOption("--query-file <path>", "Caminho para um ScreenerRequest JSON completo, ou - para stdin")
31
+ .option("--limit <limit>", "Sobrescreve o limit de topo do ScreenerRequest")
32
+ .option("--sort <sort>", "Sobrescreve o sort de topo do ScreenerRequest")
33
+ .option("--json", "Exibe saída JSON")
34
+ .action(async (options) => {
35
+ await runScreeningRunCommand(options, context);
36
+ });
37
+ }
38
+ export async function runScreeningUniversesCommand(options, context = {}) {
39
+ const stdout = context.io?.stdout ?? process.stdout;
40
+ const theme = createTerminalTheme(stdout, context.env ?? process.env);
41
+ const response = await invokeCliCapability({
42
+ capability: "screening.universes",
43
+ env: context.env,
44
+ fetch: context.fetch,
45
+ });
46
+ if (options.json) {
47
+ stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
48
+ return;
49
+ }
50
+ stdout.write(`${formatScreeningUniversesHuman(response.data, theme)}\n`);
51
+ }
52
+ export async function runScreeningIndicatorsCommand(options, context = {}) {
53
+ const stdout = context.io?.stdout ?? process.stdout;
54
+ const theme = createTerminalTheme(stdout, context.env ?? process.env);
55
+ const response = await invokeCliCapability({
56
+ capability: "screening.indicators",
57
+ env: context.env,
58
+ fetch: context.fetch,
59
+ });
60
+ if (options.json) {
61
+ stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
62
+ return;
63
+ }
64
+ stdout.write(`${formatScreeningIndicatorsHuman(response.data, theme)}\n`);
65
+ }
66
+ export async function runScreeningRunCommand(options, context = {}) {
67
+ const stdout = context.io?.stdout ?? process.stdout;
68
+ const theme = createTerminalTheme(stdout, context.env ?? process.env);
69
+ const input = await buildScreeningRunInput(options);
70
+ const response = await invokeCliCapability({
71
+ capability: "screening.run",
72
+ input,
73
+ env: context.env,
74
+ fetch: context.fetch,
75
+ });
76
+ if (options.json) {
77
+ stdout.write(`${JSON.stringify(response.data, null, 2)}\n`);
78
+ return;
79
+ }
80
+ stdout.write(`${formatScreeningRunHuman(response.data, theme)}\n`);
81
+ }
82
+ export async function buildScreeningRunInput(options) {
83
+ const selector = parseScreeningSelector(options);
84
+ const queryFile = normalizeRequiredPath(options.queryFile);
85
+ const request = await readScreenerRequest(queryFile);
86
+ if (options.limit !== undefined) {
87
+ request.limit = parseLimitOption(options.limit);
88
+ }
89
+ if (options.sort !== undefined) {
90
+ request.sort = parseSortOption(options.sort);
91
+ }
92
+ return {
93
+ ...selector,
94
+ request,
95
+ };
96
+ }
97
+ export function formatScreeningUniversesHuman(data, theme = createTerminalTheme(process.stdout)) {
98
+ const lines = [
99
+ theme.label("Universos de screening"),
100
+ "",
101
+ `${theme.label("Total:")} ${formatInteger(data.total)}`,
102
+ ];
103
+ appendUniverseGroup(lines, theme, "Carteiras do sistema", data.system);
104
+ appendUniverseGroup(lines, theme, "Watchlists", data.watchlists);
105
+ appendUniverseGroup(lines, theme, "Carteiras", data.holdings);
106
+ return lines.join("\n");
107
+ }
108
+ export function formatScreeningIndicatorsHuman(data, theme = createTerminalTheme(process.stdout)) {
109
+ const lines = [
110
+ theme.label("Indicadores de screening"),
111
+ "",
112
+ `${theme.label("Total:")} ${formatInteger(data.indicators.length)}`,
113
+ `${theme.label("Operadores:")} ${data.operators.join(", ")}`,
114
+ ];
115
+ for (const indicator of data.indicators) {
116
+ lines.push("");
117
+ lines.push(`${theme.bold(indicator.name)} ${theme.dim("-")} ${indicator.display_name}`);
118
+ lines.push(` ${indicator.description}`);
119
+ lines.push(` ${theme.label("Timeframes:")} ${indicator.timeframes.join(", ")}`);
120
+ if (indicator.params.length > 0) {
121
+ lines.push(` ${theme.label("Parâmetros:")}`);
122
+ for (const param of indicator.params) {
123
+ lines.push(` ${formatIndicatorParam(param)}`);
124
+ }
125
+ }
126
+ if (indicator.examples.length > 0) {
127
+ lines.push(` ${theme.label("Exemplos:")} ${indicator.examples
128
+ .map(example => example.label)
129
+ .join(", ")}`);
130
+ }
131
+ }
132
+ return lines.join("\n");
133
+ }
134
+ export function formatScreeningRunHuman(data, theme = createTerminalTheme(process.stdout)) {
135
+ const lines = [
136
+ theme.label("Screening de indicadores"),
137
+ "",
138
+ `${theme.bold(data.universe.name)} ${theme.dim(`- ${formatUniverseKind(data.universe.kind)}`)}`,
139
+ `${theme.label("Seletor:")} ${formatUniverseSelector(data.universe)}`,
140
+ `${theme.label("Ativos encontrados:")} ${formatInteger(data.result.count)}`,
141
+ ];
142
+ if (data.summary_markdown.trim()) {
143
+ lines.push("");
144
+ lines.push(data.summary_markdown);
145
+ }
146
+ if (data.result.results.length === 0) {
147
+ lines.push("");
148
+ lines.push("Nenhum ativo encontrado.");
149
+ return lines.join("\n");
150
+ }
151
+ const indicatorKeys = collectIndicatorKeys(data.result.results);
152
+ const visibleIndicatorKeys = indicatorKeys.slice(0, 4);
153
+ lines.push("");
154
+ lines.push(formatResultTable(data.result.results, visibleIndicatorKeys, theme));
155
+ if (indicatorKeys.length > visibleIndicatorKeys.length) {
156
+ lines.push("");
157
+ lines.push("Use --json para inspecionar todas as colunas de indicadores.");
158
+ }
159
+ return lines.join("\n");
160
+ }
161
+ function parseScreeningSelector(options) {
162
+ const selectorCount = [
163
+ options.system,
164
+ options.watchlist,
165
+ options.holding,
166
+ ].filter(value => value !== undefined).length;
167
+ if (selectorCount !== 1) {
168
+ throw createCliValidationError("Use exatamente um seletor: --system, --watchlist ou --holding.");
169
+ }
170
+ if (options.system !== undefined) {
171
+ const systemSlug = options.system.trim().toLowerCase();
172
+ if (!systemSlug) {
173
+ throw createCliValidationError("O slug da carteira do sistema é obrigatório.");
174
+ }
175
+ return { system_slug: systemSlug };
176
+ }
177
+ if (options.watchlist !== undefined) {
178
+ return {
179
+ watchlist_id: parsePositiveInteger(options.watchlist, "ID da watchlist"),
180
+ };
181
+ }
182
+ return {
183
+ holding_id: parsePositiveInteger(String(options.holding), "ID da carteira"),
184
+ };
185
+ }
186
+ function normalizeRequiredPath(value) {
187
+ const normalized = value?.trim();
188
+ if (!normalized) {
189
+ throw createCliValidationError("Use --query-file com um caminho JSON ou -.");
190
+ }
191
+ return normalized;
192
+ }
193
+ async function readScreenerRequest(path) {
194
+ let text;
195
+ try {
196
+ text = path === "-" ? await readStdinText() : await readFile(path, "utf8");
197
+ }
198
+ catch (error) {
199
+ const message = error instanceof Error ? error.message : String(error);
200
+ throw createCliValidationError(`Não foi possível ler o JSON da consulta: ${message}`);
201
+ }
202
+ let parsed;
203
+ try {
204
+ parsed = JSON.parse(text);
205
+ }
206
+ catch (error) {
207
+ const message = error instanceof Error ? error.message : String(error);
208
+ throw createCliValidationError(`Não foi possível interpretar o JSON da consulta: ${message}`);
209
+ }
210
+ if (!isJsonRecord(parsed)) {
211
+ throw createCliValidationError("O JSON da consulta deve ser um objeto.");
212
+ }
213
+ return parsed;
214
+ }
215
+ async function readStdinText() {
216
+ const chunks = [];
217
+ for await (const chunk of process.stdin) {
218
+ chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
219
+ }
220
+ return chunks.join("");
221
+ }
222
+ function parseLimitOption(value) {
223
+ const parsed = parsePositiveInteger(value, "limite");
224
+ if (parsed > 500) {
225
+ throw createCliValidationError("O limite deve ser um inteiro entre 1 e 500.");
226
+ }
227
+ return parsed;
228
+ }
229
+ function parsePositiveInteger(value, fieldName) {
230
+ const normalized = value.trim();
231
+ if (!/^\d+$/.test(normalized)) {
232
+ throw createCliValidationError(`O valor de ${fieldName} deve ser um inteiro positivo.`);
233
+ }
234
+ const parsed = Number(normalized);
235
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
236
+ throw createCliValidationError(`O valor de ${fieldName} deve ser um inteiro positivo.`);
237
+ }
238
+ return parsed;
239
+ }
240
+ function parseSortOption(value) {
241
+ const normalized = value.trim().toLowerCase();
242
+ if (!screeningSortValues.includes(normalized)) {
243
+ throw createCliValidationError("A ordenação deve ser uma destas opções: price, ticker.");
244
+ }
245
+ return normalized;
246
+ }
247
+ function appendUniverseGroup(lines, theme, title, universes) {
248
+ lines.push("");
249
+ lines.push(`${theme.label(title)} ${theme.dim(`(${formatInteger(universes.length)})`)}`);
250
+ if (universes.length === 0) {
251
+ lines.push(" nenhum item");
252
+ return;
253
+ }
254
+ for (const universe of universes) {
255
+ lines.push(` ${theme.bold(formatUniverseSelector(universe))} ${theme.dim("-")} ${universe.name}`);
256
+ lines.push(` ${theme.label("Ativos:")} ${formatInteger(universe.symbol_count)}`);
257
+ }
258
+ }
259
+ function formatUniverseSelector(universe) {
260
+ if (universe.kind === "SYSTEM") {
261
+ return `--system ${universe.slug ?? universe.id}`;
262
+ }
263
+ if (universe.kind === "WATCHLIST") {
264
+ return `--watchlist ${universe.id}`;
265
+ }
266
+ return `--holding ${universe.id}`;
267
+ }
268
+ function formatUniverseKind(kind) {
269
+ if (kind === "SYSTEM") {
270
+ return "Carteira do sistema";
271
+ }
272
+ if (kind === "WATCHLIST") {
273
+ return "Watchlist";
274
+ }
275
+ return "Carteira";
276
+ }
277
+ function formatIndicatorParam(param) {
278
+ const details = [param.type];
279
+ if (param.required) {
280
+ details.push("obrigatório");
281
+ }
282
+ if (param.default !== null) {
283
+ details.push(`padrão ${formatScalar(param.default)}`);
284
+ }
285
+ if (param.enum_values !== null && param.enum_values.length > 0) {
286
+ details.push(`opções ${param.enum_values.join("|")}`);
287
+ }
288
+ return `${param.name}: ${details.join(", ")} - ${param.description}`;
289
+ }
290
+ function formatScalar(value) {
291
+ return typeof value === "string" ? JSON.stringify(value) : String(value);
292
+ }
293
+ function collectIndicatorKeys(results) {
294
+ const keys = [];
295
+ const seen = new Set();
296
+ for (const result of results) {
297
+ for (const key of Object.keys(result.indicators)) {
298
+ if (seen.has(key)) {
299
+ continue;
300
+ }
301
+ seen.add(key);
302
+ keys.push(key);
303
+ }
304
+ }
305
+ return keys;
306
+ }
307
+ function formatResultTable(results, indicatorKeys, theme) {
308
+ const headers = [
309
+ "Ativo",
310
+ "Preço",
311
+ ...indicatorKeys.map(key => truncate(results[0]?.indicators[key]?.display_name ?? key, 18)),
312
+ ];
313
+ const rows = results.map(result => [
314
+ result.ticker,
315
+ formatNumber(result.price),
316
+ ...indicatorKeys.map(key => formatIndicatorValue(result.indicators[key])),
317
+ ]);
318
+ const widths = headers.map((header, index) => Math.max(header.length, ...rows.map(row => row[index]?.length ?? 0)));
319
+ const lines = [
320
+ headers
321
+ .map((header, index) => header.padEnd(widths[index] ?? 0))
322
+ .join(" "),
323
+ widths.map(width => "-".repeat(width)).join(" "),
324
+ ];
325
+ for (const row of rows) {
326
+ lines.push(row.map((value, index) => value.padEnd(widths[index] ?? 0)).join(" "));
327
+ }
328
+ return lines
329
+ .map((line, index) => (index === 0 ? theme.label(line) : line))
330
+ .join("\n");
331
+ }
332
+ function formatIndicatorValue(indicator) {
333
+ if (!indicator) {
334
+ return "n/d";
335
+ }
336
+ const formatted = formatNumber(indicator.value);
337
+ if (!indicator.unit) {
338
+ return formatted;
339
+ }
340
+ return indicator.unit === "%"
341
+ ? `${formatted}%`
342
+ : `${formatted} ${indicator.unit}`;
343
+ }
344
+ function formatInteger(value) {
345
+ return new Intl.NumberFormat("en-US", {
346
+ maximumFractionDigits: 0,
347
+ }).format(value);
348
+ }
349
+ function formatNumber(value) {
350
+ return new Intl.NumberFormat("en-US", {
351
+ maximumFractionDigits: 4,
352
+ }).format(value);
353
+ }
354
+ function truncate(value, maxLength) {
355
+ return value.length <= maxLength
356
+ ? value
357
+ : `${value.slice(0, Math.max(0, maxLength - 3))}...`;
358
+ }
359
+ function isJsonRecord(value) {
360
+ return typeof value === "object" && value !== null && !Array.isArray(value);
361
+ }
@@ -4,18 +4,18 @@ import { createTerminalTheme } from "../cli/terminal.js";
4
4
  export function registerSkillsCommands(program, context = {}) {
5
5
  const skillsCommand = program
6
6
  .command("skills")
7
- .description("Manage bundled public skills");
7
+ .description("Gerencia skills públicas empacotadas");
8
8
  skillsCommand
9
9
  .command("install")
10
- .description("Install bundled QuantBrasil skills into detected agent clients")
11
- .option("--all", "Install all bundled public QuantBrasil skills")
10
+ .description("Instala a skill pública do QuantBrasil nos clientes de agente detectados")
11
+ .option("--all", "Instala todas as skills públicas empacotadas")
12
12
  .action(async (options) => {
13
13
  await runSkillsInstallCommand(options, context);
14
14
  });
15
15
  }
16
16
  export async function runSkillsInstallCommand(options, context = {}) {
17
17
  if (!options.all) {
18
- throw createCliValidationError("Use --all to install the bundled QuantBrasil skill into detected agent clients.");
18
+ throw createCliValidationError("Use --all para instalar a skill QuantBrasil nos clientes de agente detectados.");
19
19
  }
20
20
  const stdout = context.io?.stdout ?? process.stdout;
21
21
  const theme = createTerminalTheme(stdout, context.env ?? process.env);
@@ -23,15 +23,15 @@ export async function runSkillsInstallCommand(options, context = {}) {
23
23
  stdout.write(`${formatSkillsInstallResult(result, theme)}\n`);
24
24
  }
25
25
  export function formatSkillsInstallResult(result, theme = createTerminalTheme(process.stdout)) {
26
- const lines = [theme.label("Installed QuantBrasil skill"), ""];
26
+ const lines = [theme.label("Skill QuantBrasil instalada"), ""];
27
27
  for (const target of result.installed) {
28
28
  lines.push(`${theme.success(target.label)} ${theme.dim(`→ ${target.installDir}`)}`);
29
29
  }
30
30
  if (result.skipped.length > 0) {
31
31
  lines.push("");
32
- lines.push(theme.label("Skipped"));
32
+ lines.push(theme.label("Ignorados"));
33
33
  for (const target of result.skipped) {
34
- lines.push(`${theme.dim(`${target.label} not detected at ${target.rootDir}`)}`);
34
+ lines.push(`${theme.dim(`${target.label} não detectado em ${target.rootDir}`)}`);
35
35
  }
36
36
  }
37
37
  return lines.join("\n");
@@ -0,0 +1,23 @@
1
+ import { Command } from "commander";
2
+ import { type TerminalWriter } from "../cli/terminal.js";
3
+ export interface UpdateCommandIO {
4
+ stdout: TerminalWriter;
5
+ }
6
+ export interface UpdateCommandOptions {
7
+ yes?: boolean;
8
+ }
9
+ export interface UpdateCommandResult {
10
+ stdout: string;
11
+ stderr: string;
12
+ }
13
+ export type UpdateCommandRunner = (command: string, args: readonly string[], env: NodeJS.ProcessEnv) => Promise<UpdateCommandResult>;
14
+ export interface UpdateCommandContext {
15
+ io?: UpdateCommandIO;
16
+ env?: NodeJS.ProcessEnv;
17
+ prompt?: (promptLabel: string) => Promise<string>;
18
+ commandRunner?: UpdateCommandRunner;
19
+ currentVersion?: string;
20
+ }
21
+ export declare function registerUpdateCommand(program: Command, context?: UpdateCommandContext): void;
22
+ export declare function runUpdateCommand(options: UpdateCommandOptions, context?: UpdateCommandContext): Promise<void>;
23
+ //# sourceMappingURL=update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAG9E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAElC,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,eAAe,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,oBAAyB,GACjC,IAAI,CAQN;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,oBAAoB,EAC7B,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CA2Cf"}
@@ -0,0 +1,209 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createCliConfigError } from "../cli/errors.js";
3
+ import { promptForConfirmation } from "../cli/prompt.js";
4
+ import { getBundledSkillInstallState, installBundledSkillEverywhere, } from "../cli/skills.js";
5
+ import { createTerminalTheme } from "../cli/terminal.js";
6
+ import { formatSkillsInstallResult } from "./skills.js";
7
+ const CLI_PACKAGE_SPEC = "@quantbrasil/cli@beta";
8
+ export function registerUpdateCommand(program, context = {}) {
9
+ program
10
+ .command("update")
11
+ .description("Atualiza o pacote global e a skill pública do QuantBrasil")
12
+ .option("--yes", "Atualiza sem confirmação interativa")
13
+ .action(async (options) => {
14
+ await runUpdateCommand(options, context);
15
+ });
16
+ }
17
+ export async function runUpdateCommand(options, context = {}) {
18
+ const env = context.env ?? process.env;
19
+ const stdout = context.io?.stdout ?? process.stdout;
20
+ const theme = createTerminalTheme(stdout, env);
21
+ const runner = context.commandRunner ?? runExternalCommand;
22
+ const currentVersion = context.currentVersion ?? "0.0.0";
23
+ const latestVersion = await getLatestBetaVersion(runner, env);
24
+ const cliNeedsUpdate = compareVersions(currentVersion, latestVersion) < 0;
25
+ const skillState = await getBundledSkillInstallState(env);
26
+ const skillNeedsUpdate = skillState.needsInstall;
27
+ if (!cliNeedsUpdate && !skillNeedsUpdate) {
28
+ stdout.write(`${theme.success("QuantBrasil CLI já está atualizado.")}\n`);
29
+ return;
30
+ }
31
+ stdout.write(`${formatUpdatePlan({
32
+ currentVersion,
33
+ latestVersion,
34
+ cliNeedsUpdate,
35
+ skillState,
36
+ }, theme)}\n`);
37
+ if (!(await shouldContinue(options, context))) {
38
+ stdout.write(`${theme.warning("Atualização cancelada.")}\n`);
39
+ return;
40
+ }
41
+ if (cliNeedsUpdate) {
42
+ await runner("npm", ["install", "-g", CLI_PACKAGE_SPEC], env);
43
+ await runner("quantbrasil", ["skills", "install", "--all"], env);
44
+ stdout.write(`${theme.success("Atualização concluída.")}\n`);
45
+ return;
46
+ }
47
+ const skillInstallResult = await installBundledSkillEverywhere(env);
48
+ stdout.write(`${formatSkillsInstallResult(skillInstallResult, theme)}\n`);
49
+ stdout.write(`${theme.success("Atualização concluída.")}\n`);
50
+ }
51
+ function formatUpdatePlan(data, theme) {
52
+ const lines = [
53
+ theme.label("Atualização do QuantBrasil CLI"),
54
+ "",
55
+ `${theme.label("Versão instalada:")} ${data.currentVersion}`,
56
+ `${theme.label("Versão beta publicada:")} ${data.latestVersion}`,
57
+ ];
58
+ const skillTargets = data.skillState.targets.filter(target => target.status === "missing" || target.status === "stale");
59
+ if (skillTargets.length > 0) {
60
+ lines.push(`${theme.label("Skills:")} ${skillTargets.length} alvo(s) precisam ser atualizados`);
61
+ for (const target of skillTargets) {
62
+ lines.push(` ${target.label}: ${formatSkillStatus(target.status)} ${theme.dim(`(${target.installDir})`)}`);
63
+ }
64
+ }
65
+ else {
66
+ lines.push(`${theme.label("Skills:")} já estão atualizadas`);
67
+ }
68
+ lines.push("");
69
+ lines.push(theme.label("Ações"));
70
+ if (data.cliNeedsUpdate) {
71
+ lines.push(` npm install -g ${CLI_PACKAGE_SPEC}`);
72
+ lines.push(" quantbrasil skills install --all");
73
+ }
74
+ else if (skillTargets.length > 0) {
75
+ lines.push(" quantbrasil skills install --all");
76
+ }
77
+ return lines.join("\n");
78
+ }
79
+ async function shouldContinue(options, context) {
80
+ if (options.yes) {
81
+ return true;
82
+ }
83
+ if (context.prompt) {
84
+ return parseConfirmationAnswer(await context.prompt("Atualizar agora? [s/N] "));
85
+ }
86
+ return promptForConfirmation("Atualizar agora? [s/N] ");
87
+ }
88
+ function formatSkillStatus(status) {
89
+ if (status === "missing") {
90
+ return "não instalada";
91
+ }
92
+ if (status === "stale") {
93
+ return "desatualizada";
94
+ }
95
+ return status;
96
+ }
97
+ function parseConfirmationAnswer(answer) {
98
+ return ["s", "sim", "y", "yes"].includes(answer.trim().toLowerCase());
99
+ }
100
+ async function getLatestBetaVersion(runner, env) {
101
+ const result = await runner("npm", ["view", CLI_PACKAGE_SPEC, "version"], env);
102
+ const version = result.stdout.trim();
103
+ if (!version) {
104
+ throw createCliConfigError(`Não foi possível resolver a versão publicada de ${CLI_PACKAGE_SPEC}.`);
105
+ }
106
+ return version;
107
+ }
108
+ function compareVersions(left, right) {
109
+ const parsedLeft = parseVersion(left);
110
+ const parsedRight = parseVersion(right);
111
+ for (let index = 0; index < 3; index += 1) {
112
+ const diff = (parsedLeft.main[index] ?? 0) - (parsedRight.main[index] ?? 0);
113
+ if (diff !== 0) {
114
+ return diff;
115
+ }
116
+ }
117
+ if (parsedLeft.prerelease.length === 0 &&
118
+ parsedRight.prerelease.length === 0) {
119
+ return 0;
120
+ }
121
+ if (parsedLeft.prerelease.length === 0) {
122
+ return 1;
123
+ }
124
+ if (parsedRight.prerelease.length === 0) {
125
+ return -1;
126
+ }
127
+ const length = Math.max(parsedLeft.prerelease.length, parsedRight.prerelease.length);
128
+ for (let index = 0; index < length; index += 1) {
129
+ const leftPart = parsedLeft.prerelease[index];
130
+ const rightPart = parsedRight.prerelease[index];
131
+ if (leftPart === undefined) {
132
+ return -1;
133
+ }
134
+ if (rightPart === undefined) {
135
+ return 1;
136
+ }
137
+ const diff = comparePrereleasePart(leftPart, rightPart);
138
+ if (diff !== 0) {
139
+ return diff;
140
+ }
141
+ }
142
+ return 0;
143
+ }
144
+ function parseVersion(version) {
145
+ const [mainVersion = "0.0.0", prerelease = ""] = version
146
+ .trim()
147
+ .replace(/^v/, "")
148
+ .split("-");
149
+ const mainParts = mainVersion.split(".").map(part => Number(part));
150
+ const main = [
151
+ normalizeVersionPart(mainParts[0]),
152
+ normalizeVersionPart(mainParts[1]),
153
+ normalizeVersionPart(mainParts[2]),
154
+ ];
155
+ return {
156
+ main,
157
+ prerelease: prerelease ? prerelease.split(".") : [],
158
+ };
159
+ }
160
+ function normalizeVersionPart(part) {
161
+ return Number.isFinite(part) ? Number(part) : 0;
162
+ }
163
+ function comparePrereleasePart(left, right) {
164
+ const leftNumber = Number(left);
165
+ const rightNumber = Number(right);
166
+ const leftIsNumeric = /^\d+$/.test(left);
167
+ const rightIsNumeric = /^\d+$/.test(right);
168
+ if (leftIsNumeric && rightIsNumeric) {
169
+ return leftNumber - rightNumber;
170
+ }
171
+ if (leftIsNumeric) {
172
+ return -1;
173
+ }
174
+ if (rightIsNumeric) {
175
+ return 1;
176
+ }
177
+ return left.localeCompare(right);
178
+ }
179
+ function runExternalCommand(command, args, env) {
180
+ return new Promise((resolve, reject) => {
181
+ const child = spawn(command, args, {
182
+ env,
183
+ stdio: ["ignore", "pipe", "pipe"],
184
+ });
185
+ let stdout = "";
186
+ let stderr = "";
187
+ child.stdout.on("data", chunk => {
188
+ stdout += chunk;
189
+ });
190
+ child.stderr.on("data", chunk => {
191
+ stderr += chunk;
192
+ });
193
+ child.on("error", error => {
194
+ reject(createCliConfigError(`Não foi possível executar ${command}: ${error.message}`));
195
+ });
196
+ child.on("close", code => {
197
+ if (code === 0) {
198
+ resolve({ stdout, stderr });
199
+ return;
200
+ }
201
+ reject(createCliConfigError([
202
+ `${command} ${args.join(" ")} falhou com exit ${code}.`,
203
+ stderr.trim() || stdout.trim(),
204
+ ]
205
+ .filter(Boolean)
206
+ .join("\n")));
207
+ });
208
+ });
209
+ }
package/dist/index.d.ts CHANGED
@@ -8,6 +8,8 @@ export * from "./commands/capabilities.js";
8
8
  export * from "./commands/init.js";
9
9
  export * from "./commands/market.js";
10
10
  export * from "./commands/portfolios.js";
11
+ export * from "./commands/screening.js";
11
12
  export * from "./commands/skills.js";
12
13
  export * from "./commands/status.js";
14
+ export * from "./commands/update.js";
13
15
  //# sourceMappingURL=index.d.ts.map
@@ -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,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,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -8,5 +8,7 @@ export * from "./commands/capabilities.js";
8
8
  export * from "./commands/init.js";
9
9
  export * from "./commands/market.js";
10
10
  export * from "./commands/portfolios.js";
11
+ export * from "./commands/screening.js";
11
12
  export * from "./commands/skills.js";
12
13
  export * from "./commands/status.js";
14
+ export * from "./commands/update.js";
@@ -2,6 +2,7 @@ export * from "./types.js";
2
2
  export * from "./shared.js";
3
3
  export * from "./market.js";
4
4
  export * from "./assets.js";
5
+ export * from "./screening.js";
5
6
  export * from "./portfolios.js";
6
7
  export * from "./registry.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/capabilities/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/capabilities/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC"}