brito-cli 0.6.1 → 0.6.3

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 CHANGED
@@ -27,6 +27,10 @@ Brito is an agent for software development. Use it in the terminal (`brito-cli`
27
27
 
28
28
  ## How it works
29
29
 
30
+ ![Indexing once, then answering from the graph instead of grep and file reads](docs/images/graph-vs-grep.svg)
31
+
32
+ *The graph is built once and updated incrementally; a question like "what breaks if I change this?" becomes one or two graph calls.*
33
+
30
34
  ```mermaid
31
35
  flowchart LR
32
36
  code["Your project<br/>.ts · .js · .py · .java · .kt · .go · .rb · .php · .cs · .rs · .svelte · .vue · .astro"] -->|"tree-sitter<br/>incremental"| cg[("Code graph<br/>symbols · calls · imports")]
@@ -47,8 +51,18 @@ flowchart LR
47
51
  - **Skills**: step-by-step procedures for *your* project (commands, order, traps, how to verify). The agent loads one with `skill_load` before a task it covers.
48
52
  - **Journal**: Brito remembers across sessions which tasks repeated, which is the best hint that a procedure is general and worth saving.
49
53
 
54
+ ### Architecture at a glance
55
+
56
+ ![Brito architecture: context, permissions, tools and subagents around the model](docs/images/architecture.svg)
57
+
58
+ *Everything the model sees or can do is one of eight components, grouped by role. The mono-spaced text is the real file or command; the pill says when it fires.*
59
+
50
60
  ## Skills that earn their place
51
61
 
62
+ ![Skill lifecycle: task, reflection, trial or pending, active or archived](docs/images/skill-lifecycle.svg)
63
+
64
+ *The two paths a skill can take. Safe ones prove themselves in trial; risky ones wait for your approval.*
65
+
52
66
  ![Reviewing a pending skill, with a destructive-command warning](docs/images/skills-review.png)
53
67
 
54
68
  0. **Autonomous path (default).** The reflection saves a new skill as *trial*, marked "(em teste)" in the index. Each turn that loads it counts as a win (no error-then-fix, no user correction) or a loss. 3 wins (and at most 1 loss per 3 wins) promote it; 2 losses without more wins, or 21 days unused, archive it to `.brito/skills/.trash`. Brito prints one line per promotion or archive. Limits: at most 3 new automatic skills a day, project scope only, and a weekly automatic consolidation that only touches automatic skills.
@@ -133,7 +147,7 @@ OK: 20 arquivos, 66 símbolos, 54 chamadas, 17 imports | +0 ~0 -0 =20 (28ms)
133
147
  |---|---|
134
148
  | `/help` | list commands |
135
149
  | `/model`, `/effort`, `/reasoning` | choose model, reasoning effort and thinking (session, global or once) |
136
- | `/modo` or `Shift+Tab` | switch permission mode: manual → auto-edits → plan |
150
+ | `/modo` or `Shift+Tab` | switch permission mode: manual → auto-edits → plan → bypass ("everything allowed") |
137
151
  | `/skills` | list skills · `pending` · `approve <name>` · `reject <name>` · `consolidate` · `rm <name>` · `promote <name>` |
138
152
  | `/learn` | extract skills from the last turn now |
139
153
  | `/agents`, `/tools`, `/index`, `/context` | inspect subagents, tools, the index and the context window |
package/README.pt-BR.md CHANGED
@@ -27,6 +27,10 @@ O Brito é um agente para desenvolvimento de software. Use no terminal (`brito-c
27
27
 
28
28
  ## Como funciona
29
29
 
30
+ ![Indexação uma vez, depois respostas vindas do grafo em vez de grep e leitura de arquivos](docs/images/graph-vs-grep.pt-BR.svg)
31
+
32
+ *O grafo é construído uma vez e atualizado de forma incremental; uma pergunta como "o que quebra se eu mudar isto?" vira uma ou duas chamadas ao grafo.*
33
+
30
34
  ```mermaid
31
35
  flowchart LR
32
36
  code["Seu projeto<br/>.ts · .js · .py · .java · .kt · .go · .rb · .php · .cs · .rs · .svelte · .vue · .astro"] -->|"tree-sitter<br/>incremental"| cg[("Grafo de código<br/>símbolos · chamadas · imports")]
@@ -43,8 +47,18 @@ flowchart LR
43
47
  - **Skills**: procedimentos passo a passo para o *seu* projeto (comandos, ordem, armadilhas, como verificar). O agente carrega uma com `skill_load` antes de uma tarefa que ela cobre.
44
48
  - **Journal**: o Brito lembra entre sessões quais tarefas se repetiram, o melhor indício de que um procedimento é geral e vale guardar.
45
49
 
50
+ ### Arquitetura em uma imagem
51
+
52
+ ![Arquitetura do Brito: contexto, permissões, ferramentas e subagentes ao redor do modelo](docs/images/architecture.pt-BR.svg)
53
+
54
+ *Tudo o que o modelo vê ou pode fazer é um dos oito componentes, agrupados por papel. O texto em monoespaçado é o arquivo ou comando real; a etiqueta diz quando ele entra em ação.*
55
+
46
56
  ## Skills que merecem o lugar
47
57
 
58
+ ![Ciclo de vida da skill: tarefa, reflexão, teste ou pendente, ativa ou arquivada](docs/images/skill-lifecycle.pt-BR.svg)
59
+
60
+ *Os dois caminhos de uma skill. As seguras se provam em teste; as arriscadas esperam a sua aprovação.*
61
+
48
62
  ![Revisando uma skill pendente, com aviso de comando destrutivo](docs/images/skills-review.png)
49
63
 
50
64
  0. **Caminho autônomo (padrão).** A reflexão grava a skill nova como *em teste*, marcada "(em teste)" no índice. Cada turno que a carrega conta como vitória (sem erro→conserto nem correção do usuário) ou falha. 3 vitórias (e no máximo 1 falha a cada 3 vitórias) a promovem; 2 falhas sem mais vitórias, ou 21 dias sem uso, a arquivam em `.brito/skills/.trash`. O Brito imprime uma linha a cada promoção ou arquivamento. Limites: no máximo 3 skills automáticas novas por dia, só no escopo do projeto, e uma consolidação semanal automática que só mexe em skills automáticas.
@@ -129,7 +143,7 @@ OK: 20 arquivos, 66 símbolos, 54 chamadas, 17 imports | +0 ~0 -0 =20 (28ms)
129
143
  |---|---|
130
144
  | `/help` | lista os comandos |
131
145
  | `/model`, `/effort`, `/reasoning` | escolhem modelo, esforço de raciocínio e thinking (sessão, global ou uma vez) |
132
- | `/modo` ou `Shift+Tab` | troca o modo de permissão: manual → auto-edições → plano |
146
+ | `/modo` ou `Shift+Tab` | troca o modo de permissão: manual → auto-edições → plano → tudo liberado |
133
147
  | `/skills` | lista skills · `pending` · `approve <nome>` · `reject <nome>` · `consolidate` · `rm <nome>` · `promote <nome>` |
134
148
  | `/learn` | extrai skills do último turno agora |
135
149
  | `/agents`, `/tools`, `/index`, `/context` | inspecionam subagentes, tools, o índice e a janela de contexto |
@@ -6,8 +6,8 @@ export const perms = { mode: "manual" };
6
6
  */
7
7
  export const MODE_ENV = "BRITO_PERMS_MODE";
8
8
  export const modeFromEnv = (v) => ["manual", "auto", "plan", "bypass"].find((m) => m === v);
9
- const CYCLE = ["manual", "auto", "plan"];
10
- /** Próximo modo do ciclo; `bypass` (de --yes ou "tudo") volta para manual. */
9
+ export const CYCLE = ["manual", "auto", "plan", "bypass"];
10
+ /** Próximo modo do ciclo; depois de `bypass` (tudo liberado) volta para manual. */
11
11
  export function cycleMode() {
12
12
  perms.mode = CYCLE[(CYCLE.indexOf(perms.mode) + 1) % CYCLE.length];
13
13
  return perms.mode;
package/dist/repl.js CHANGED
@@ -39,11 +39,14 @@ export async function runRepl(b, o) {
39
39
  const modeColor = { manual: c.muted, auto: c.ok, plan: c.warn, bypass: c.err };
40
40
  const modeChip = () => { const m = MODE_LABEL[perms.mode]; return modeColor[perms.mode](`${m.icon} ${m.name}`); };
41
41
  const status = () => ui.statusLine({
42
+ project: path.basename(o.root),
42
43
  mode: modeChip(),
43
44
  model: b.model(), ctxTokens: b.contextTokens(), ctxWindow: b.ctxWindow,
44
45
  input: b.usage().input, output: b.usage().output, cacheRead: b.usage().cacheRead, startedAt: o.startedAt, cost: b.cost?.(),
45
46
  extra: `${skillCount} ${skillCount === 1 ? "skill" : "skills"}`,
46
47
  });
48
+ if (interactive && process.stdout.isTTY)
49
+ process.stdout.write(`\x1b]0;brito · ${path.basename(o.root)}\x07`);
47
50
  const dock = interactive ? new Dock({ status, commands: menu, historyFile: path.join(globalHome(), "history") }) : null;
48
51
  if (dock) {
49
52
  await dock.start();
@@ -90,7 +93,7 @@ export async function runRepl(b, o) {
90
93
  "/help": () => {
91
94
  for (const m of menu)
92
95
  ui.print(` ${c.soft(m.name.padEnd(12))} ${c.muted(m.desc)}`);
93
- ui.print(` ${c.soft("Shift+Tab".padEnd(12))} ${c.muted("alterna modo: manual → auto-edições → plano")}`);
96
+ ui.print(` ${c.soft("Shift+Tab".padEnd(12))} ${c.muted("alterna modo: manual → auto-edições → plano → tudo liberado")}`);
94
97
  ui.print(` ${c.soft("!comando".padEnd(12))} ${c.muted("roda no shell, sem chamar o modelo")}`);
95
98
  ui.print(c.dim(" Enter envia · Alt+Enter nova linha · ↑↓ histórico · Tab completa · Ctrl+C interrompe"));
96
99
  },
@@ -106,13 +109,13 @@ export async function runRepl(b, o) {
106
109
  "/indicadores": () => { for (const l of ui.statusLegend())
107
110
  ui.print(l); },
108
111
  "/modo": ([m]) => {
109
- const pick = { manual: "manual", auto: "auto", plano: "plan", plan: "plan" };
112
+ const pick = { manual: "manual", auto: "auto", plano: "plan", plan: "plan", tudo: "bypass", bypass: "bypass" };
110
113
  if (m && pick[m]) {
111
114
  perms.mode = pick[m];
112
115
  dock?.repaint();
113
116
  }
114
117
  else if (m) {
115
- ui.warn("use: /modo manual | auto | plano");
118
+ ui.warn("use: /modo manual | auto | plano | tudo");
116
119
  return;
117
120
  }
118
121
  const i = MODE_LABEL[perms.mode];
@@ -150,13 +153,13 @@ export async function runRepl(b, o) {
150
153
  const echo = (text) => {
151
154
  const lines = text.split("\n");
152
155
  const shown = lines.length > 4 ? [...lines.slice(0, 3), c.dim(`… (+${lines.length - 3} linhas)`)] : lines;
153
- // balão do usuário: mesmo formato da caixa do Brito, em vermelho e com o nome de usuário da máquina
156
+ // balão do usuário (sem borda lateral, para copiar limpo): mesmo formato da caixa do Brito, em vermelho e com o nome de usuário da máquina
154
157
  const label = ` ${userName()} `;
155
- const room = Math.max(10, cols() - 2);
158
+ const room = Math.max(10, cols() - 1);
156
159
  const body = shown.flatMap((l) => (l.length <= room ? [l] : l.match(new RegExp(`.{1,${room}}`, "gs")) ?? [l]));
157
- ui.print("\n" + c.err("╭─") + c.bold(c.err(label)) + c.err("─".repeat(Math.max(2, cols() - label.length - 2))) + "\n" +
158
- body.map((l) => c.err("│ ") + c.bold(c.err(l))).join("\n") + "\n" +
159
- c.err("╰" + "─".repeat(cols() - 1)));
160
+ ui.print("\n" + c.err("──") + c.bold(c.err(label)) + c.err("─".repeat(Math.max(2, cols() - label.length - 2))) + "\n" +
161
+ body.map((l) => c.bold(c.err(l))).join("\n") + "\n" +
162
+ c.err("─".repeat(cols())));
160
163
  };
161
164
  const shutdown = async () => {
162
165
  if (closing)
@@ -0,0 +1,102 @@
1
+ import stringWidth from "string-width";
2
+ import { c } from "./theme.js";
3
+ /** Separa as células de uma linha `| a | b |` (aceita `\|` dentro da célula). */
4
+ function cells(line) {
5
+ let s = line.trim();
6
+ if (s.startsWith("|"))
7
+ s = s.slice(1);
8
+ if (s.endsWith("|") && !s.endsWith("\\|"))
9
+ s = s.slice(0, -1);
10
+ return s.split(/(?<!\\)\|/).map((x) => x.replace(/\\\|/g, "|").trim());
11
+ }
12
+ const isSep = (row) => row.length > 0 && row.every((x) => /^:?-{1,}:?$/.test(x.replace(/\s/g, "")));
13
+ /** Tira a marcação inline (**negrito**, *itálico*, `código`) — a tabela é desenhada sem ela. */
14
+ const plain = (s) => s.replace(/\*\*(.+?)\*\*/g, "$1").replace(/__(.+?)__/g, "$1").replace(/(^|[^\w*])\*(?!\s)(.+?)\*(?!\w)/g, "$1$2").replace(/`([^`]*)`/g, "$1").replace(/<br\s*\/?>/gi, " ");
15
+ /** Quebra o texto em linhas de no máximo `w` colunas (palavra longa demais é cortada). */
16
+ function wrap(text, w) {
17
+ const out = [];
18
+ let cur = "";
19
+ const push = (word) => {
20
+ while (stringWidth(word) > w) {
21
+ if (cur) {
22
+ out.push(cur);
23
+ cur = "";
24
+ }
25
+ let piece = "";
26
+ for (const ch of word) {
27
+ if (stringWidth(piece + ch) > w)
28
+ break;
29
+ piece += ch;
30
+ }
31
+ out.push(piece);
32
+ word = word.slice(piece.length);
33
+ }
34
+ if (!cur)
35
+ cur = word;
36
+ else if (stringWidth(cur) + 1 + stringWidth(word) <= w)
37
+ cur += " " + word;
38
+ else {
39
+ out.push(cur);
40
+ cur = word;
41
+ }
42
+ };
43
+ for (const word of text.split(/\s+/).filter(Boolean))
44
+ push(word);
45
+ if (cur || !out.length)
46
+ out.push(cur);
47
+ return out;
48
+ }
49
+ const pad = (s, w, align) => {
50
+ const gap = Math.max(0, w - stringWidth(s));
51
+ if (align === "r")
52
+ return " ".repeat(gap) + s;
53
+ if (align === "c")
54
+ return " ".repeat(Math.floor(gap / 2)) + s + " ".repeat(Math.ceil(gap / 2));
55
+ return s + " ".repeat(gap);
56
+ };
57
+ /**
58
+ * Desenha uma tabela markdown como grade com bordas (┌─┬─┐ / │ / ├─┼─┤ / └─┴─┘), como o Claude Code.
59
+ * Cabe em `width` colunas: as colunas mais largas encolhem e o texto quebra dentro delas.
60
+ * Devolve null se as linhas não formam uma tabela (falta a linha `|---|---|`).
61
+ */
62
+ export function renderTable(lines, width) {
63
+ if (lines.length < 2)
64
+ return null;
65
+ const head = cells(lines[0]);
66
+ const sepRow = cells(lines[1]);
67
+ if (!isSep(sepRow))
68
+ return null;
69
+ const n = head.length;
70
+ const align = Array.from({ length: n }, (_, i) => {
71
+ const s = (sepRow[i] ?? "").replace(/\s/g, "");
72
+ return s.startsWith(":") && s.endsWith(":") ? "c" : s.endsWith(":") ? "r" : "l";
73
+ });
74
+ const norm = (r) => Array.from({ length: n }, (_, i) => plain(r[i] ?? ""));
75
+ const rows = [norm(head), ...lines.slice(2).map(cells).map(norm)];
76
+ const w = Array.from({ length: n }, (_, i) => Math.max(1, ...rows.map((r) => stringWidth(r[i]))));
77
+ const room = Math.max(n, width - (3 * n + 1));
78
+ // encolhe sempre a coluna mais larga até caber (mínimo de 8 colunas, ou menos se não houver espaço)
79
+ const min = Math.max(1, Math.min(8, Math.floor(room / n)));
80
+ while (w.reduce((a, b) => a + b, 0) > room) {
81
+ const i = w.indexOf(Math.max(...w));
82
+ if (w[i] <= min)
83
+ break;
84
+ w[i]--;
85
+ }
86
+ const border = (l, m, r) => c.accent(l + w.map((x) => "─".repeat(x + 2)).join(m) + r);
87
+ const bar = c.accent("│");
88
+ const out = [border("┌", "┬", "┐")];
89
+ rows.forEach((r, ri) => {
90
+ const wrapped = r.map((cell, i) => wrap(cell, w[i]));
91
+ const h = Math.max(...wrapped.map((x) => x.length));
92
+ for (let k = 0; k < h; k++) {
93
+ const body = wrapped.map((x, i) => {
94
+ const cell = pad(x[k] ?? "", w[i], ri === 0 ? "c" : align[i]);
95
+ return " " + (ri === 0 ? c.bold(cell) : cell) + " ";
96
+ });
97
+ out.push(bar + body.join(bar) + bar);
98
+ }
99
+ out.push(ri === rows.length - 1 ? border("└", "┴", "┘") : border("├", "┼", "┤"));
100
+ });
101
+ return out;
102
+ }
package/dist/ui/ui.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { BOLD, CODE, ITALIC, OFF, c, cols, palette, useColor } from "./theme.js";
2
2
  import { renderBanner } from "./banner.js";
3
+ import { renderTable } from "./table.js";
3
4
  import stringWidth from "string-width";
4
5
  export const NAME = "Brito";
5
6
  const isTTY = !!process.stdout.isTTY;
@@ -159,6 +160,70 @@ class UI {
159
160
  skip = false; // descarta o resto da linha do ``` (nome da linguagem)
160
161
  bol = true; // só espaços até agora nesta linha
161
162
  prev = " "; // último caractere resolvido
163
+ // tabelas markdown: linhas que começam com `|` são guardadas inteiras e desenhadas alinhadas no fim da tabela
164
+ rawBol = true; // início de linha no texto bruto do modelo
165
+ lead = ""; // espaços no início da linha, antes de decidir se é tabela
166
+ tblLine = null; // linha de tabela em andamento
167
+ tbl = []; // linhas completas da tabela atual
168
+ /** Primeira etapa do streaming: separa as linhas de tabela; o resto segue para o markdown. */
169
+ raw(ch) {
170
+ if (this.tblLine !== null) {
171
+ if (ch === "\n") {
172
+ this.tbl.push(this.tblLine);
173
+ this.tblLine = null;
174
+ this.rawBol = true;
175
+ }
176
+ else
177
+ this.tblLine += ch;
178
+ return;
179
+ }
180
+ if (this.rawBol && !this.st.fence) {
181
+ if (ch === " " || ch === "\t") {
182
+ this.lead += ch;
183
+ return;
184
+ }
185
+ if (ch === "|") {
186
+ this.tblLine = "|";
187
+ this.lead = "";
188
+ return;
189
+ }
190
+ }
191
+ this.flushTable();
192
+ if (this.lead) {
193
+ for (const x of this.lead)
194
+ this.feed(x);
195
+ this.lead = "";
196
+ }
197
+ this.rawBol = ch === "\n";
198
+ this.feed(ch);
199
+ }
200
+ /** Fecha a tabela pendente: desenha alinhada ou, se não for tabela de verdade, devolve as linhas como texto. */
201
+ flushTable() {
202
+ if (this.tblLine !== null) {
203
+ this.tbl.push(this.tblLine);
204
+ this.tblLine = null;
205
+ }
206
+ if (!this.tbl.length)
207
+ return;
208
+ const lines = this.tbl;
209
+ this.tbl = [];
210
+ const drawn = renderTable(lines, cols() - 1);
211
+ if (!drawn) {
212
+ for (const l of lines)
213
+ for (const x of l + "\n")
214
+ this.feed(x);
215
+ return;
216
+ }
217
+ this.emitWord();
218
+ if (!this.lineStart)
219
+ this.out("\n");
220
+ for (const l of drawn)
221
+ this.out(l + "\n");
222
+ this.lineStart = true;
223
+ this.lineW = 0;
224
+ this.prev = "\n";
225
+ this.bol = true;
226
+ }
162
227
  seq() {
163
228
  const { b, i, k, fence } = this.st;
164
229
  return k || fence ? CODE : (i ? ITALIC : "") + (b ? BOLD : "");
@@ -178,6 +243,10 @@ class UI {
178
243
  this.skip = false;
179
244
  this.bol = true;
180
245
  this.prev = " ";
246
+ this.rawBol = true;
247
+ this.lead = "";
248
+ this.tblLine = null;
249
+ this.tbl = [];
181
250
  }
182
251
  /** Descarrega a palavra em andamento, quebrando a linha antes dela se não couber. */
183
252
  emitWord() {
@@ -190,11 +259,8 @@ class UI {
190
259
  this.lineStart = true;
191
260
  this.lineW = 0;
192
261
  }
193
- if (this.lineStart) {
194
- this.out(c.accent("│ "));
195
- this.lineStart = false;
196
- }
197
- // o estilo é reaplicado por palavra: a borda e os espaços não herdam a cor
262
+ this.lineStart = false; // sem borda lateral: copiar o texto do terminal sai limpo
263
+ // o estilo é reaplicado por palavra: os espaços não herdam a cor
198
264
  this.out(this.wordStart + this.word + (this.active() ? OFF : ""));
199
265
  this.lineW += w;
200
266
  this.word = "";
@@ -269,16 +335,13 @@ class UI {
269
335
  this.st.i = false;
270
336
  } // linha em branco encerra ênfase aberta
271
337
  this.wordStart = this.seq();
272
- this.out((this.lineStart ? c.accent("│") : "") + "\n");
338
+ this.out("\n");
273
339
  this.lineStart = true;
274
340
  this.lineW = 0;
275
341
  }
276
342
  else if (ch === " ") {
277
343
  this.emitWord();
278
- if (this.lineStart) {
279
- this.out(c.accent("│ "));
280
- this.lineStart = false;
281
- }
344
+ this.lineStart = false;
282
345
  if (this.lineW + 1 > cols() - 2) {
283
346
  this.out("\n");
284
347
  this.lineStart = true;
@@ -299,7 +362,7 @@ class UI {
299
362
  if (!this.textOpen) {
300
363
  this.clear();
301
364
  const label = ` ${NAME} `;
302
- this.out(c.accent("╭─") + c.bold(c.light(label)) + c.accent("─".repeat(Math.max(2, cols() - label.length - 2))) + "\n");
365
+ this.out(c.accent("──") + c.bold(c.light(label)) + c.accent("─".repeat(Math.max(2, cols() - label.length - 2))) + "\n");
303
366
  this.textOpen = true;
304
367
  this.lineStart = true;
305
368
  this.lineW = 0;
@@ -307,18 +370,24 @@ class UI {
307
370
  this.resetStyle();
308
371
  }
309
372
  for (const ch of chunk)
310
- this.feed(ch);
373
+ this.raw(ch);
311
374
  }
312
375
  textEnd() {
313
376
  if (!this.textOpen)
314
377
  return;
378
+ this.flushTable();
379
+ if (this.lead) {
380
+ for (const x of this.lead)
381
+ this.feed(x);
382
+ this.lead = "";
383
+ }
315
384
  if (this.run.n)
316
385
  this.resolve("\n"); // marcador no fim da resposta: fecha se havia abertura, senão fica literal
317
386
  this.emitWord();
318
387
  this.resetStyle();
319
388
  if (!this.lineStart)
320
389
  this.out("\n");
321
- this.out(c.accent("╰" + "─".repeat(cols() - 1)) + "\n");
390
+ this.out(c.accent("─".repeat(cols())) + "\n");
322
391
  this.textOpen = false;
323
392
  this.draw();
324
393
  }
@@ -355,17 +424,17 @@ class UI {
355
424
  return;
356
425
  this.textEnd();
357
426
  const w = cols();
358
- const wrapped = body.split("\n").flatMap((l) => wrapLine(l.trimEnd(), w - 4));
427
+ const wrapped = body.split("\n").flatMap((l) => wrapLine(l.trimEnd(), w - 1));
359
428
  const MAX = 10;
360
429
  const cut = !this.reasoning.full && wrapped.length > MAX;
361
430
  const lines = cut ? wrapped.slice(0, MAX) : wrapped;
362
431
  const label = " raciocínio ";
363
- this.print(c.dim(`┌─${label}${"─".repeat(Math.max(0, w - 3 - label.length))}┐`));
432
+ this.print(c.dim(`──${label}${"─".repeat(Math.max(0, w - 2 - label.length))}`));
364
433
  for (const l of lines)
365
- this.print(c.dim(`│ ${l}`));
434
+ this.print(c.dim(l));
366
435
  if (cut)
367
- this.print(c.dim(`│ … +${wrapped.length - MAX} linhas (/reasoning full mostra tudo)`));
368
- this.print(c.dim(`└${"─".repeat(w - 2)}┘`));
436
+ this.print(c.dim(`… +${wrapped.length - MAX} linhas (/reasoning full mostra tudo)`));
437
+ this.print(c.dim("─".repeat(w)));
369
438
  }
370
439
  info(msg) { this.textEnd(); this.print(c.muted(msg)); }
371
440
  warn(msg) { this.textEnd(); this.print(c.rgb(palette.warn)(msg)); }
@@ -389,6 +458,7 @@ class UI {
389
458
  const hitColour = hit === undefined ? c.muted : hit >= 70 ? c.ok : hit >= 40 ? c.warn : c.err;
390
459
  // [prioridade (maior = descarta primeiro), texto]
391
460
  const segs = [
461
+ ...(s.project ? [[0, c.light("📁 ") + c.bold(c.soft(s.project))]] : []),
392
462
  ...(s.mode ? [[0, s.mode]] : []),
393
463
  [0, c.light("⬢ ") + c.bold(c.soft(s.model))],
394
464
  [0, label("contexto") + c.muted(`${fmtTokens(s.ctxTokens)}/${fmtTokens(s.ctxWindow)}`) + ` [${bar}] ${colour(`${pct}%`)}`],
@@ -417,6 +487,7 @@ class UI {
417
487
  ["tokens", "total da sessão: ↑ enviados ao modelo (inclui cache) · ↓ gerados por ele."],
418
488
  ["custo", "gasto estimado da sessão em dólares."],
419
489
  ["tempo", "há quanto tempo a sessão está aberta · (ativo) soma do tempo gasto em todos os turnos."],
490
+ ["📁", "projeto (pasta) em que o Brito está trabalhando — também aparece no título da janela do terminal."],
420
491
  ["skill", "skill carregada (skill_load) no turno em andamento."],
421
492
  ].map(([k, v]) => ` ${c.soft(k.padEnd(9))} ${c.muted(v)}`);
422
493
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brito-cli",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Coding-agent CLI with a code knowledge graph, memory and reviewable skills / CLI de agente de código com grafo de conhecimento, memória e skills revisáveis",
5
5
  "type": "module",
6
6
  "bin": {