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 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
- console.log(ui.banner());
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
- const client = await getClient(modelConfig);
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
- { role: 'system', content: SYSTEM_PROMPT }
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('shift+tab to accept edits', '? · /pro (Torne-se Pro)'));
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, modelConfig.id, messages, allTools);
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
+ }
@@ -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
- console.log(ui.banner());
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 estilo Gemini CLI: logo + nome + versão */
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
- const VERSION = '1.0.5';
6
- /** Logo em estilo chevron com gradiente (azul → rosa → roxo) */
7
- function logo() {
8
- const c = (s, color) => color(s);
9
- const block = (char, col) => col(char);
10
- const blue = chalk.rgb(100, 149, 237);
11
- const pink = chalk.rgb(255, 105, 180);
12
- const purple = chalk.rgb(147, 112, 219);
13
- return block('▸', blue) + ' ' + block('▸', pink) + ' ' + block('▸', purple);
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('You:'),
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 estilo Gemini CLI: logo + nome + versão */
112
+ /** Banner principal em ASCII (Pokt CLI) */
27
113
  banner: (customVersion) => {
28
- const ver = customVersion ?? VERSION;
29
- const line1 = logo() + ' ' + chalk.whiteBright.bold('Pokt CLI') + chalk.gray(` v${ver}`);
30
- return line1;
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(`Logged in with ${providerLabel} ${chalk.underline(configPath)}`);
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('Tips for getting started:');
132
+ const title = chalk.white('Dicas para começar:');
40
133
  const tips = [
41
- chalk.gray('1. /help for more information'),
42
- chalk.gray('2. Ask coding questions, edit code or run commands'),
43
- chalk.gray('3. Be specific for the best results'),
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 to accept edits', right = '? for shortcuts', center) => {
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('Type your message or @path/to/file'),
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pokt-cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Vibe Coding AI CLI for OpenRouter and Ollama",
5
5
  "main": "./dist/bin/pokt.js",
6
6
  "type": "module",