sabia 1.1.0 → 1.1.1
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/package.json +1 -1
- package/sabia.mjs +94 -9
package/package.json
CHANGED
package/sabia.mjs
CHANGED
|
@@ -15,14 +15,73 @@ const VERSAO = JSON.parse(fs.readFileSync(new URL("./package.json", import.meta.
|
|
|
15
15
|
const REPO = "https://github.com/dxaraujo/sabia.git";
|
|
16
16
|
const INDICE = "https://github.com/dxaraujo/sabia-toolkits.git";
|
|
17
17
|
const ALVOS = ["kiro", "claude", "opencode"];
|
|
18
|
-
const
|
|
19
|
-
▄▄▀
|
|
20
|
-
███████╗ █████╗ ██████╗ ██╗ █████╗
|
|
21
|
-
██╔════╝██╔══██╗██╔══██╗██║██╔══██╗
|
|
22
|
-
███████╗███████║██████╔╝██║███████║
|
|
23
|
-
╚════██║██╔══██║██╔══██╗██║██╔══██║
|
|
24
|
-
███████║██║ ██║██████╔╝██║██║ ██║
|
|
25
|
-
╚══════╝╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝
|
|
18
|
+
const NOME = [
|
|
19
|
+
" ▄▄▀",
|
|
20
|
+
"███████╗ █████╗ ██████╗ ██╗ █████╗",
|
|
21
|
+
"██╔════╝██╔══██╗██╔══██╗██║██╔══██╗",
|
|
22
|
+
"███████╗███████║██████╔╝██║███████║",
|
|
23
|
+
"╚════██║██╔══██║██╔══██╗██║██╔══██║",
|
|
24
|
+
"███████║██║ ██║██████╔╝██║██║ ██║",
|
|
25
|
+
"╚══════╝╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═╝",
|
|
26
|
+
];
|
|
27
|
+
// o sabiá do sabia-site/logo.svg: as sete facetas (uma por etapa) e o bico, cinza para aparecer no fundo escuro
|
|
28
|
+
const SABIA = [
|
|
29
|
+
["#00BBCE", [56, 57, 70, 42, 90, 38, 104, 46, 106, 62, 84, 74, 57, 70]],
|
|
30
|
+
["#B2D235", [84, 74, 106, 62, 118, 74, 116, 92, 92, 94]],
|
|
31
|
+
["#0786E3", [57, 70, 84, 74, 92, 94, 86, 122, 70, 112, 60, 92]],
|
|
32
|
+
["#2C5168", [92, 94, 116, 92, 134, 108, 112, 124, 86, 122]],
|
|
33
|
+
["#76B043", [86, 122, 112, 124, 132, 152, 118, 170, 94, 146]],
|
|
34
|
+
["#FAA61A", [134, 108, 150, 128, 150, 154, 132, 152, 112, 124]],
|
|
35
|
+
["#0094AD", [132, 152, 150, 154, 180, 196, 164, 198]],
|
|
36
|
+
["#8A8F8C", [36, 64, 56, 57, 57, 70]],
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
function noSabia(x, y, p) {
|
|
40
|
+
let d = false;
|
|
41
|
+
for (let i = 0, j = p.length - 2; i < p.length; j = i, i += 2)
|
|
42
|
+
if (p[i + 1] > y !== p[j + 1] > y && x < ((p[j] - p[i]) * (y - p[i + 1])) / (p[j + 1] - p[i + 1]) + p[i]) d = !d;
|
|
43
|
+
return d;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** O sabiá em meio-blocos coloridos: cada caractere são dois pixels de `passo` unidades do SVG. */
|
|
47
|
+
function desenharSabia(cor, passo = 13) {
|
|
48
|
+
// pixel: a cor que mais aparece em 4x4 amostras; vazio se menos da metade cai no sabiá
|
|
49
|
+
const pixel = (cx, cy) => {
|
|
50
|
+
if (Math.hypot(cx - 82, cy - 54) < passo * 0.55) return "#0E1210"; // o olho
|
|
51
|
+
const n = {};
|
|
52
|
+
for (let i = 0; i < 16; i++) {
|
|
53
|
+
const x = cx + ((i % 4) + 0.5 - 2) * (passo / 4), y = cy + (Math.floor(i / 4) + 0.5 - 2) * (passo / 4);
|
|
54
|
+
const c = SABIA.find(([, p]) => noSabia(x, y, p))?.[0];
|
|
55
|
+
if (c) n[c] = (n[c] || 0) + 1;
|
|
56
|
+
}
|
|
57
|
+
const total = Object.values(n).reduce((a, b) => a + b, 0);
|
|
58
|
+
return total >= 8 ? Object.entries(n).sort((a, b) => b[1] - a[1])[0][0] : null;
|
|
59
|
+
};
|
|
60
|
+
const linhas = [];
|
|
61
|
+
for (let y = 36; y < 200; y += 2 * passo) {
|
|
62
|
+
let l = "";
|
|
63
|
+
for (let x = 34; x < 182; x += passo) {
|
|
64
|
+
const a = pixel(x + passo / 2, y + passo / 2), b = pixel(x + passo / 2, y + 1.5 * passo);
|
|
65
|
+
l += !a && !b ? " " : !b ? cor(a) + "▀\x1b[0m" : !a ? cor(b) + "▄\x1b[0m" : cor(a) + cor(b, true) + "▀\x1b[0m";
|
|
66
|
+
}
|
|
67
|
+
linhas.push(l);
|
|
68
|
+
}
|
|
69
|
+
while (!linhas.at(-1).trim()) linhas.pop();
|
|
70
|
+
return linhas;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Sem terminal ou com NO_COLOR, só o nome; senão o sabiá ao lado, em 24 bits ou nos 256 tons. */
|
|
74
|
+
export function banner({ cores = process.stdout.isTTY && !process.env.NO_COLOR, bits24 = /truecolor|24bit/.test(process.env.COLORTERM ?? "") } = {}) {
|
|
75
|
+
if (!cores) return "\n" + NOME.join("\n");
|
|
76
|
+
const cor = (h, fundo) => {
|
|
77
|
+
const [r, g, b] = [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
78
|
+
return `\x1b[${fundo ? 48 : 38};` + (bits24 ? `2;${r};${g};${b}m` : `5;${16 + 36 * Math.round(r / 51) + 6 * Math.round(g / 51) + Math.round(b / 51)}m`);
|
|
79
|
+
};
|
|
80
|
+
const desenho = desenharSabia(cor), largura = [...desenho[0]].filter((c) => " ▀▄".includes(c)).length;
|
|
81
|
+
const texto = [...NOME.map((l) => `\x1b[1m${l}\x1b[0m`), "", `\x1b[2mengenharia agêntica, da ideia ao resultado aceito v${VERSAO}\x1b[0m`];
|
|
82
|
+
return "\n" + texto.map((t, i) => (desenho[i] ?? " ".repeat(largura)) + " " + t).join("\n");
|
|
83
|
+
}
|
|
84
|
+
const BANNER = banner();
|
|
26
85
|
|
|
27
86
|
// trocáveis nos testes
|
|
28
87
|
export const ctx = {
|
|
@@ -301,7 +360,8 @@ export async function instalar(opcoes = {}) {
|
|
|
301
360
|
} finally {
|
|
302
361
|
gravarInventario(inv);
|
|
303
362
|
}
|
|
304
|
-
ctx.log('Pronto.
|
|
363
|
+
ctx.log('Pronto: skills e MCPs instalados. Os agentes são de cada projeto: abra a ferramenta na raiz dele, na sessão\n' +
|
|
364
|
+
'padrão, e peça "configurar o Sabiá" (ou, num projeto já configurado, "atualizar os agentes do Sabiá").');
|
|
305
365
|
}
|
|
306
366
|
|
|
307
367
|
export async function atualizar(opcoes = {}) {
|
|
@@ -371,6 +431,8 @@ export function validarToolkit(pasta, nomeEsperado = null) {
|
|
|
371
431
|
const nomeOk = (n) => typeof n === "string" && /^[a-z0-9][a-z0-9-]*$/.test(n) && !n.startsWith("sa-");
|
|
372
432
|
if (!nomeOk(m.nome)) erros.push(`nome inválido (kebab-case, sem sa-): ${m.nome}`);
|
|
373
433
|
if (nomeEsperado && m.nome !== nomeEsperado) erros.push(`nome ${m.nome} ≠ ${nomeEsperado} do índice`);
|
|
434
|
+
if (m.customiza && (!nomeOk(m.customiza.toolkit) || m.customiza.toolkit === m.nome || typeof m.customiza.versao !== "string"))
|
|
435
|
+
erros.push("customiza: precisa de toolkit (outro nome, kebab-case) e versao da base");
|
|
374
436
|
const existe = (c, de) => {
|
|
375
437
|
try { if (!fs.existsSync(path.join(pasta, dentro(c, de)))) erros.push(`${de}: não existe ${c}`); } catch (e) { erros.push(e.message); }
|
|
376
438
|
};
|
|
@@ -453,6 +515,11 @@ export async function instalarToolkit(alvo, opcoes = {}) {
|
|
|
453
515
|
const pasta = sabia("toolkits", baixado);
|
|
454
516
|
const m = JSON.parse(fs.readFileSync(path.join(pasta, "toolkit.json"), "utf8"));
|
|
455
517
|
const inv = lerInventario();
|
|
518
|
+
// a customização é uma cópia modificada da base: as duas juntas no projeto dão skills e agentes repetidos
|
|
519
|
+
const familia = m.customiza?.toolkit ?? baixado;
|
|
520
|
+
const rival = Object.keys(inv.toolkits).find((n) => n !== baixado && (baseDe(n) ?? n) === familia);
|
|
521
|
+
// mesmos nomes de skill e de agente: as duas no usuário se sobrescreveriam
|
|
522
|
+
if (rival) throw new Error(`${baixado} e ${rival} são versões do mesmo toolkit (base ${familia}); instale um só: sabia toolkits remover ${rival}`);
|
|
456
523
|
const tk = (inv.toolkits[baixado] ||= { versao: m.versao, alvos: {} });
|
|
457
524
|
const alvos = opcoes.alvo ? opcoes.alvo.split(",") : Object.keys(inv.alvos).length ? Object.keys(inv.alvos) : await escolherAlvos(opcoes);
|
|
458
525
|
try {
|
|
@@ -504,6 +571,12 @@ export async function atualizarToolkits(nome, opcoes = {}) {
|
|
|
504
571
|
}
|
|
505
572
|
}
|
|
506
573
|
|
|
574
|
+
/** A base que o toolkit baixado customiza (do toolkit.json em ~/.sabia/toolkits), ou null. */
|
|
575
|
+
function baseDe(nome) {
|
|
576
|
+
const f = sabia("toolkits", nome, "toolkit.json");
|
|
577
|
+
return fs.existsSync(f) ? JSON.parse(fs.readFileSync(f, "utf8")).customiza?.toolkit ?? null : null;
|
|
578
|
+
}
|
|
579
|
+
|
|
507
580
|
export function listarToolkits(opcoes = {}) {
|
|
508
581
|
const inv = lerInventario();
|
|
509
582
|
const projeto = path.join(process.cwd(), ".sabia", "config", "agentes.json");
|
|
@@ -517,6 +590,18 @@ export function listarToolkits(opcoes = {}) {
|
|
|
517
590
|
}
|
|
518
591
|
}
|
|
519
592
|
for (const [n, t] of Object.entries(inv.toolkits)) if (!linhas.some((l) => l[0] === n)) linhas.push([n, "—", t.versao, doProjeto[n]?.versao || "—", "(fora dos índices)"]);
|
|
593
|
+
// a customização logo abaixo da base, com ↳ e de onde veio
|
|
594
|
+
for (const l of [...linhas]) {
|
|
595
|
+
const f = sabia("toolkits", l[0], "toolkit.json");
|
|
596
|
+
const c = fs.existsSync(f) ? JSON.parse(fs.readFileSync(f, "utf8")).customiza : null;
|
|
597
|
+
if (!c) continue;
|
|
598
|
+
linhas.splice(linhas.indexOf(l), 1);
|
|
599
|
+
const i = linhas.findIndex((b) => b[0] === c.toolkit);
|
|
600
|
+
// só a primeira mudança (quem customizou), como resumo; a lista inteira está no toolkit.json
|
|
601
|
+
l[4] = `customiza ${c.toolkit} ${c.versao}${c.mudancas?.length ? `: ${c.mudancas[0]}${c.mudancas.length > 1 ? "; …" : ""}` : ""}${l[4] ? " — " + l[4] : ""}`;
|
|
602
|
+
l[0] = `↳ ${l[0]}`;
|
|
603
|
+
linhas.splice(i < 0 ? linhas.length : i + 1, 0, l);
|
|
604
|
+
}
|
|
520
605
|
ctx.log(["| Toolkit | No índice | Instalado | Neste projeto | Descrição |", "|---|---|---|---|---|",
|
|
521
606
|
...linhas.map((l) => `| ${l.join(" | ")} |`)].join("\n"));
|
|
522
607
|
return linhas;
|