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 +15 -8
- package/bin/kafox.js +24 -8
- package/package.json +1 -1
- package/src/sessions.js +34 -0
- package/src/tui.js +8 -6
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.
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
70
|
-
|
|
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
|
-
/**
|
|
29
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
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
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
|
-
|
|
1172
|
-
|
|
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) -
|
|
1175
|
-
const
|
|
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.
|
|
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." };
|