pokt-cli 1.0.7 → 1.0.8
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/dist/bin/pokt.js +1 -1
- package/dist/chat/loop.js +231 -6
- package/dist/chat/sessions.d.ts +18 -0
- package/dist/chat/sessions.js +120 -0
- package/dist/commands/chat.js +1 -1
- package/dist/ui.d.ts +6 -1
- package/dist/ui.js +114 -21
- package/package.json +1 -1
package/dist/bin/pokt.js
CHANGED
|
@@ -37,7 +37,7 @@ async function showMenu() {
|
|
|
37
37
|
const active = getEffectiveActiveModel();
|
|
38
38
|
const providerLabel = active ? (PROVIDER_LABELS[active.provider] ?? active.provider) : 'No provider';
|
|
39
39
|
console.log('');
|
|
40
|
-
|
|
40
|
+
await ui.printBanner({ animate: true });
|
|
41
41
|
console.log(ui.statusLine(providerLabel));
|
|
42
42
|
console.log('');
|
|
43
43
|
console.log(ui.separator());
|
package/dist/chat/loop.js
CHANGED
|
@@ -2,9 +2,11 @@ import prompts from 'prompts';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { ui } from '../ui.js';
|
|
4
4
|
import { runProFlow } from '../commands/pro.js';
|
|
5
|
+
import { PROVIDER_LABELS, ALL_PROVIDERS } from '../config.js';
|
|
5
6
|
import { config } from '../config.js';
|
|
6
7
|
import { getClient } from './client.js';
|
|
7
8
|
import { tools, executeTool } from './tools.js';
|
|
9
|
+
import { saveAuto, loadAuto, listCheckpoints, saveCheckpoint, loadCheckpoint, deleteCheckpoint, exportConversation, getSessionsDir } from './sessions.js';
|
|
8
10
|
import { connectMcpServer, getAllMcpToolsOpenAI, callMcpTool, isMcpTool, disconnectAllMcp, } from '../mcp/client.js';
|
|
9
11
|
const SYSTEM_PROMPT = `You are Pokt CLI, an elite AI Software Engineer.
|
|
10
12
|
Your goal is to help the user build, fix, and maintain software projects with high quality.
|
|
@@ -46,7 +48,8 @@ async function loadProjectStructure() {
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
export async function startChatLoop(modelConfig) {
|
|
49
|
-
|
|
51
|
+
let activeModel = modelConfig;
|
|
52
|
+
let client = await getClient(activeModel);
|
|
50
53
|
// Conectar servidores MCP configurados e montar lista de tools (nativas + MCP)
|
|
51
54
|
const mcpServers = config.get('mcpServers') ?? [];
|
|
52
55
|
for (const server of mcpServers) {
|
|
@@ -59,14 +62,174 @@ export async function startChatLoop(modelConfig) {
|
|
|
59
62
|
...tools,
|
|
60
63
|
...getAllMcpToolsOpenAI(),
|
|
61
64
|
];
|
|
62
|
-
const messages = [
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
const messages = [{ role: 'system', content: SYSTEM_PROMPT }];
|
|
66
|
+
// Auto-resume do projeto (estilo gemini): se existir, adiciona mensagens anteriores (sem duplicar system prompt)
|
|
67
|
+
const prev = loadAuto();
|
|
68
|
+
if (prev && prev.length > 0) {
|
|
69
|
+
for (const m of prev) {
|
|
70
|
+
if (m.role === 'system')
|
|
71
|
+
continue;
|
|
72
|
+
messages.push({ role: m.role, content: m.content });
|
|
73
|
+
}
|
|
74
|
+
console.log(ui.dim(`Sessão anterior carregada (projeto). Use /chat list | /chat save <tag>.`));
|
|
75
|
+
}
|
|
76
|
+
function modelLabel(m) {
|
|
77
|
+
const provider = PROVIDER_LABELS[m.provider] ?? m.provider;
|
|
78
|
+
return `[${provider}] ${m.id}`;
|
|
79
|
+
}
|
|
80
|
+
async function switchModelFlow(mode = 'model') {
|
|
81
|
+
const models = config.get('registeredModels') ?? [];
|
|
82
|
+
if (!Array.isArray(models) || models.length === 0) {
|
|
83
|
+
console.log(ui.error('Nenhum modelo registrado. Rode: pokt models list'));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const providerChoices = ALL_PROVIDERS.map((p) => {
|
|
87
|
+
const label = PROVIDER_LABELS[p] ?? p;
|
|
88
|
+
const hasAny = models.some((m) => m.provider === p);
|
|
89
|
+
const star = activeModel.provider === p ? '★ ' : '';
|
|
90
|
+
return {
|
|
91
|
+
title: `${star}${label}${hasAny ? '' : ' (sem modelos)'}`,
|
|
92
|
+
value: p,
|
|
93
|
+
disabled: !hasAny,
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
const providerPick = await prompts({
|
|
97
|
+
type: 'select',
|
|
98
|
+
name: 'provider',
|
|
99
|
+
message: mode === 'provider' ? 'Trocar provedor:' : 'Selecione o provedor:',
|
|
100
|
+
choices: [...providerChoices, { title: '🔙 Cancelar', value: 'cancel' }],
|
|
101
|
+
});
|
|
102
|
+
if (!providerPick.provider || providerPick.provider === 'cancel')
|
|
103
|
+
return;
|
|
104
|
+
const provider = providerPick.provider;
|
|
105
|
+
const providerModels = models.filter((m) => m.provider === provider);
|
|
106
|
+
if (providerModels.length === 0) {
|
|
107
|
+
console.log(ui.error(`Sem modelos para ${PROVIDER_LABELS[provider] ?? provider}.`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
let selected = null;
|
|
111
|
+
if (mode === 'provider') {
|
|
112
|
+
selected = providerModels[0];
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const pick = await prompts({
|
|
116
|
+
type: 'select',
|
|
117
|
+
name: 'idx',
|
|
118
|
+
message: `Modelos em ${PROVIDER_LABELS[provider] ?? provider}:`,
|
|
119
|
+
choices: [
|
|
120
|
+
...providerModels.map((m, i) => ({
|
|
121
|
+
title: `${activeModel.provider === m.provider && activeModel.id === m.id ? '★ ' : ''}${m.id}`,
|
|
122
|
+
value: i,
|
|
123
|
+
})),
|
|
124
|
+
{ title: '🔙 Cancelar', value: 'cancel' },
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
if (pick.idx === 'cancel' || typeof pick.idx !== 'number')
|
|
128
|
+
return;
|
|
129
|
+
selected = providerModels[pick.idx] ?? null;
|
|
130
|
+
}
|
|
131
|
+
if (!selected)
|
|
132
|
+
return;
|
|
133
|
+
// Validar chaves/credenciais necessárias (reaproveita mesma lógica do providerCommand / getClient)
|
|
134
|
+
try {
|
|
135
|
+
const newClient = await getClient(selected);
|
|
136
|
+
client = newClient;
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
console.log(ui.error(e?.message ?? String(e)));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
activeModel = selected;
|
|
143
|
+
config.set('activeModel', selected);
|
|
144
|
+
console.log(ui.success(`Modelo ativo atualizado: ${modelLabel(selected)}`));
|
|
145
|
+
console.log(ui.dim('Dica: o histórico do chat foi mantido; apenas o modelo/provedor mudou.'));
|
|
146
|
+
}
|
|
147
|
+
function printHelp() {
|
|
148
|
+
console.log(ui.dim(`
|
|
149
|
+
Comandos do chat:
|
|
150
|
+
${ui.accent('/help')} — mostra esta ajuda
|
|
151
|
+
${ui.accent('/clear')} — limpa a tela
|
|
152
|
+
${ui.accent('/status')} — mostra modelo/provider atual
|
|
153
|
+
${ui.accent('/model')} — trocar modelo (menu interativo)
|
|
154
|
+
${ui.accent('/provider')} — trocar provedor (usa o 1º modelo disponível)
|
|
155
|
+
${ui.accent('/chat')} — checkpoints/sessões (list/save/resume/delete/share)
|
|
156
|
+
${ui.accent('/resume')} — alias de /chat
|
|
157
|
+
${ui.accent('/copy')} — copia a última resposta do Pokt (Windows: clip)
|
|
158
|
+
${ui.accent('/pro')} — abrir Pokt Pro no navegador
|
|
159
|
+
${ui.accent('/quit')} ou ${ui.accent('exit')} — sair do chat
|
|
160
|
+
`));
|
|
161
|
+
}
|
|
162
|
+
let lastAssistantText = '';
|
|
163
|
+
async function handleChatCommand(raw) {
|
|
164
|
+
const parts = raw.trim().split(/\s+/);
|
|
165
|
+
const cmd = parts[0].toLowerCase();
|
|
166
|
+
const sub = (parts[1] ?? 'list').toLowerCase();
|
|
167
|
+
const arg = parts.slice(2).join(' ').trim();
|
|
168
|
+
if (sub === 'dir') {
|
|
169
|
+
console.log(ui.dim(`Sessões: ${getSessionsDir()}`));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (sub === 'list') {
|
|
173
|
+
const items = listCheckpoints();
|
|
174
|
+
if (items.length === 0) {
|
|
175
|
+
console.log(ui.dim('Nenhum checkpoint salvo ainda. Use: /chat save <tag>'));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
console.log(ui.dim('Checkpoints:'));
|
|
179
|
+
for (const it of items) {
|
|
180
|
+
console.log(`- ${ui.accent(it.tag)} ${it.updatedAt ? ui.dim(`(${it.updatedAt})`) : ''}`);
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (sub === 'save') {
|
|
185
|
+
if (!arg) {
|
|
186
|
+
console.log(ui.error('Uso: /chat save <tag>'));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
saveCheckpoint(arg, messages);
|
|
190
|
+
console.log(ui.success(`Checkpoint salvo: ${arg}`));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (sub === 'resume' || sub === 'load') {
|
|
194
|
+
if (!arg) {
|
|
195
|
+
console.log(ui.error('Uso: /chat resume <tag>'));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const loaded = loadCheckpoint(arg);
|
|
199
|
+
// mantém system prompt; substitui resto
|
|
200
|
+
const sys = messages[0];
|
|
201
|
+
messages.length = 0;
|
|
202
|
+
messages.push(sys);
|
|
203
|
+
for (const m of loaded) {
|
|
204
|
+
if (m.role === 'system')
|
|
205
|
+
continue;
|
|
206
|
+
messages.push({ role: m.role, content: m.content });
|
|
207
|
+
}
|
|
208
|
+
console.log(ui.success(`Checkpoint carregado: ${arg}`));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (sub === 'delete' || sub === 'rm') {
|
|
212
|
+
if (!arg) {
|
|
213
|
+
console.log(ui.error('Uso: /chat delete <tag>'));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
deleteCheckpoint(arg);
|
|
217
|
+
console.log(ui.success(`Checkpoint removido: ${arg}`));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (sub === 'share' || sub === 'export') {
|
|
221
|
+
const filename = arg || `pokt-chat-${Date.now()}.md`;
|
|
222
|
+
const out = exportConversation(filename, messages);
|
|
223
|
+
console.log(ui.success(`Exportado: ${out}`));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
console.log(ui.warn(`Subcomando desconhecido: ${sub}. Use /chat list|save|resume|delete|share`));
|
|
227
|
+
}
|
|
65
228
|
while (true) {
|
|
66
229
|
console.log('');
|
|
67
230
|
const cwd = process.cwd();
|
|
68
231
|
console.log(ui.dim(`Diretório atual: ${cwd}`));
|
|
69
|
-
console.log(ui.shortcutsLine(
|
|
232
|
+
console.log(ui.shortcutsLine(undefined, '? · /help · /pro'));
|
|
70
233
|
const response = await prompts({
|
|
71
234
|
type: 'text',
|
|
72
235
|
name: 'input',
|
|
@@ -82,6 +245,55 @@ export async function startChatLoop(modelConfig) {
|
|
|
82
245
|
}
|
|
83
246
|
const trimmed = userInput.trim();
|
|
84
247
|
const low = trimmed.toLowerCase();
|
|
248
|
+
if (low === '/help' || low === '/?' || low === 'help') {
|
|
249
|
+
printHelp();
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (low === '/clear') {
|
|
253
|
+
console.clear();
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (low === '/status') {
|
|
257
|
+
console.log(ui.statusBar({ cwd: process.cwd(), model: `/model ${activeModel.provider} (${activeModel.id})` }));
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (low.startsWith('/chat') || low.startsWith('/resume')) {
|
|
261
|
+
await handleChatCommand(trimmed.replace(/^\/resume/i, '/chat'));
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (low === '/copy') {
|
|
265
|
+
try {
|
|
266
|
+
if (!lastAssistantText) {
|
|
267
|
+
console.log(ui.warn('Nada para copiar ainda.'));
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
// Sem interpolar conteúdo no comando (evita quebra por aspas/newlines).
|
|
271
|
+
// Escreve para um arquivo temporário e copia via Get-Content | clip (Windows).
|
|
272
|
+
const tmp = `.pokt_copy_${Date.now()}.txt`;
|
|
273
|
+
await executeTool('write_file', JSON.stringify({ path: tmp, content: lastAssistantText }));
|
|
274
|
+
await executeTool('run_command', JSON.stringify({ command: `powershell -NoProfile -Command "Get-Content -Raw '${tmp}' | clip"` }));
|
|
275
|
+
// best-effort cleanup
|
|
276
|
+
try {
|
|
277
|
+
await executeTool('delete_file', JSON.stringify({ path: tmp }));
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// ignore
|
|
281
|
+
}
|
|
282
|
+
console.log(ui.success('Copiado para a área de transferência.'));
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
console.log(ui.warn('Falha ao copiar. Se estiver no Windows, verifique se o comando "clip" está disponível.'));
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (low === '/model') {
|
|
290
|
+
await switchModelFlow('model');
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (low === '/provider') {
|
|
294
|
+
await switchModelFlow('provider');
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
85
297
|
if (low === '/pro' || low === '/torne-se-pro' || low === 'torne-se pro') {
|
|
86
298
|
runProFlow();
|
|
87
299
|
continue;
|
|
@@ -91,10 +303,12 @@ export async function startChatLoop(modelConfig) {
|
|
|
91
303
|
Atalhos:
|
|
92
304
|
${ui.accent('/pro')} ou ${ui.accent('/torne-se-pro')} — abrir Pokt Pro no navegador (pagamento + chave)
|
|
93
305
|
exit, ${ui.accent('/quit')} — sair do chat
|
|
306
|
+
${ui.accent('/help')} — ver comandos do chat
|
|
94
307
|
`));
|
|
95
308
|
continue;
|
|
96
309
|
}
|
|
97
310
|
messages.push({ role: 'user', content: userInput });
|
|
311
|
+
saveAuto(messages);
|
|
98
312
|
// Primeiro o modelo vê o pedido; depois carregamos a estrutura do projeto para ele entender e então criar/editar
|
|
99
313
|
const isFirstUserMessage = messages.filter(m => m.role === 'user').length === 1;
|
|
100
314
|
if (isFirstUserMessage) {
|
|
@@ -103,7 +317,18 @@ Atalhos:
|
|
|
103
317
|
loadSpinner.stop();
|
|
104
318
|
messages.push({ role: 'system', content: `Current Project Structure:\n${projectStructure}` });
|
|
105
319
|
}
|
|
106
|
-
await processLLMResponse(client,
|
|
320
|
+
await processLLMResponse(client, activeModel.id, messages, allTools);
|
|
321
|
+
// Atualiza auto-save após resposta
|
|
322
|
+
saveAuto(messages);
|
|
323
|
+
// Captura última resposta do assistente para /copy (melhor esforço)
|
|
324
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
325
|
+
const m = messages[i];
|
|
326
|
+
if (m?.role === 'assistant') {
|
|
327
|
+
const c = m.content;
|
|
328
|
+
lastAssistantText = typeof c === 'string' ? c : Array.isArray(c) ? c.map((p) => (p && p.text ? p.text : String(p))).join('') : String(c ?? '');
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
107
332
|
}
|
|
108
333
|
await disconnectAllMcp();
|
|
109
334
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions/completions.js';
|
|
2
|
+
type PersistedMessage = {
|
|
3
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
4
|
+
content: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function getProjectHash(cwd?: string): string;
|
|
7
|
+
export declare function getSessionsDir(): string;
|
|
8
|
+
export declare function saveAuto(messages: ChatCompletionMessageParam[]): void;
|
|
9
|
+
export declare function loadAuto(): PersistedMessage[] | null;
|
|
10
|
+
export declare function listCheckpoints(): Array<{
|
|
11
|
+
tag: string;
|
|
12
|
+
updatedAt?: string;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function saveCheckpoint(tag: string, messages: ChatCompletionMessageParam[]): void;
|
|
15
|
+
export declare function loadCheckpoint(tag: string): PersistedMessage[];
|
|
16
|
+
export declare function deleteCheckpoint(tag: string): void;
|
|
17
|
+
export declare function exportConversation(filename: string, messages: ChatCompletionMessageParam[]): string;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
function toPersisted(messages) {
|
|
6
|
+
return messages
|
|
7
|
+
.filter((m) => !!m && typeof m === 'object' && 'role' in m)
|
|
8
|
+
.map((m) => {
|
|
9
|
+
const role = m.role;
|
|
10
|
+
const content = m.content;
|
|
11
|
+
if (typeof content === 'string')
|
|
12
|
+
return { role, content };
|
|
13
|
+
if (Array.isArray(content)) {
|
|
14
|
+
const text = content
|
|
15
|
+
.map((part) => (part && typeof part === 'object' && 'text' in part ? String(part.text) : String(part)))
|
|
16
|
+
.join('');
|
|
17
|
+
return { role, content: text };
|
|
18
|
+
}
|
|
19
|
+
return { role, content: content != null ? String(content) : '' };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function ensureDir(p) {
|
|
23
|
+
fs.mkdirSync(p, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
function safeTag(tag) {
|
|
26
|
+
return tag.trim().replace(/[<>:"/\\|?*\x00-\x1F]/g, '_').slice(0, 80);
|
|
27
|
+
}
|
|
28
|
+
export function getProjectHash(cwd = process.cwd()) {
|
|
29
|
+
const h = crypto.createHash('sha1');
|
|
30
|
+
h.update(path.resolve(cwd));
|
|
31
|
+
return h.digest('hex').slice(0, 12);
|
|
32
|
+
}
|
|
33
|
+
export function getSessionsDir() {
|
|
34
|
+
// compatível Windows/mac/linux
|
|
35
|
+
const base = path.join(os.homedir(), '.pokt', 'tmp');
|
|
36
|
+
const proj = getProjectHash();
|
|
37
|
+
const dir = path.join(base, proj);
|
|
38
|
+
ensureDir(dir);
|
|
39
|
+
return dir;
|
|
40
|
+
}
|
|
41
|
+
function getAutoPath() {
|
|
42
|
+
return path.join(getSessionsDir(), 'auto.json');
|
|
43
|
+
}
|
|
44
|
+
export function saveAuto(messages) {
|
|
45
|
+
const payload = {
|
|
46
|
+
updatedAt: new Date().toISOString(),
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
messages: toPersisted(messages),
|
|
49
|
+
};
|
|
50
|
+
fs.writeFileSync(getAutoPath(), JSON.stringify(payload, null, 2), 'utf8');
|
|
51
|
+
}
|
|
52
|
+
export function loadAuto() {
|
|
53
|
+
try {
|
|
54
|
+
const raw = fs.readFileSync(getAutoPath(), 'utf8');
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
return Array.isArray(parsed?.messages) ? parsed.messages : null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function tagPath(tag) {
|
|
63
|
+
return path.join(getSessionsDir(), `checkpoint.${safeTag(tag)}.json`);
|
|
64
|
+
}
|
|
65
|
+
export function listCheckpoints() {
|
|
66
|
+
const dir = getSessionsDir();
|
|
67
|
+
const files = fs.readdirSync(dir).filter((f) => f.startsWith('checkpoint.') && f.endsWith('.json'));
|
|
68
|
+
return files
|
|
69
|
+
.map((f) => {
|
|
70
|
+
const tag = f.replace(/^checkpoint\./, '').replace(/\.json$/, '');
|
|
71
|
+
try {
|
|
72
|
+
const raw = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
return { tag, updatedAt: parsed.updatedAt };
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { tag };
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.sort((a, b) => String(b.updatedAt ?? '').localeCompare(String(a.updatedAt ?? '')));
|
|
81
|
+
}
|
|
82
|
+
export function saveCheckpoint(tag, messages) {
|
|
83
|
+
const t = safeTag(tag);
|
|
84
|
+
if (!t)
|
|
85
|
+
throw new Error('Tag inválida.');
|
|
86
|
+
const payload = {
|
|
87
|
+
tag: t,
|
|
88
|
+
updatedAt: new Date().toISOString(),
|
|
89
|
+
cwd: process.cwd(),
|
|
90
|
+
messages: toPersisted(messages),
|
|
91
|
+
};
|
|
92
|
+
fs.writeFileSync(tagPath(t), JSON.stringify(payload, null, 2), 'utf8');
|
|
93
|
+
}
|
|
94
|
+
export function loadCheckpoint(tag) {
|
|
95
|
+
const t = safeTag(tag);
|
|
96
|
+
const raw = fs.readFileSync(tagPath(t), 'utf8');
|
|
97
|
+
const parsed = JSON.parse(raw);
|
|
98
|
+
if (!Array.isArray(parsed?.messages))
|
|
99
|
+
throw new Error('Checkpoint inválido.');
|
|
100
|
+
return parsed.messages;
|
|
101
|
+
}
|
|
102
|
+
export function deleteCheckpoint(tag) {
|
|
103
|
+
const t = safeTag(tag);
|
|
104
|
+
fs.unlinkSync(tagPath(t));
|
|
105
|
+
}
|
|
106
|
+
export function exportConversation(filename, messages) {
|
|
107
|
+
const out = path.isAbsolute(filename) ? filename : path.join(process.cwd(), filename);
|
|
108
|
+
const ext = path.extname(out).toLowerCase();
|
|
109
|
+
if (ext === '.json') {
|
|
110
|
+
fs.writeFileSync(out, JSON.stringify({ exportedAt: new Date().toISOString(), messages: toPersisted(messages) }, null, 2), 'utf8');
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
// markdown
|
|
114
|
+
const md = toPersisted(messages)
|
|
115
|
+
.filter((m) => m.role !== 'system')
|
|
116
|
+
.map((m) => `## ${m.role}\n\n${m.content}\n`)
|
|
117
|
+
.join('\n');
|
|
118
|
+
fs.writeFileSync(out, `# Pokt CLI chat export\n\nExportado em: ${new Date().toISOString()}\n\n${md}`, 'utf8');
|
|
119
|
+
return out;
|
|
120
|
+
}
|
package/dist/commands/chat.js
CHANGED
|
@@ -27,7 +27,7 @@ export const chatCommand = {
|
|
|
27
27
|
}
|
|
28
28
|
// Se veio do menu interativo, não repetir banner/tips (já foram exibidos)
|
|
29
29
|
if (!fromMenu) {
|
|
30
|
-
|
|
30
|
+
await ui.printBanner({ animate: true });
|
|
31
31
|
console.log(ui.statusLine(`[${activeModel.provider}] ${activeModel.id}`));
|
|
32
32
|
console.log('');
|
|
33
33
|
console.log(ui.tips());
|
package/dist/ui.d.ts
CHANGED
|
@@ -9,8 +9,13 @@ export declare const ui: {
|
|
|
9
9
|
labelPokt: () => any;
|
|
10
10
|
accent: (text: string) => any;
|
|
11
11
|
muted: (text: string) => any;
|
|
12
|
-
/** Banner principal
|
|
12
|
+
/** Banner principal em ASCII (Pokt CLI) */
|
|
13
13
|
banner: (customVersion?: string) => string;
|
|
14
|
+
/** Imprime o banner com “typewriter” (letra por letra) */
|
|
15
|
+
printBanner: (opts?: {
|
|
16
|
+
animate?: boolean;
|
|
17
|
+
version?: string;
|
|
18
|
+
}) => Promise<void>;
|
|
14
19
|
/** Status de login / provider (uma linha) */
|
|
15
20
|
statusLine: (providerLabel: string, configPath?: string) => any;
|
|
16
21
|
/** Seção "Tips for getting started" */
|
package/dist/ui.js
CHANGED
|
@@ -2,15 +2,101 @@ import chalk from 'chalk';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
function lerp(a, b, t) {
|
|
9
|
+
return a + (b - a) * t;
|
|
10
|
+
}
|
|
11
|
+
function gradientColor(idx, len, start, end) {
|
|
12
|
+
if (len <= 1)
|
|
13
|
+
return start;
|
|
14
|
+
const t = idx / (len - 1);
|
|
15
|
+
return [
|
|
16
|
+
Math.round(lerp(start[0], end[0], t)),
|
|
17
|
+
Math.round(lerp(start[1], end[1], t)),
|
|
18
|
+
Math.round(lerp(start[2], end[2], t)),
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
function gradientChars(text, start, end) {
|
|
22
|
+
const chars = [...text];
|
|
23
|
+
return chars.map((ch, i) => {
|
|
24
|
+
if (ch === ' ')
|
|
25
|
+
return ' ';
|
|
26
|
+
const [r, g, b] = gradientColor(i, chars.length, start, end);
|
|
27
|
+
return chalk.rgb(r, g, b).bold(ch);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function getVersion() {
|
|
31
|
+
// Prioridade: env de empacotamento → package.json → fallback
|
|
32
|
+
const envVer = process.env.POKT_CLI_VERSION || process.env.npm_package_version;
|
|
33
|
+
if (envVer)
|
|
34
|
+
return envVer;
|
|
35
|
+
try {
|
|
36
|
+
const pkgPath = path.resolve(process.cwd(), 'package.json');
|
|
37
|
+
if (fs.existsSync(pkgPath)) {
|
|
38
|
+
const raw = fs.readFileSync(pkgPath, 'utf8');
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
if (parsed?.version)
|
|
41
|
+
return parsed.version;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// ignore
|
|
46
|
+
}
|
|
47
|
+
return 'dev';
|
|
48
|
+
}
|
|
49
|
+
function bannerLines(ver) {
|
|
50
|
+
const left = [
|
|
51
|
+
'██████╗ ██████╗ ██╗ ██╗████████╗',
|
|
52
|
+
'██╔══██╗██╔═══██╗██║ ██╔╝╚══██╔══╝',
|
|
53
|
+
'██████╔╝██║ ██║█████╔╝ ██║',
|
|
54
|
+
'██╔═══╝ ██║ ██║██╔═██╗ ██║',
|
|
55
|
+
'██║ ╚██████╔╝██║ ██╗ ██║',
|
|
56
|
+
'╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝',
|
|
57
|
+
];
|
|
58
|
+
const right = [
|
|
59
|
+
' ██████╗██╗ ██╗',
|
|
60
|
+
' ██╔════╝██║ ██║',
|
|
61
|
+
' ██║ ██║ ██║',
|
|
62
|
+
' ██║ ██║ ██║',
|
|
63
|
+
' ╚██████╗███████╗██║',
|
|
64
|
+
' ╚═════╝╚══════╝╚═╝',
|
|
65
|
+
];
|
|
66
|
+
const lines = left.map((l, i) => `${l}${right[i] ?? ''}`);
|
|
67
|
+
lines.push(' '.repeat(18) + `CLI Version v${ver}`);
|
|
68
|
+
return lines;
|
|
69
|
+
}
|
|
70
|
+
function bannerAscii(ver) {
|
|
71
|
+
const start = [0, 205, 255]; // azul/ciano
|
|
72
|
+
const end = [155, 89, 255]; // roxo
|
|
73
|
+
const lines = bannerLines(ver);
|
|
74
|
+
return lines
|
|
75
|
+
.map((line, i) => {
|
|
76
|
+
if (i === lines.length - 1)
|
|
77
|
+
return chalk.gray(line);
|
|
78
|
+
return gradientChars(line, start, end).join('');
|
|
79
|
+
})
|
|
80
|
+
.join('\n');
|
|
81
|
+
}
|
|
82
|
+
async function printBannerAnimated(customVersion) {
|
|
83
|
+
const ver = customVersion ?? getVersion();
|
|
84
|
+
const start = [0, 205, 255];
|
|
85
|
+
const end = [155, 89, 255];
|
|
86
|
+
const lines = bannerLines(ver);
|
|
87
|
+
for (let li = 0; li < lines.length; li++) {
|
|
88
|
+
const line = lines[li] ?? '';
|
|
89
|
+
if (li === lines.length - 1) {
|
|
90
|
+
process.stdout.write(chalk.gray(line) + '\n');
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const colored = gradientChars(line, start, end);
|
|
94
|
+
for (let i = 0; i <= colored.length; i++) {
|
|
95
|
+
process.stdout.write('\r' + colored.slice(0, i).join(''));
|
|
96
|
+
await sleep(1);
|
|
97
|
+
}
|
|
98
|
+
process.stdout.write('\n');
|
|
99
|
+
}
|
|
14
100
|
}
|
|
15
101
|
export const ui = {
|
|
16
102
|
title: (text) => chalk.whiteBright.bold(text),
|
|
@@ -19,33 +105,40 @@ export const ui = {
|
|
|
19
105
|
error: (text) => chalk.red(text),
|
|
20
106
|
warn: (text) => chalk.yellow(text),
|
|
21
107
|
dim: (text) => chalk.gray(text),
|
|
22
|
-
labelYou: () => chalk.cyan('
|
|
108
|
+
labelYou: () => chalk.cyan('Você:'),
|
|
23
109
|
labelPokt: () => chalk.green('Pokt:'),
|
|
24
110
|
accent: (text) => chalk.blue(text),
|
|
25
111
|
muted: (text) => chalk.gray(text),
|
|
26
|
-
/** Banner principal
|
|
112
|
+
/** Banner principal em ASCII (Pokt CLI) */
|
|
27
113
|
banner: (customVersion) => {
|
|
28
|
-
const ver = customVersion ??
|
|
29
|
-
|
|
30
|
-
|
|
114
|
+
const ver = customVersion ?? getVersion();
|
|
115
|
+
return bannerAscii(ver);
|
|
116
|
+
},
|
|
117
|
+
/** Imprime o banner com “typewriter” (letra por letra) */
|
|
118
|
+
printBanner: async (opts) => {
|
|
119
|
+
if (opts?.animate) {
|
|
120
|
+
await printBannerAnimated(opts.version);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
console.log(ui.banner(opts?.version));
|
|
31
124
|
},
|
|
32
125
|
/** Status de login / provider (uma linha) */
|
|
33
126
|
statusLine: (providerLabel, configPath = '/config') => {
|
|
34
|
-
const auth = chalk.gray(`
|
|
127
|
+
const auth = chalk.gray(`Ativo: ${providerLabel} ${chalk.underline(configPath)}`);
|
|
35
128
|
return auth;
|
|
36
129
|
},
|
|
37
130
|
/** Seção "Tips for getting started" */
|
|
38
131
|
tips: () => {
|
|
39
|
-
const title = chalk.white('
|
|
132
|
+
const title = chalk.white('Dicas para começar:');
|
|
40
133
|
const tips = [
|
|
41
|
-
chalk.gray('1. /help
|
|
42
|
-
chalk.gray('2.
|
|
43
|
-
chalk.gray('3.
|
|
134
|
+
chalk.gray('1. Digite /help para ver comandos'),
|
|
135
|
+
chalk.gray('2. Peça ajuda para codar, editar arquivos ou rodar comandos'),
|
|
136
|
+
chalk.gray('3. Seja específico para melhores resultados'),
|
|
44
137
|
].join('\n');
|
|
45
138
|
return title + '\n' + tips;
|
|
46
139
|
},
|
|
47
140
|
/** Linha de atalhos acima do input */
|
|
48
|
-
shortcutsLine: (left = 'shift+tab
|
|
141
|
+
shortcutsLine: (left = 'shift+tab para aceitar edições', right = '? para atalhos', center) => {
|
|
49
142
|
const l = chalk.gray(left);
|
|
50
143
|
const r = chalk.gray(right);
|
|
51
144
|
const c = center ? chalk.gray(center) : '';
|
|
@@ -55,7 +148,7 @@ export const ui = {
|
|
|
55
148
|
return l + ' '.repeat(Math.max(0, 60 - left.length - right.length)) + r;
|
|
56
149
|
},
|
|
57
150
|
/** Placeholder do input */
|
|
58
|
-
inputPlaceholder: () => chalk.gray('
|
|
151
|
+
inputPlaceholder: () => chalk.gray('Digite sua mensagem (ou /help)'),
|
|
59
152
|
/** Barra de status inferior: path, branch, sandbox, model */
|
|
60
153
|
statusBar: (opts) => {
|
|
61
154
|
const cwd = opts.cwd ?? process.cwd();
|