@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.
- package/README.md +41 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +20 -1
- package/dist/cli/prompt.d.ts +1 -0
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +17 -0
- package/dist/cli/skills.d.ts +9 -0
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +68 -4
- package/dist/commands/screening.d.ts +120 -0
- package/dist/commands/screening.d.ts.map +1 -0
- package/dist/commands/screening.js +361 -0
- package/dist/commands/skills.js +7 -7
- package/dist/commands/update.d.ts +23 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +209 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/vendor/core/capabilities/index.d.ts +1 -0
- package/dist/vendor/core/capabilities/index.d.ts.map +1 -1
- package/dist/vendor/core/capabilities/index.js +1 -0
- package/dist/vendor/core/capabilities/registry.d.ts +266 -0
- package/dist/vendor/core/capabilities/registry.d.ts.map +1 -1
- package/dist/vendor/core/capabilities/registry.js +2 -0
- package/dist/vendor/core/capabilities/screening.d.ts +136 -0
- package/dist/vendor/core/capabilities/screening.d.ts.map +1 -0
- package/dist/vendor/core/capabilities/screening.js +155 -0
- package/dist/vendor/core/capabilities/types.d.ts +1 -1
- package/dist/vendor/core/capabilities/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/skills/quantbrasil/SKILL.md +16 -11
- package/skills/quantbrasil/references/cli.md +53 -0
- package/skills/quantbrasil/references/costs.md +6 -1
- package/skills/quantbrasil/references/portfolios.md +13 -2
- package/skills/quantbrasil/references/quality-eval-queries.json +127 -0
- package/skills/quantbrasil/references/screening.md +212 -0
- package/skills/quantbrasil/references/unsupported.md +2 -0
- 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
|
+
}
|
package/dist/commands/skills.js
CHANGED
|
@@ -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("
|
|
7
|
+
.description("Gerencia skills públicas empacotadas");
|
|
8
8
|
skillsCommand
|
|
9
9
|
.command("install")
|
|
10
|
-
.description("
|
|
11
|
-
.option("--all", "
|
|
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
|
|
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("
|
|
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("
|
|
32
|
+
lines.push(theme.label("Ignorados"));
|
|
33
33
|
for (const target of result.skipped) {
|
|
34
|
-
lines.push(`${theme.dim(`${target.label}
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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";
|
|
@@ -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"}
|