kafox-cli 0.7.0 → 0.8.0

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
@@ -12,7 +12,7 @@ npm install -g kafox-cli
12
12
  O comando instalado e `kafox`. Alternativa sem npm:
13
13
 
14
14
  ```bash
15
- npm install -g https://kafox.pypag.com/static/downloads/kafox-cli-0.3.0.tgz
15
+ npm install -g https://kafox.pypag.com/static/downloads/kafox-cli-0.8.0.tgz
16
16
  ```
17
17
 
18
18
  Requisito: Node.js 22.5+.
@@ -41,7 +41,7 @@ URL `https://kafox.pypag.com/cli?code=...`, entre na sua conta e clique em
41
41
  | `kafox key` | cria uma API key |
42
42
  | `kafox ask "pergunta" [-m modelo]` | pergunta ao gateway |
43
43
  | `kafox agent "objetivo" [-y] [--max-steps 12] [--json]` | roda o agente sem TUI (script/CI) |
44
- | `kafox history` | lista as conversas salvas (titulo + como retomar) |
44
+ | `kafox history [n]` | lista as conversas salvas (titulo - data + como retomar) |
45
45
  | `kafox resume [codigo]` | retoma uma conversa; sem codigo abre o seletor na tela |
46
46
  | `kafox continue` / `kafox -c` | volta para a conversa mais recente |
47
47
  | `kafox status` | conta, saldo e gateway |
@@ -58,16 +58,21 @@ CLI mostra o codigo:
58
58
  Retome esta conversa: kafox --resume 3f1c9a0b
59
59
  ```
60
60
 
61
- `kafox history` lista tudo:
61
+ `kafox history` lista da mais nova para a mais antiga, cada uma com o comando
62
+ que retoma (e `kafox history 20` mostra so as 20 ultimas):
62
63
 
63
64
  ```
64
- - hello world em python solicitado, usa arquivo e roda teste
65
- kafox resume 3f1c9a0b
65
+ hello world em python, tem como fazer um sis... - 28/10
66
+ kafox resume 3f1c9a0b
67
+
68
+ nessa cli que voce ve, tem como fazer alguma co... - 27/10
69
+ kafox resume 8ad2f19c
66
70
  ```
67
71
 
68
72
  `kafox resume` (sem codigo) abre o seletor com os titulos das conversas — setas
69
- ou mouse escolhem, Enter retoma. `kafox continue` (ou `-c`) vai direto para a
70
- ultima.
73
+ ou a roda do mouse navegam, clique ou Enter retomam a conversa escolhida.
74
+ `kafox resume 3f1c` aceita o codigo inteiro ou so o comeco dele, e
75
+ `kafox continue` (ou `-c`) vai direto para a ultima.
71
76
 
72
77
  ## Agentes e subagentes
73
78
 
@@ -176,7 +181,9 @@ motivo do erro quando falha) e `/mcp` lista tudo; `/mcp reload` reconecta.
176
181
  - `~/.kafox/mcp.json` guarda os servidores MCP.
177
182
 
178
183
  A CLI so responde se a conta tiver saldo; sem credito ela avisa e nao chama o
179
- gateway.
184
+ gateway. Nao existe limite de mensagens por chamada nem de passos do agente: a
185
+ conversa continua ate o modelo terminar (ou ate voce apertar Esc). O unico freio
186
+ e o saldo.
180
187
 
181
188
  ## Escopo
182
189
 
package/bin/kafox.js CHANGED
@@ -25,19 +25,35 @@ program
25
25
  .option("-c, --continue", "retoma a conversa mais recente")
26
26
  .version(version);
27
27
 
28
- /** `kafox history`: titulo de cada conversa e como retomar. */
29
- function printHistory() {
28
+ /**
29
+ * `kafox history [n]`: lista as conversas salvas, da mais nova para a mais
30
+ * antiga, no formato "titulo - 28/10" seguido do comando para retomar.
31
+ * Sem argumento lista todas; `kafox history 20` mostra as 20 ultimas.
32
+ */
33
+ function printHistory(limit) {
30
34
  const sessions = require("../src/sessions");
31
35
  const chats = sessions.list();
32
36
  if (!chats.length) {
33
37
  console.log("Nenhuma conversa salva ainda.");
38
+ console.log(chalk.dim("Comece um chat com `kafox` e saia com Ctrl+C para ele ser salvo."));
34
39
  return;
35
40
  }
36
- for (const chat of chats) {
37
- console.log(`- ${chalk.bold(chat.title)}`);
38
- console.log(` kafox resume ${chat.id}`);
41
+
42
+ const wanted = Number.parseInt(String(limit ?? ""), 10);
43
+ const shown =
44
+ Number.isFinite(wanted) && wanted > 0 ? chats.slice(0, wanted) : chats;
45
+
46
+ for (const chat of shown) {
47
+ const when = sessions.dayMonth(chat.updatedAt);
48
+ console.log(`${chalk.bold(chat.title)}${when ? ` - ${when}` : ""}`);
49
+ console.log(chalk.cyan(`kafox resume ${chat.id}`));
39
50
  console.log("");
40
51
  }
52
+ if (shown.length < chats.length) {
53
+ console.log(
54
+ chalk.dim(`${shown.length} de ${chats.length} conversa(s). Use \`kafox history\` para ver todas.`)
55
+ );
56
+ }
41
57
  }
42
58
 
43
59
  program.command("login").description("Login with your Kafox account (device code SSO)").action(login);
@@ -73,10 +89,10 @@ program
73
89
  program.command("theme").description("Open the theme selector").action(runTheme);
74
90
  program.command("background").description("Set terminal background image (URL)").argument("[url]", "Image URL").action(setContentBackground);
75
91
  program
76
- .command("history")
92
+ .command("history [quantidade]")
77
93
  .alias("historico")
78
- .description("Lista todas as conversas salvas")
79
- .action(printHistory);
94
+ .description("Lista as conversas salvas e como retomar (ex.: kafox history 20)")
95
+ .action((quantidade) => printHistory(quantidade));
80
96
 
81
97
  program
82
98
  .command("resume [codigo]")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kafox-cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Kafox CLI: TUI com streaming, thinking, edit com diff, agent tools (files/shell), MCP e temas",
5
5
  "bin": {
6
6
  "kafox": "bin/kafox.js"
package/src/sessions.js CHANGED
@@ -40,6 +40,23 @@ function fileFor(id) {
40
40
  return path.join(chatsDir(), `${id}.json`);
41
41
  }
42
42
 
43
+ /** Codigo digitado pelo usuario: so letras/numeros (sem caminho, sem "."). */
44
+ function normalizeId(value) {
45
+ return String(value || "")
46
+ .trim()
47
+ .toLowerCase()
48
+ .replace(/[^a-z0-9]/g, "");
49
+ }
50
+
51
+ /** Data curta do chat: "2026-10-28T..." -> "28/10". */
52
+ function dayMonth(value) {
53
+ const parsed = new Date(value || "");
54
+ if (Number.isNaN(parsed.getTime())) return "";
55
+ const day = String(parsed.getDate()).padStart(2, "0");
56
+ const month = String(parsed.getMonth() + 1).padStart(2, "0");
57
+ return `${day}/${month}`;
58
+ }
59
+
43
60
  /** Titulo curto: primeira mensagem do usuario, em uma linha. */
44
61
  function titleFrom(messages, fallback = "") {
45
62
  const first = (messages || []).find(
@@ -116,6 +133,20 @@ function latest() {
116
133
  return list()[0] || null;
117
134
  }
118
135
 
136
+ /**
137
+ * Acha a conversa por id exato ou por prefixo unico: `kafox resume 3f1c`
138
+ * tambem funciona quando o codigo foi copiado pela metade.
139
+ */
140
+ function find(value) {
141
+ const needle = normalizeId(value);
142
+ if (!needle) return null;
143
+ const exact = load(needle);
144
+ if (exact) return exact;
145
+ const matches = list().filter((chat) => chat.id.startsWith(needle));
146
+ if (matches.length !== 1) return null;
147
+ return load(matches[0].id);
148
+ }
149
+
119
150
  function prune() {
120
151
  for (const chat of list().slice(MAX_SESSIONS)) {
121
152
  try {
@@ -130,9 +161,12 @@ module.exports = {
130
161
  chatsDir,
131
162
  newId,
132
163
  titleFrom,
164
+ normalizeId,
165
+ dayMonth,
133
166
  save,
134
167
  load,
135
168
  list,
136
169
  latest,
170
+ find,
137
171
  prune,
138
172
  };
package/src/tui.js CHANGED
@@ -1168,11 +1168,13 @@ function buildOverlay(state, cols, rows) {
1168
1168
  if (themePicker) {
1169
1169
  line = padRight(` ${caret} ${padRight(item.id, 15)} ` + themeSwatch(item.id), width - 4);
1170
1170
  } else if (chatPicker) {
1171
- const when = String(item.updatedAt || "").slice(0, 10);
1172
- const right = chalk.dim(`${when} ${item.id}`);
1171
+ // Formato do seletor: "titulo do chat - 28/10", com o codigo do
1172
+ // resume discreto na direita para quem quiser copiar.
1173
+ const right = chalk.dim(item.id);
1173
1174
  const space = width - 6;
1174
- const room = Math.max(12, space - visible(right) - 2);
1175
- const left = ` ${caret} ${truncate(item.title || item.id, room)}`;
1175
+ const room = Math.max(12, space - visible(right) - 12);
1176
+ const when = sessions.dayMonth(item.updatedAt);
1177
+ const left = ` ${caret} ${truncate(item.title || item.id, room)}${when ? ` - ${when}` : ""}`;
1176
1178
  const gap = Math.max(1, space - visible(left) - visible(right));
1177
1179
  line = padRight(left + " ".repeat(gap) + right, width - 4);
1178
1180
  } else {
@@ -1340,7 +1342,7 @@ async function runTUI({
1340
1342
  resumePick = true;
1341
1343
  } else {
1342
1344
  const id = resume === "last" || resume === true ? sessions.latest()?.id : String(resume);
1343
- const saved = sessions.load(id);
1345
+ const saved = resume === "last" || resume === true ? sessions.load(id) : sessions.find(id);
1344
1346
  if (saved) {
1345
1347
  state.sessionId = saved.id;
1346
1348
  state.sessionStartedAt = saved.startedAt;
@@ -1548,7 +1550,7 @@ async function runTUI({
1548
1550
 
1549
1551
  /** Troca a sessao atual pela conversa escolhida no seletor. */
1550
1552
  function resumeChat(id) {
1551
- const saved = sessions.load(id);
1553
+ const saved = sessions.find(id);
1552
1554
  state.picker = null;
1553
1555
  if (!saved) {
1554
1556
  state.modal = { title: "Resume", text: "Nao consegui abrir essa conversa." };