brito-cli 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -144,11 +144,11 @@ export class ClaudeSession {
144
144
  ui.turnStart();
145
145
  const { ok: imgs, tooBig } = splitOversize(findImages(text, this.o.root));
146
146
  for (const i of tooBig)
147
- ui.warn(`${i.name} passa de 5 MB e não foi anexada`);
147
+ ui.warn(`${i.name} passa do limite da API e não foi anexado`);
148
148
  if (imgs.length)
149
- ui.info(`🖼 ${imgs.map((i) => `${i.name} (${fmtBytes(i.bytes)})`).join(", ")} anexada${imgs.length > 1 ? "s" : ""}`);
149
+ ui.info(`📎 ${imgs.map((i) => `${i.name} (${fmtBytes(i.bytes)})`).join(", ")} anexado${imgs.length > 1 ? "s" : ""}`);
150
150
  // O claude vê a imagem pela tool Read: o prompt diz quais arquivos são imagens anexadas pelo usuário.
151
- const prompt = imgs.length ? `${text}\n\n[O usuário anexou ${imgs.length > 1 ? "estas imagens" : "esta imagem"}; abra com a tool Read para vê-${imgs.length > 1 ? "las" : "la"}:\n${imgs.map((i) => `- ${i.path}`).join("\n")}]` : text;
151
+ const prompt = imgs.length ? `${text}\n\n[O usuário anexou ${imgs.length > 1 ? "estes arquivos" : "este arquivo"}; abra com a tool Read para vê-${imgs.length > 1 ? "las" : "la"}:\n${imgs.map((i) => `- ${i.path}`).join("\n")}]` : text;
152
152
  const cmd = claudeCommand(this.args(prompt, [...new Set(imgs.map((i) => path.dirname(i.path)))]));
153
153
  const child = (this.child = spawn(cmd.command, cmd.args, {
154
154
  cwd: this.o.root, stdio: ["ignore", "pipe", "pipe"],
@@ -1,5 +1,5 @@
1
1
  /** Imagem em base64 tem megabytes, mas custa ~1,6 mil tokens: troca o conteúdo por um marcador antes de medir ou resumir. */
2
- const stubImages = (_k, v) => (v && typeof v === "object" && v.type === "image" ? { type: "image", note: "x".repeat(6400) } : v);
2
+ const stubImages = (_k, v) => (v && typeof v === "object" && (v.type === "image" || v.type === "document") && v.source ? { type: v.type, note: "x".repeat(6400) } : v);
3
3
  export const estimateTokens = (msgs) => Math.ceil(JSON.stringify(msgs, stubImages).length / 4);
4
4
  const MAX_OLD_RESULT = 1500;
5
5
  /** Fase 1 (barata): encurta tool_results antigos, preservando as últimas `keep` mensagens. */
@@ -35,7 +35,7 @@ export async function summarizeMiddle(client, model, msgs, keepTail = 6) {
35
35
  model,
36
36
  max_tokens: 2000,
37
37
  system: "Resuma a conversa de um agente de programação. Preserve: objetivo, decisões, arquivos/símbolos tocados, erros encontrados, estado atual e próximos passos. Seja denso.",
38
- messages: [{ role: "user", content: JSON.stringify(middle, (_k, v) => (v && typeof v === "object" && v.type === "image" ? { type: "image", note: "[imagem omitida]" } : v)).slice(0, 150_000) }],
38
+ messages: [{ role: "user", content: JSON.stringify(middle, (_k, v) => (v && typeof v === "object" && (v.type === "image" || v.type === "document") && v.source ? { type: v.type, note: "[anexo omitido]" } : v)).slice(0, 150_000) }],
39
39
  });
40
40
  const summary = res.content.map((b) => (b.type === "text" ? b.text : "")).join("");
41
41
  return [...head, { role: "user", content: `[Resumo da conversa anterior]\n${summary}` }, { role: "assistant", content: "Entendido, continuando a partir do resumo." }, ...tail];
@@ -63,9 +63,9 @@ export class Agent {
63
63
  // (usado na reflexão) guarda só o texto, para não carregar base64.
64
64
  const { ok, tooBig } = splitOversize(findImages(userText, this.ctx.root));
65
65
  for (const i of tooBig)
66
- this.log(`⚠ ${i.name} passa de 5 MB e não foi anexada`);
66
+ this.log(`⚠ ${i.name} passa do limite da API e não foi anexado`);
67
67
  if (ok.length) {
68
- this.log(`🖼 ${ok.map((i) => `${i.name} (${fmtBytes(i.bytes)})`).join(", ")} anexada${ok.length > 1 ? "s" : ""}`);
68
+ this.log(`📎 ${ok.map((i) => `${i.name} (${fmtBytes(i.bytes)})`).join(", ")} anexado${ok.length > 1 ? "s" : ""}`);
69
69
  this.messages.push({ role: "user", content: [...ok.map(imageBlock), { type: "text", text: userText }] });
70
70
  turn.messages.push({ role: "user", content: userText });
71
71
  }
package/dist/repl.js CHANGED
@@ -188,15 +188,16 @@ export async function runRepl(b, o) {
188
188
  await shutdown();
189
189
  return;
190
190
  }
191
- if (line.startsWith("/")) {
192
- const run = common[key] ?? b.commands[key]?.run;
193
- if (!run) {
194
- ui.warn(`comando desconhecido: ${cmd}. Digite /help.`);
195
- return;
196
- }
191
+ const run = line.startsWith("/") ? common[key] ?? b.commands[key]?.run : undefined;
192
+ if (run) {
197
193
  await run(args);
198
194
  return;
199
195
  }
196
+ // "/Users/x/arquivo.pdf" é caminho, não comando: segue como mensagem ao agente
197
+ if (line.startsWith("/") && !cmd.slice(1).includes("/")) {
198
+ ui.warn(`comando desconhecido: ${cmd}. Digite /help.`);
199
+ return;
200
+ }
200
201
  const ac = (running = new AbortController());
201
202
  dock?.setBusy(true);
202
203
  try {
package/dist/ui/dock.js CHANGED
@@ -8,7 +8,7 @@ const graphemes = (s) => Array.from(seg.segment(s), (x) => x.segment);
8
8
  const ANSI = /\x1b\[[0-9;]*[A-Za-z]/y;
9
9
  const MAX_INPUT_ROWS = 8;
10
10
  const MAX_MENU = 7;
11
- const PASTE_RE = /\[(?:colado|imagem) #(\d+): [^\]]*\]/g;
11
+ const PASTE_RE = /\[(?:colado|imagem|anexo) #(\d+): [^\]]*\]/g;
12
12
  export class Dock {
13
13
  opts;
14
14
  onSubmit = () => { };
@@ -415,7 +415,7 @@ export class Dock {
415
415
  if (imgs.length) {
416
416
  const n = ++this.pasteN;
417
417
  this.pastes.set(n, imgs.map((i) => (/\s/.test(i.path) ? `"${i.path}"` : i.path)).join(" "));
418
- this.insert(`[imagem #${n}: ${imgs.length === 1 ? imgs[0].name : `${imgs.length} imagens`}] `);
418
+ this.insert(`[anexo #${n}: ${imgs.length === 1 ? imgs[0].name : `${imgs.length} arquivos`}] `);
419
419
  }
420
420
  else if (lines >= 2 || text.length > 300) {
421
421
  const n = ++this.pasteN;
@@ -2,10 +2,13 @@ import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- const TYPES = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp" };
6
- const IMG_EXT = /\.(png|jpe?g|gif|webp)$/i;
7
- /** Limite da API por imagem (5 MB) e teto de anexos por mensagem. */
5
+ const TYPES = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", pdf: "application/pdf" };
6
+ const IMG_EXT = /\.(png|jpe?g|gif|webp|pdf)$/i;
7
+ /** Limite da API por imagem (5 MB), por PDF (32 MB) e teto de anexos por mensagem. */
8
8
  export const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
9
+ export const MAX_PDF_BYTES = 32 * 1024 * 1024;
10
+ export const limitOf = (i) => (i.mediaType === "application/pdf" ? MAX_PDF_BYTES : MAX_IMAGE_BYTES);
11
+ export const isPdf = (i) => i.mediaType === "application/pdf";
9
12
  export const MAX_IMAGES = 5;
10
13
  /** Divide o texto em tokens, respeitando aspas e (fora do Windows) `\ ` como espaço do nome do arquivo. */
11
14
  function tokens(text) {
@@ -67,9 +70,11 @@ export function droppedImages(text, root = process.cwd()) {
67
70
  export const fmtBytes = (n) => (n >= 1_048_576 ? `${(n / 1_048_576).toFixed(1)} MB` : `${Math.max(1, Math.round(n / 1024))} kB`);
68
71
  /** Separa o que dá para enviar do que passa do limite da API. */
69
72
  export function splitOversize(imgs) {
70
- return { ok: imgs.filter((i) => i.bytes <= MAX_IMAGE_BYTES), tooBig: imgs.filter((i) => i.bytes > MAX_IMAGE_BYTES) };
73
+ return { ok: imgs.filter((i) => i.bytes <= limitOf(i)), tooBig: imgs.filter((i) => i.bytes > limitOf(i)) };
71
74
  }
72
- /** Bloco `image` da API da Anthropic, com o arquivo em base64. */
75
+ /** Bloco `image` (ou `document`, para PDF) da API da Anthropic, com o arquivo em base64. */
73
76
  export function imageBlock(img) {
77
+ if (isPdf(img))
78
+ return { type: "document", source: { type: "base64", media_type: "application/pdf", data: fs.readFileSync(img.path).toString("base64") } };
74
79
  return { type: "image", source: { type: "base64", media_type: img.mediaType, data: fs.readFileSync(img.path).toString("base64") } };
75
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brito-cli",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
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": {