tibiaway-ai 1.0.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.
@@ -0,0 +1,94 @@
1
+ import inquirer from 'inquirer';
2
+ import ora from 'ora';
3
+ import chalk from 'chalk';
4
+ import { getCharacter } from '../utils/config.js';
5
+ import { askClaude } from '../utils/api.js';
6
+ import { error, highlight, separator } from '../utils/banner.js';
7
+
8
+ export async function handleSet() {
9
+ const character = getCharacter();
10
+ if (!character) {
11
+ error('No hay personaje configurado. Ejecuta: tibiaway setup');
12
+ return;
13
+ }
14
+
15
+ console.log(chalk.hex('#FF6B1A').bold('πŸ›‘ SET & IMBUEMENTS ADVISOR\n'));
16
+
17
+ const answers = await inquirer.prompt([
18
+ {
19
+ type: 'list',
20
+ name: 'situacion',
21
+ message: highlight('ΒΏPara quΓ© situaciΓ³n quieres armar el set?'),
22
+ choices: [
23
+ { name: 'Hunting diario (grinding)', value: 'hunting' },
24
+ { name: 'Boss run', value: 'boss' },
25
+ { name: 'PvP / Battlegrounds', value: 'pvp' },
26
+ { name: 'Training / offline training', value: 'training' },
27
+ { name: 'Quest especΓ­fica', value: 'quest' },
28
+ ],
29
+ },
30
+ {
31
+ type: 'list',
32
+ name: 'budget',
33
+ message: highlight('ΒΏCuΓ‘l es tu presupuesto aproximado?'),
34
+ choices: [
35
+ { name: 'BΓ‘sico (sin gastar mucho)', value: 'bajo' },
36
+ { name: 'Moderado (100k - 1M gold)', value: 'medio' },
37
+ { name: 'Competitivo (1M - 10M gold)', value: 'alto' },
38
+ { name: 'Sin lΓ­mite (best in slot)', value: 'bis' },
39
+ ],
40
+ },
41
+ {
42
+ type: 'input',
43
+ name: 'zona',
44
+ message: highlight('ΒΏPara quΓ© zona o criaturas? (Enter para genΓ©rico):'),
45
+ },
46
+ {
47
+ type: 'input',
48
+ name: 'items_actuales',
49
+ message: highlight('ΒΏQuΓ© equipo tienes actualmente? (Enter si no sabes/no importa):'),
50
+ },
51
+ ]);
52
+
53
+ const prompt = `Arma el set Γ³ptimo para mi personaje en esta situaciΓ³n.
54
+
55
+ SituaciΓ³n: ${answers.situacion}
56
+ Presupuesto: ${answers.budget}
57
+ ${answers.zona ? `Zona/criaturas: ${answers.zona}` : ''}
58
+ ${answers.items_actuales ? `Equipo actual: ${answers.items_actuales}` : ''}
59
+
60
+ Dame:
61
+ 1. SET RECOMENDADO - slot por slot:
62
+ - Helmet / Armor / Legs / Boots / Shield (si aplica) / Weapon / Offhand / Amulet / Ring / Backpack
63
+
64
+ 2. IMBUEMENTS - por cada pieza que aplique:
65
+ - QuΓ© imbu poner y por quΓ©
66
+ - VersiΓ³n econΓ³mica vs versiΓ³n ideal
67
+
68
+ 3. SUPPLIES recomendados por hora de hunt:
69
+ - Potiones (tipo y cantidad)
70
+ - Runes (si aplica)
71
+ - Food
72
+ - Otros consumibles importantes
73
+
74
+ 4. ALTERNATIVAS BUDGET-FRIENDLY:
75
+ - Opciones mΓ‘s baratas para cada slot crΓ­tico
76
+
77
+ SΓ© especΓ­fico con nombres de Γ­tems. Explica brevemente el por quΓ© de cada elecciΓ³n importante.`;
78
+
79
+ console.log('');
80
+ const spinner = ora({ text: 'Armando tu set Γ³ptimo...', color: 'yellow' }).start();
81
+
82
+ try {
83
+ const response = await askClaude(prompt);
84
+ spinner.stop();
85
+ separator();
86
+ console.log('');
87
+ console.log(chalk.hex('#f0f0f0')(response));
88
+ console.log('');
89
+ separator();
90
+ } catch (err) {
91
+ spinner.stop();
92
+ error(`Error al consultar la IA: ${err.message}`);
93
+ }
94
+ }
@@ -0,0 +1,14 @@
1
+ import chalk from 'chalk';
2
+ import { syncCharacter } from '../utils/character.js';
3
+ import { success, error, printCharacterCard } from '../utils/banner.js';
4
+
5
+ export async function handleSync() {
6
+ console.log(chalk.hex('#888888')('Actualizando perfil desde tibiadata.com...\n'));
7
+
8
+ const updated = await syncCharacter();
9
+ if (!updated) return;
10
+
11
+ printCharacterCard(updated);
12
+ console.log('');
13
+ success(`Perfil actualizado: ${chalk.hex('#FF6B1A').bold(updated.name)} | Level ${chalk.hex('#FF6B1A').bold(updated.level)} | ${updated.vocation}`);
14
+ }
@@ -0,0 +1,78 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import { showBanner, success, error, info, highlight, separator } from './utils/banner.js';
4
+ import { setupCharacter } from './utils/character.js';
5
+ import { setApiKey, getApiKey, writeConfig, readConfig } from './utils/config.js';
6
+
7
+ export async function runInstaller() {
8
+ showBanner();
9
+
10
+ console.log(chalk.hex('#f0f0f0')('Bienvenido a TibiaWayAI. Vamos a configurar tu asistente.\n'));
11
+ separator();
12
+ console.log('');
13
+
14
+ // Step 1: API Key
15
+ const existingKey = getApiKey();
16
+ let apiKey = existingKey;
17
+
18
+ if (!existingKey) {
19
+ info('Necesitas una API key de Anthropic para usar TibiaWayAI.');
20
+ info('ObtΓ©n la tuya en: https://console.anthropic.com');
21
+ console.log('');
22
+
23
+ const { key } = await inquirer.prompt([
24
+ {
25
+ type: 'password',
26
+ name: 'key',
27
+ message: highlight('Ingresa tu Anthropic API key:'),
28
+ mask: '*',
29
+ validate: (input) => {
30
+ if (!input.trim()) return 'La API key no puede estar vacΓ­a.';
31
+ if (!input.trim().startsWith('sk-')) return 'La API key debe empezar con "sk-".';
32
+ return true;
33
+ },
34
+ },
35
+ ]);
36
+
37
+ apiKey = key.trim();
38
+ setApiKey(apiKey);
39
+ success('API key guardada correctamente.');
40
+ console.log('');
41
+ } else {
42
+ success('API key ya configurada.');
43
+ console.log('');
44
+ }
45
+
46
+ // Step 2: Character setup
47
+ const config = readConfig();
48
+ if (!config.character) {
49
+ separator();
50
+ console.log('');
51
+ info('Ahora vamos a vincular tu personaje de Tibia.');
52
+ console.log('');
53
+ await setupCharacter();
54
+ } else {
55
+ success(`Personaje ya configurado: ${highlight(config.character.name)} (${config.character.vocation} lv ${config.character.level})`);
56
+ }
57
+
58
+ console.log('');
59
+ separator();
60
+ console.log('');
61
+ console.log(chalk.hex('#FF6B1A').bold('Β‘TibiaWayAI estΓ‘ listo!'));
62
+ console.log('');
63
+ console.log(chalk.hex('#f0f0f0')('Comandos disponibles:'));
64
+ const cmds = [
65
+ ['tibiaway hunt', 'Sugiere dΓ³nde cazar'],
66
+ ['tibiaway profit', 'Calcula profit y waste'],
67
+ ['tibiaway quest', 'Recomienda quests'],
68
+ ['tibiaway set', 'Arma tu equipment'],
69
+ ['tibiaway boss <nombre>', 'MecΓ‘nicas de boss'],
70
+ ['tibiaway diary', 'Diario de progreso'],
71
+ ['tibiaway analyze <archivo>', 'Analiza screenshots'],
72
+ ['tibiaway sync', 'Actualiza tu perfil'],
73
+ ];
74
+ for (const [cmd, desc] of cmds) {
75
+ console.log(` ${chalk.hex('#FF6B1A')(cmd.padEnd(28))} ${chalk.hex('#888888')(desc)}`);
76
+ }
77
+ console.log('');
78
+ }
@@ -0,0 +1,129 @@
1
+ import chalk from 'chalk';
2
+ import { isConfigured, getCharacter } from './utils/config.js';
3
+ import { showBanner, showMiniLogo, error, info, highlight } from './utils/banner.js';
4
+ import { runInstaller } from './installer.js';
5
+ import { handleHunt } from './commands/hunt.js';
6
+ import { handleProfit } from './commands/profit.js';
7
+ import { handleQuest } from './commands/quest.js';
8
+ import { handleSet } from './commands/set.js';
9
+ import { handleBoss } from './commands/boss.js';
10
+ import { handleDiary } from './commands/diary.js';
11
+ import { handleAnalyze } from './commands/analyze.js';
12
+ import { handleSync } from './commands/sync.js';
13
+
14
+ const COMMANDS = {
15
+ hunt: handleHunt,
16
+ profit: handleProfit,
17
+ quest: handleQuest,
18
+ set: handleSet,
19
+ boss: handleBoss,
20
+ diary: handleDiary,
21
+ analyze: handleAnalyze,
22
+ sync: handleSync,
23
+ setup: runInstaller,
24
+ install: runInstaller,
25
+ };
26
+
27
+ function printWelcome() {
28
+ showBanner();
29
+ const character = getCharacter();
30
+ if (character) {
31
+ console.log(chalk.hex('#FFD700')(' Personaje activo:'));
32
+ console.log(` ${chalk.hex('#FF6B1A').bold(character.name)} ${chalk.hex('#888888')(`Β· ${character.vocation} Β· Level ${character.level} Β· ${character.world}`)}`);
33
+ console.log('');
34
+ }
35
+ console.log(chalk.hex('#888888')(' Escribe un comando para empezar:\n'));
36
+ const quick = [
37
+ ['tibiaway hunt', 'Buscar spots de caza'],
38
+ ['tibiaway profit', 'Calcular profit de sesiΓ³n'],
39
+ ['tibiaway boss <nombre>', 'Ver mecΓ‘nicas de boss'],
40
+ ['tibiaway help', 'Ver todos los comandos'],
41
+ ];
42
+ for (const [cmd, desc] of quick) {
43
+ console.log(` ${chalk.hex('#FF6B1A')(cmd.padEnd(28))} ${chalk.hex('#555555')(desc)}`);
44
+ }
45
+ console.log('');
46
+ }
47
+
48
+ function printHelp() {
49
+ showBanner();
50
+ console.log(chalk.hex('#f0f0f0')('Uso: tibiaway <comando> [opciones]\n'));
51
+ console.log(chalk.hex('#FFD700')('Comandos:\n'));
52
+
53
+ const cmds = [
54
+ ['hunt', 'Sugiere hunting spots para tu nivel y vocaciΓ³n'],
55
+ ['profit', 'Calcula profit, waste y exp por hora'],
56
+ ['quest', 'Recomienda quests segΓΊn tus objetivos'],
57
+ ['set', 'Arma tu equipment e imbuements'],
58
+ ['boss <nombre>', 'MecΓ‘nicas y estrategia para un boss'],
59
+ ['diary', 'Diario de progreso (add / show / summary)'],
60
+ ['diary add', 'Agrega una entrada al diario'],
61
+ ['diary show', 'Muestra el historial'],
62
+ ['diary summary', 'Resumen de progreso con IA'],
63
+ ['analyze <archivo>', 'Analiza screenshot o notas (.png, .jpg, .txt)'],
64
+ ['sync', 'Actualiza tu perfil desde tibiadata.com'],
65
+ ['setup', 'ConfiguraciΓ³n inicial (API key + personaje)'],
66
+ ];
67
+
68
+ for (const [cmd, desc] of cmds) {
69
+ console.log(` ${chalk.hex('#FF6B1A')(('tibiaway ' + cmd).padEnd(30))} ${chalk.hex('#888888')(desc)}`);
70
+ }
71
+
72
+ const character = getCharacter();
73
+ if (character) {
74
+ console.log('');
75
+ console.log(chalk.hex('#444444')('─'.repeat(60)));
76
+ console.log(` ${chalk.hex('#FFD700')('Personaje activo:')} ${chalk.white(character.name)} ${chalk.hex('#888888')(`(${character.vocation} lv ${character.level} - ${character.world})`)}`);
77
+ }
78
+
79
+ console.log('');
80
+ }
81
+
82
+ export async function run(args) {
83
+ const [command, ...rest] = args;
84
+
85
+ // Sin argumentos: si no estΓ‘ configurado β†’ installer, si estΓ‘ β†’ welcome screen
86
+ if (!command) {
87
+ if (!isConfigured()) {
88
+ await runInstaller();
89
+ } else {
90
+ printWelcome();
91
+ }
92
+ return;
93
+ }
94
+
95
+ if (command === 'help' || command === '--help' || command === '-h') {
96
+ printHelp();
97
+ return;
98
+ }
99
+
100
+ if (command === 'setup' || command === 'install') {
101
+ await runInstaller();
102
+ return;
103
+ }
104
+
105
+ // Auto-setup si falta config y no es sync
106
+ if (!isConfigured() && command !== 'sync') {
107
+ info('Primera vez que ejecutas TibiaWayAI. Vamos a configurarlo.');
108
+ console.log('');
109
+ await runInstaller();
110
+ return;
111
+ }
112
+
113
+ const handler = COMMANDS[command];
114
+ if (!handler) {
115
+ error(`Comando desconocido: "${command}"`);
116
+ info('Ejecuta tibiaway --help para ver los comandos disponibles.');
117
+ process.exit(1);
118
+ }
119
+
120
+ if (command === 'boss') {
121
+ await handleBoss(rest[0] || null);
122
+ } else if (command === 'diary') {
123
+ await handleDiary(rest[0] || null);
124
+ } else if (command === 'analyze') {
125
+ await handleAnalyze(rest[0] || null);
126
+ } else {
127
+ await handler(...rest);
128
+ }
129
+ }
@@ -0,0 +1,55 @@
1
+ import fetch from 'node-fetch';
2
+ import ora from 'ora';
3
+
4
+ const BASE_URL = 'https://api.tibiadata.com/v4';
5
+
6
+ export async function fetchCharacter(name) {
7
+ const spinner = ora({
8
+ text: `Consultando tibiadata.com para "${name}"...`,
9
+ color: 'yellow',
10
+ }).start();
11
+
12
+ try {
13
+ const encodedName = encodeURIComponent(name.trim());
14
+ const url = `${BASE_URL}/character/${encodedName}`;
15
+ const response = await fetch(url, {
16
+ headers: { 'Accept': 'application/json' },
17
+ timeout: 10000,
18
+ });
19
+
20
+ if (!response.ok) {
21
+ spinner.fail(`Error al consultar la API (HTTP ${response.status})`);
22
+ return null;
23
+ }
24
+
25
+ const data = await response.json();
26
+ spinner.stop();
27
+
28
+ const char = data?.character?.character;
29
+ if (!char || !char.name) {
30
+ return null;
31
+ }
32
+
33
+ return normalizeCharacter(char);
34
+ } catch (err) {
35
+ spinner.fail('No se pudo conectar a tibiadata.com');
36
+ if (process.env.DEBUG) console.error(err);
37
+ return null;
38
+ }
39
+ }
40
+
41
+ function normalizeCharacter(raw) {
42
+ const guildName = raw.guild?.name || null;
43
+ return {
44
+ name: raw.name,
45
+ vocation: raw.vocation || 'Unknown',
46
+ level: raw.level || 0,
47
+ world: raw.world || 'Unknown',
48
+ guild: guildName,
49
+ sex: raw.sex || null,
50
+ residence: raw.residence || null,
51
+ account_status: raw.account_status || null,
52
+ achievement_points: raw.achievement_points || 0,
53
+ last_login: raw.last_login || null,
54
+ };
55
+ }
@@ -0,0 +1,109 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import { readFileSync } from 'fs';
3
+ import { getApiKey, getCharacter } from './config.js';
4
+ import { error } from './banner.js';
5
+
6
+ const MODEL = 'claude-sonnet-4-20250514';
7
+ const MAX_TOKENS = 2048;
8
+
9
+ function buildSystemPrompt(extraContext = '') {
10
+ const character = getCharacter();
11
+
12
+ let charBlock = '';
13
+ if (character) {
14
+ charBlock = `
15
+ Perfil del jugador:
16
+ - Nombre: ${character.name}
17
+ - VocaciΓ³n: ${character.vocation}
18
+ - Level: ${character.level}
19
+ - World: ${character.world}
20
+ - Guild: ${character.guild || 'Sin guild'}
21
+ `;
22
+ } else {
23
+ charBlock = '\nPerfil del jugador: No configurado todavΓ­a.\n';
24
+ }
25
+
26
+ return `Eres TibiaWayAI, un asistente experto en el MMORPG Tibia con mΓ‘s de 20 aΓ±os de conocimiento del juego. Conoces todas las mecΓ‘nicas, criaturas, quests, bosses, vocaciones y meta actual.
27
+ ${charBlock}
28
+ Siempre personaliza tus respuestas segΓΊn este perfil.
29
+ Usa terminologΓ­a del juego naturalmente (loot, waste, imbu, respawn, etc).
30
+ SΓ© directo y preciso. Incluye nΓΊmeros y estimaciones cuando sea posible.
31
+ Responde en espaΓ±ol a menos que el jugador escriba en otro idioma.
32
+ Tienes personalidad de veterano tibian: directo, preciso, con humor de la comunidad. Nunca condescendiente.
33
+ ${extraContext}`;
34
+ }
35
+
36
+ function getClient() {
37
+ const apiKey = getApiKey();
38
+ if (!apiKey) {
39
+ error('No se encontrΓ³ API key de Anthropic.');
40
+ console.error('Configura tu API key con: tibiaway setup');
41
+ console.error('O exporta la variable: export ANTHROPIC_API_KEY=tu_key');
42
+ process.exit(1);
43
+ }
44
+ return new Anthropic({ apiKey });
45
+ }
46
+
47
+ export async function askClaude(userMessage, extraContext = '') {
48
+ const client = getClient();
49
+
50
+ const response = await client.messages.create({
51
+ model: MODEL,
52
+ max_tokens: MAX_TOKENS,
53
+ system: buildSystemPrompt(extraContext),
54
+ messages: [{ role: 'user', content: userMessage }],
55
+ });
56
+
57
+ return response.content[0]?.text || '';
58
+ }
59
+
60
+ export async function askClaudeWithImage(userMessage, imagePath, extraContext = '') {
61
+ const client = getClient();
62
+
63
+ const imageBuffer = readFileSync(imagePath);
64
+ const base64Image = imageBuffer.toString('base64');
65
+ const ext = imagePath.split('.').pop().toLowerCase();
66
+ const mediaTypeMap = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp' };
67
+ const mediaType = mediaTypeMap[ext] || 'image/png';
68
+
69
+ const response = await client.messages.create({
70
+ model: MODEL,
71
+ max_tokens: MAX_TOKENS,
72
+ system: buildSystemPrompt(extraContext),
73
+ messages: [
74
+ {
75
+ role: 'user',
76
+ content: [
77
+ {
78
+ type: 'image',
79
+ source: { type: 'base64', media_type: mediaType, data: base64Image },
80
+ },
81
+ { type: 'text', text: userMessage },
82
+ ],
83
+ },
84
+ ],
85
+ });
86
+
87
+ return response.content[0]?.text || '';
88
+ }
89
+
90
+ export async function streamClaude(userMessage, extraContext = '', onChunk) {
91
+ const client = getClient();
92
+
93
+ const stream = client.messages.stream({
94
+ model: MODEL,
95
+ max_tokens: MAX_TOKENS,
96
+ system: buildSystemPrompt(extraContext),
97
+ messages: [{ role: 'user', content: userMessage }],
98
+ });
99
+
100
+ let fullText = '';
101
+ for await (const chunk of stream) {
102
+ if (chunk.type === 'content_block_delta' && chunk.delta?.type === 'text_delta') {
103
+ fullText += chunk.delta.text;
104
+ if (onChunk) onChunk(chunk.delta.text);
105
+ }
106
+ }
107
+
108
+ return fullText;
109
+ }
@@ -0,0 +1,72 @@
1
+ import chalk from 'chalk';
2
+
3
+ const FIRE_ORANGE = '#FF6B1A';
4
+ const DARK_ORANGE = '#CC4A00';
5
+ const GOLD = '#FFD700';
6
+ const DIM_TEXT = '#888888';
7
+
8
+ export function showBanner() {
9
+ const banner = `
10
+ ${chalk.hex(FIRE_ORANGE).bold('β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—')}
11
+ ${chalk.hex(FIRE_ORANGE).bold('β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘')}
12
+ ${chalk.hex(DARK_ORANGE).bold(' β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘')}
13
+ ${chalk.hex(DARK_ORANGE).bold(' β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘')}
14
+ ${chalk.hex(FIRE_ORANGE).bold(' β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘')}
15
+ ${chalk.hex(FIRE_ORANGE).bold(' β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β• β•šβ•β•β•β•šβ•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•šβ•β•')}`;
16
+
17
+ console.log(banner);
18
+ console.log(chalk.hex(GOLD)(' βš” Your path through Tibia, powered by AI βš”'));
19
+ console.log(chalk.hex(DIM_TEXT)(' ─────────────────────────────────────────────\n'));
20
+ }
21
+
22
+ export function showMiniLogo() {
23
+ console.log(chalk.hex(FIRE_ORANGE).bold('βš” TibiaWayAI') + chalk.hex(DIM_TEXT)(' β€” Your path through Tibia, powered by AI'));
24
+ }
25
+
26
+ export function separator() {
27
+ console.log(chalk.hex(DARK_ORANGE)('─'.repeat(60)));
28
+ }
29
+
30
+ export function success(msg) {
31
+ console.log(chalk.hex(FIRE_ORANGE)('βœ“') + ' ' + chalk.white(msg));
32
+ }
33
+
34
+ export function info(msg) {
35
+ console.log(chalk.hex(GOLD)('β„Ή') + ' ' + chalk.hex('#f0f0f0')(msg));
36
+ }
37
+
38
+ export function warn(msg) {
39
+ console.log(chalk.yellow('⚠') + ' ' + chalk.yellow(msg));
40
+ }
41
+
42
+ export function error(msg) {
43
+ console.log(chalk.red('βœ—') + ' ' + chalk.red(msg));
44
+ }
45
+
46
+ export function highlight(text) {
47
+ return chalk.hex(FIRE_ORANGE).bold(text);
48
+ }
49
+
50
+ export function gold(text) {
51
+ return chalk.hex(GOLD)(text);
52
+ }
53
+
54
+ export function dim(text) {
55
+ return chalk.hex(DIM_TEXT)(text);
56
+ }
57
+
58
+ export function printCharacterCard(character) {
59
+ const { name, vocation, level, world, guild } = character;
60
+ const line = '═'.repeat(40);
61
+ console.log(chalk.hex(DARK_ORANGE)(`β•”${line}β•—`));
62
+ console.log(chalk.hex(DARK_ORANGE)('β•‘') + chalk.hex(FIRE_ORANGE).bold(' TIBIA CHARACTER PROFILE'.padEnd(40)) + chalk.hex(DARK_ORANGE)('β•‘'));
63
+ console.log(chalk.hex(DARK_ORANGE)(`β• ${line}β•£`));
64
+ console.log(chalk.hex(DARK_ORANGE)('β•‘') + ` ${chalk.hex(GOLD)('Nombre:')} ${chalk.white(name)}`.padEnd(49) + chalk.hex(DARK_ORANGE)('β•‘'));
65
+ console.log(chalk.hex(DARK_ORANGE)('β•‘') + ` ${chalk.hex(GOLD)('VocaciΓ³n:')} ${chalk.white(vocation)}`.padEnd(49) + chalk.hex(DARK_ORANGE)('β•‘'));
66
+ console.log(chalk.hex(DARK_ORANGE)('β•‘') + ` ${chalk.hex(GOLD)('Level:')} ${chalk.hex(FIRE_ORANGE).bold(String(level))}`.padEnd(57) + chalk.hex(DARK_ORANGE)('β•‘'));
67
+ console.log(chalk.hex(DARK_ORANGE)('β•‘') + ` ${chalk.hex(GOLD)('World:')} ${chalk.white(world)}`.padEnd(49) + chalk.hex(DARK_ORANGE)('β•‘'));
68
+ if (guild) {
69
+ console.log(chalk.hex(DARK_ORANGE)('β•‘') + ` ${chalk.hex(GOLD)('Guild:')} ${chalk.white(guild)}`.padEnd(49) + chalk.hex(DARK_ORANGE)('β•‘'));
70
+ }
71
+ console.log(chalk.hex(DARK_ORANGE)(`β•š${line}╝`));
72
+ }
@@ -0,0 +1,77 @@
1
+ import inquirer from 'inquirer';
2
+ import { fetchCharacter } from '../tibia-api.js';
3
+ import { setCharacter, getCharacter } from './config.js';
4
+ import { printCharacterCard, success, error, info, highlight } from './banner.js';
5
+
6
+ export async function setupCharacter() {
7
+ let confirmed = false;
8
+ let character = null;
9
+
10
+ while (!confirmed) {
11
+ const { name } = await inquirer.prompt([
12
+ {
13
+ type: 'input',
14
+ name: 'name',
15
+ message: highlight('ΒΏCuΓ‘l es el nombre de tu personaje en Tibia?'),
16
+ validate: (input) => input.trim().length > 0 || 'El nombre no puede estar vacΓ­o.',
17
+ },
18
+ ]);
19
+
20
+ character = await fetchCharacter(name.trim());
21
+
22
+ if (!character) {
23
+ error(`No se encontrΓ³ el personaje "${name}". Verifica el nombre e intenta de nuevo.`);
24
+ const { retry } = await inquirer.prompt([
25
+ {
26
+ type: 'confirm',
27
+ name: 'retry',
28
+ message: 'ΒΏQuieres intentar con otro nombre?',
29
+ default: true,
30
+ },
31
+ ]);
32
+ if (!retry) {
33
+ info('Puedes configurar tu personaje mΓ‘s tarde con: tibiaway setup');
34
+ return null;
35
+ }
36
+ continue;
37
+ }
38
+
39
+ console.log('');
40
+ printCharacterCard(character);
41
+ console.log('');
42
+
43
+ const { confirm } = await inquirer.prompt([
44
+ {
45
+ type: 'confirm',
46
+ name: 'confirm',
47
+ message: highlight('ΒΏEs este tu personaje?'),
48
+ default: true,
49
+ },
50
+ ]);
51
+
52
+ if (confirm) {
53
+ confirmed = true;
54
+ }
55
+ }
56
+
57
+ setCharacter(character);
58
+ success(`Β‘Perfil guardado! Bienvenido, ${highlight(character.name)} (${character.vocation} lv ${character.level}).`);
59
+ return character;
60
+ }
61
+
62
+ export async function syncCharacter() {
63
+ const current = getCharacter();
64
+ if (!current) {
65
+ error('No hay personaje configurado. Ejecuta: tibiaway setup');
66
+ return null;
67
+ }
68
+
69
+ const updated = await fetchCharacter(current.name);
70
+ if (!updated) {
71
+ error(`No se pudo actualizar el perfil de "${current.name}".`);
72
+ return null;
73
+ }
74
+
75
+ setCharacter(updated);
76
+ return updated;
77
+ }