@vierratale/ai 0.1.0-beta.5 → 0.1.0-beta.6

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,27 +12,43 @@ npm install -g @vierratale/ai
12
12
  npx @vierratale/ai
13
13
  ```
14
14
 
15
+ The first launch detects and, if needed, installs and starts the local engine
16
+ (**Cortex** - "secretly" Ollama under the hood), then pulls the model you use.
17
+
15
18
  ## Usage
16
19
 
17
20
  ```bash
18
21
  vierrataleai # Start chat (auto-detect provider)
19
22
  vierrataleai --provider cortex # Use local models
20
- vierrataleai --provider openai # Use cloud models
23
+ vierrataleai --provider openai # Use cloud models (OpenAI)
24
+ vierrataleai --provider anthropic # Use cloud models (Claude)
25
+ vierrataleai --provider gemini # Use cloud models (Gemini)
21
26
  vierrataleai --model vierratale-pro # Use specific model
22
27
  ```
23
28
 
24
29
  ## Models
25
30
 
26
- | Model | Description |
27
- |-------|-------------|
28
- | vierratale-lite | Minimal, fast responses |
29
- | vierratale-fast | Fast, balanced quality |
30
- | vierratale-small | Small, efficient |
31
- | vierratale-balanced | Balanced performance |
32
- | vierratale-plus | Enhanced capability |
33
- | vierratale-pro | Professional grade |
34
- | vierratale-cloud | Cloud-powered |
35
- | vierratale-cloud-pro | Cloud professional |
31
+ Local models run through the **Cortex** engine:
32
+
33
+ | Model | Backend |
34
+ |-------|---------|
35
+ | vierratale-lite | qwen2.5-coder:0.5b |
36
+ | vierratale-fast | qwen2.5-coder:1.5b |
37
+ | vierratale-small | qwen2.5-coder:3b |
38
+ | vierratale-balanced | qwen2.5-coder:7b |
39
+ | vierratale-plus | qwen2.5-coder:14b |
40
+ | vierratale-pro | qwen2.5-coder:32b |
41
+
42
+ Cloud models are available when the matching provider + API key is configured:
43
+
44
+ | Model | Backend |
45
+ |-------|---------|
46
+ | vierratale-cloud-mini | gpt-4o-mini (OpenAI) |
47
+ | vierratale-cloud | gpt-4o (OpenAI) |
48
+ | vierratale-cloud-fast | claude-3-5-haiku-20241022 (Anthropic) |
49
+ | vierratale-cloud-pro | claude-sonnet-4-20250514 (Anthropic) |
50
+ | vierratale-cloud-lite | gemini-2.0-flash (Gemini) |
51
+ | vierratale-cloud-plus | gemini-1.5-pro (Gemini) |
36
52
 
37
53
  ## Commands
38
54
 
@@ -40,6 +56,7 @@ vierrataleai --model vierratale-pro # Use specific model
40
56
  - `/model [name]` - Switch model
41
57
  - `/provider [name]` - Switch provider
42
58
  - `/models` - List available models
59
+ - `/system [text]` - Set a custom system prompt / persona
43
60
  - `/search <query>` - Search the web and summarize with AI
44
61
  - `/clear` - Clear screen
45
62
  - `/new` - Start a new conversation (clear memory)
@@ -67,9 +84,11 @@ you can continue where you left off. Use `/new` to start fresh.
67
84
  Config file: `~/.config/vierrataleai/config.json`
68
85
 
69
86
  Environment variables:
70
- - `VIERRATALE_PROVIDER` - Default provider
87
+ - `VIERRATALE_PROVIDER` - Default provider (auto/cortex/openai/anthropic/gemini)
71
88
  - `VIERRATALE_MODEL` - Default model
72
- - `OPENAI_API_KEY` - OpenAI API key (for cloud models)
89
+ - `OPENAI_API_KEY` - OpenAI API key (openai provider)
90
+ - `ANTHROPIC_API_KEY` - Anthropic API key (anthropic provider)
91
+ - `GEMINI_API_KEY` - Google AI API key (gemini provider)
73
92
 
74
93
  ## License
75
94
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vierratale/ai",
3
- "version": "0.1.0-beta.5",
3
+ "version": "0.1.0-beta.6",
4
4
  "description": "VierrataleAI - Intelligent terminal assistant",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,7 +11,9 @@
11
11
  ".": "./src/index.js"
12
12
  },
13
13
  "scripts": {
14
- "start": "node bin/vierrataleai.js"
14
+ "start": "node bin/vierrataleai.js",
15
+ "test": "node --test test/",
16
+ "postinstall": "node scripts/postinstall.js"
15
17
  },
16
18
  "keywords": ["ai", "chatbot", "terminal", "assistant", "llm"],
17
19
  "author": "Vierratale",
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ // Optional engine setup after install. Never fails the package install.
3
+ import { execSync } from 'child_process';
4
+ import { existsSync } from 'fs';
5
+
6
+ const isInstalled = () => {
7
+ try {
8
+ execSync('which ollama 2>/dev/null', { stdio: 'ignore' });
9
+ return true;
10
+ } catch {}
11
+ return existsSync('/usr/local/bin/ollama') || existsSync('/usr/bin/ollama');
12
+ };
13
+
14
+ try {
15
+ if (isInstalled()) {
16
+ process.exit(0);
17
+ }
18
+
19
+ const nonInteractive =
20
+ !process.stdout.isTTY || process.env.CI || process.env.NODE_ENV === 'test';
21
+
22
+ // Only auto-install when explicitly requested. Otherwise just inform the user
23
+ // that the engine will be installed on first launch.
24
+ if (process.env.VIERRATALE_INSTALL_ENGINE === '1' && !nonInteractive) {
25
+ execSync('curl -fsSL https://ollama.com/install.sh | sh', { stdio: 'inherit' });
26
+ } else {
27
+ console.log(
28
+ '\n[vierrataleai] The local engine (ollama) was not detected.\n' +
29
+ ' - Run the CLI: it will attempt to install and start the engine automatically.\n' +
30
+ ' - Or install it yourself: curl -fsSL https://ollama.com/install.sh | sh\n'
31
+ );
32
+ }
33
+ } catch {
34
+ // Never fail the install because of engine setup.
35
+ }
package/src/catalog.js CHANGED
@@ -60,6 +60,26 @@ export const Catalog = {
60
60
  return 'vierratale-fast';
61
61
  },
62
62
 
63
+ getDefaultCloudModel() {
64
+ return 'vierratale-cloud-mini';
65
+ },
66
+
67
+ isLocalModel(displayName) {
68
+ return LOCAL_MODELS.includes(displayName);
69
+ },
70
+
71
+ isCloudModel(displayName) {
72
+ return CLOUD_MODELS.includes(displayName);
73
+ },
74
+
75
+ getVendorForCloudModel(displayName) {
76
+ const real = REVERSE[displayName];
77
+ if (real && real.startsWith('claude-')) return 'anthropic';
78
+ if (real && real.startsWith('gemini-')) return 'gemini';
79
+ if (real && real.startsWith('gpt-')) return 'openai';
80
+ return 'openai';
81
+ },
82
+
63
83
  getModelInfo(displayName) {
64
84
  const real = REVERSE[displayName];
65
85
  if (!real) return null;
package/src/cli.js CHANGED
@@ -72,7 +72,7 @@ async function answerWithSearch(messages, provider, query, systemPrompt) {
72
72
  let response = '';
73
73
  try {
74
74
  for await (const chunk of provider.stream(messages, {
75
- model: Config.get('model'),
75
+ model: Config.getEffectiveModel(provider.name),
76
76
  systemPrompt,
77
77
  })) {
78
78
  Terminal.printAIChunk(chunk);
@@ -140,7 +140,7 @@ async function chat(provider, systemPrompt) {
140
140
 
141
141
  if (cmd === '/clear') {
142
142
  Terminal.clear();
143
- showBanner(provider.name, Config.get('model'), provider.displayName);
143
+ showBanner(provider.name, Config.getEffectiveModel(provider.name), provider.displayName);
144
144
  continue;
145
145
  }
146
146
 
@@ -157,13 +157,13 @@ async function chat(provider, systemPrompt) {
157
157
  }
158
158
 
159
159
  if (cmd === '/models') {
160
- const models = provider.name === 'cortex'
160
+ const models = provider.isLocal
161
161
  ? Catalog.getLocalModels()
162
162
  : Catalog.getCloudModels();
163
163
  Terminal.printInfo('Available models:');
164
164
  for (const m of models) {
165
165
  const info = Catalog.getModelInfo(m);
166
- const current = m === Config.get('model') ? ' (active)' : '';
166
+ const current = m === Config.getEffectiveModel(provider.name) ? ' (active)' : '';
167
167
  console.log(` ${Branding.colors.accent}${m}${Branding.colors.dim}${current} - ${info.tier}${Branding.colors.reset}`);
168
168
  }
169
169
  continue;
@@ -174,6 +174,7 @@ async function chat(provider, systemPrompt) {
174
174
  if (modelName) {
175
175
  if (Catalog.getModelInfo(modelName)) {
176
176
  Config.save({ model: modelName });
177
+ Config.setProviderModel(provider.name, modelName);
177
178
  Terminal.printSuccess(`Model: ${modelName}`);
178
179
  } else {
179
180
  Terminal.printError(`Unknown model: ${modelName}`);
@@ -183,13 +184,13 @@ async function chat(provider, systemPrompt) {
183
184
  }
184
185
 
185
186
  if (cmd === '/model') {
186
- Terminal.printInfo(`Current model: ${Config.get('model')}`);
187
+ Terminal.printInfo(`Current model: ${Config.getEffectiveModel(provider.name)}`);
187
188
  continue;
188
189
  }
189
190
 
190
191
  if (cmd.startsWith('/provider ')) {
191
192
  const prov = cmd.split(' ')[1];
192
- if (['cortex', 'openai'].includes(prov)) {
193
+ if (['cortex', 'openai', 'anthropic', 'gemini'].includes(prov)) {
193
194
  Config.save({ provider: prov });
194
195
  const next = ProviderFactory.create(prov);
195
196
  if (next && await next.isAvailable()) {
@@ -209,6 +210,22 @@ async function chat(provider, systemPrompt) {
209
210
  continue;
210
211
  }
211
212
 
213
+ if (cmd.startsWith('/system')) {
214
+ const prompt = trimmed.slice(7).trim();
215
+ if (prompt.toLowerCase() === 'reset') {
216
+ Config.save({ systemPrompt: '' });
217
+ systemPrompt = loadSystemPrompt();
218
+ Terminal.printSuccess('System prompt reset to default.');
219
+ } else if (prompt) {
220
+ Config.save({ systemPrompt: prompt });
221
+ systemPrompt = prompt;
222
+ Terminal.printSuccess('System prompt updated.');
223
+ } else {
224
+ Terminal.printInfo(`Current system prompt: ${systemPrompt}`);
225
+ }
226
+ continue;
227
+ }
228
+
212
229
  if (cmd.startsWith('/search ')) {
213
230
  const query = trimmed.slice(8).trim();
214
231
  if (!query) {
@@ -233,7 +250,7 @@ async function chat(provider, systemPrompt) {
233
250
  let response = '';
234
251
  try {
235
252
  for await (const chunk of provider.stream(messages, {
236
- model: Config.get('model'),
253
+ model: Config.getEffectiveModel(provider.name),
237
254
  systemPrompt,
238
255
  })) {
239
256
  Terminal.printAIChunk(chunk);
@@ -280,7 +297,7 @@ export async function run() {
280
297
  Config.save({ provider: provider.name });
281
298
 
282
299
  if (args.clear) Terminal.clear();
283
- showBanner(provider.name, Config.get('model'), provider.displayName);
300
+ showBanner(provider.name, Config.getEffectiveModel(provider.name), provider.displayName);
284
301
 
285
302
  const systemPrompt = loadSystemPrompt();
286
303
  await chat(provider, systemPrompt);
package/src/config.js CHANGED
@@ -44,6 +44,9 @@ export const Config = {
44
44
  maxTokens: parseInt(getEnv('VIERRATALE_MAX_TOKENS', String(file.maxTokens || DEFAULTS.maxTokens))),
45
45
  openaiApiKey: getEnv('OPENAI_API_KEY', file.openaiApiKey || ''),
46
46
  anthropicApiKey: getEnv('ANTHROPIC_API_KEY', file.anthropicApiKey || ''),
47
+ geminiApiKey: getEnv('GEMINI_API_KEY', file.geminiApiKey || ''),
48
+ systemPrompt: file.systemPrompt || '',
49
+ providerModels: file.providerModels || {},
47
50
  };
48
51
  return this._config;
49
52
  },
@@ -58,6 +61,22 @@ export const Config = {
58
61
  return { ...this._config };
59
62
  },
60
63
 
64
+ getProviderModel(provider) {
65
+ if (!this._config) this.load();
66
+ return this._config.providerModels?.[provider] || null;
67
+ },
68
+
69
+ setProviderModel(provider, model) {
70
+ if (!this._config) this.load();
71
+ this._config.providerModels = this._config.providerModels || {};
72
+ this._config.providerModels[provider] = model;
73
+ this.save();
74
+ },
75
+
76
+ getEffectiveModel(provider) {
77
+ return this.getProviderModel(provider) || this.get('model');
78
+ },
79
+
61
80
  save(overrides = {}) {
62
81
  if (!existsSync(CONFIG_DIR)) {
63
82
  mkdirSync(CONFIG_DIR, { recursive: true });
@@ -0,0 +1,99 @@
1
+ import { BaseProvider } from './base.js';
2
+ import { Catalog } from '../catalog.js';
3
+ import { Config } from '../config.js';
4
+
5
+ const ANTHROPIC_URL = 'https://api.anthropic.com/v1/messages';
6
+
7
+ export class AnthropicProvider extends BaseProvider {
8
+ constructor() {
9
+ super('anthropic');
10
+ }
11
+
12
+ get displayName() {
13
+ return 'Nebula';
14
+ }
15
+
16
+ _resolveModel(options) {
17
+ const requested = options.model || Config.get('model');
18
+ if (Catalog.isLocalModel(requested) || !Catalog.isCloudModel(requested)) {
19
+ return Catalog.getRealModel(Catalog.getDefaultCloudModel());
20
+ }
21
+ const real = Catalog.getRealModel(requested);
22
+ return real.startsWith('claude-') ? real : 'claude-3-5-haiku-20241022';
23
+ }
24
+
25
+ async isAvailable() {
26
+ const key = Config.get('anthropicApiKey');
27
+ return Boolean(key);
28
+ }
29
+
30
+ async listModels() {
31
+ const key = Config.get('anthropicApiKey');
32
+ if (!key) return [];
33
+ const models = Object.keys(Catalog.getAllModels()).filter((m) => m.startsWith('claude-'));
34
+ return models.map((real) => ({
35
+ realName: real,
36
+ displayName: Catalog.getDisplayName(real),
37
+ }));
38
+ }
39
+
40
+ async *stream(messages, options = {}) {
41
+ const key = Config.get('anthropicApiKey');
42
+ if (!key) throw new Error('Anthropic API key not configured');
43
+
44
+ const model = this._resolveModel(options);
45
+ const systemPrompt = options.systemPrompt || '';
46
+
47
+ const system = [];
48
+ if (systemPrompt) system.push({ type: 'text', text: systemPrompt });
49
+ const apiMessages = messages.map((m) => ({
50
+ role: m.role === 'assistant' ? 'assistant' : 'user',
51
+ content: m.content,
52
+ }));
53
+
54
+ const resp = await fetch(ANTHROPIC_URL, {
55
+ method: 'POST',
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ 'x-api-key': key,
59
+ 'anthropic-version': '2023-06-01',
60
+ },
61
+ body: JSON.stringify({
62
+ model,
63
+ system: system.length ? system : undefined,
64
+ messages: apiMessages,
65
+ max_tokens: options.maxTokens || Config.get('maxTokens'),
66
+ stream: true,
67
+ temperature: options.temperature || Config.get('temperature'),
68
+ }),
69
+ });
70
+
71
+ if (!resp.ok) {
72
+ throw new Error('Failed to connect to cloud engine');
73
+ }
74
+
75
+ const reader = resp.body.getReader();
76
+ const decoder = new TextDecoder();
77
+ let buffer = '';
78
+
79
+ while (true) {
80
+ const { done, value } = await reader.read();
81
+ if (done) break;
82
+
83
+ buffer += decoder.decode(value, { stream: true });
84
+ const lines = buffer.split('\n');
85
+ buffer = lines.pop() || '';
86
+
87
+ for (const line of lines) {
88
+ if (!line.startsWith('data: ')) continue;
89
+ const data = line.slice(6);
90
+ try {
91
+ const json = JSON.parse(data);
92
+ if (json.type === 'content_block_delta' && json.delta?.text) {
93
+ yield json.delta.text;
94
+ }
95
+ } catch {}
96
+ }
97
+ }
98
+ }
99
+ }
@@ -7,6 +7,10 @@ export class BaseProvider {
7
7
  return 'Cortex';
8
8
  }
9
9
 
10
+ get isLocal() {
11
+ return false;
12
+ }
13
+
10
14
  async *stream(messages, options = {}) {
11
15
  throw new Error('stream() must be implemented');
12
16
  }
@@ -12,6 +12,10 @@ export class CortexProvider extends BaseProvider {
12
12
  return 'Cortex';
13
13
  }
14
14
 
15
+ get isLocal() {
16
+ return true;
17
+ }
18
+
15
19
  async isAvailable() {
16
20
  try {
17
21
  const resp = await fetch(`${this.host}/api/tags`, { signal: AbortSignal.timeout(3000) });
@@ -0,0 +1,97 @@
1
+ import { BaseProvider } from './base.js';
2
+ import { Catalog } from '../catalog.js';
3
+ import { Config } from '../config.js';
4
+
5
+ const GEMINI_URL = 'https://generativelanguage.googleapis.com/v1beta/models';
6
+
7
+ export class GeminiProvider extends BaseProvider {
8
+ constructor() {
9
+ super('gemini');
10
+ }
11
+
12
+ get displayName() {
13
+ return 'Nebula';
14
+ }
15
+
16
+ _resolveModel(options) {
17
+ const requested = options.model || Config.get('model');
18
+ if (Catalog.isLocalModel(requested) || !Catalog.isCloudModel(requested)) {
19
+ return Catalog.getRealModel(Catalog.getDefaultCloudModel());
20
+ }
21
+ const real = Catalog.getRealModel(requested);
22
+ return real.startsWith('gemini-') ? real : 'gemini-2.0-flash';
23
+ }
24
+
25
+ async isAvailable() {
26
+ const key = Config.get('geminiApiKey');
27
+ return Boolean(key);
28
+ }
29
+
30
+ async listModels() {
31
+ const key = Config.get('geminiApiKey');
32
+ if (!key) return [];
33
+ const models = Object.keys(Catalog.getAllModels()).filter((m) => m.startsWith('gemini-'));
34
+ return models.map((real) => ({
35
+ realName: real,
36
+ displayName: Catalog.getDisplayName(real),
37
+ }));
38
+ }
39
+
40
+ async *stream(messages, options = {}) {
41
+ const key = Config.get('geminiApiKey');
42
+ if (!key) throw new Error('Gemini API key not configured');
43
+
44
+ const model = this._resolveModel(options);
45
+ const systemPrompt = options.systemPrompt || '';
46
+
47
+ const contents = messages.map((m) => ({
48
+ role: m.role === 'assistant' ? 'model' : 'user',
49
+ parts: [{ text: m.content }],
50
+ }));
51
+ if (systemPrompt) {
52
+ contents.unshift({ role: 'user', parts: [{ text: `System: ${systemPrompt}` }] });
53
+ }
54
+
55
+ const resp = await fetch(
56
+ `${GEMINI_URL}/${model}:streamGenerateContent?key=${key}&alt=sse`,
57
+ {
58
+ method: 'POST',
59
+ headers: { 'Content-Type': 'application/json' },
60
+ body: JSON.stringify({
61
+ contents,
62
+ generationConfig: {
63
+ temperature: options.temperature || Config.get('temperature'),
64
+ maxOutputTokens: options.maxTokens || Config.get('maxTokens'),
65
+ },
66
+ }),
67
+ }
68
+ );
69
+
70
+ if (!resp.ok) {
71
+ throw new Error('Failed to connect to cloud engine');
72
+ }
73
+
74
+ const reader = resp.body.getReader();
75
+ const decoder = new TextDecoder();
76
+ let buffer = '';
77
+
78
+ while (true) {
79
+ const { done, value } = await reader.read();
80
+ if (done) break;
81
+
82
+ buffer += decoder.decode(value, { stream: true });
83
+ const lines = buffer.split('\n');
84
+ buffer = lines.pop() || '';
85
+
86
+ for (const line of lines) {
87
+ if (!line.startsWith('data: ')) continue;
88
+ const data = line.slice(6);
89
+ try {
90
+ const json = JSON.parse(data);
91
+ const text = json.candidates?.[0]?.content?.parts?.[0]?.text;
92
+ if (text) yield text;
93
+ } catch {}
94
+ }
95
+ }
96
+ }
97
+ }
@@ -1,5 +1,7 @@
1
1
  import { CortexProvider } from './cortex.js';
2
2
  import { OpenAIProvider } from './openai.js';
3
+ import { AnthropicProvider } from './anthropic.js';
4
+ import { GeminiProvider } from './gemini.js';
3
5
  import { Config } from '../config.js';
4
6
 
5
7
  let providers = {};
@@ -9,6 +11,8 @@ function getProviders() {
9
11
  providers = {
10
12
  cortex: new CortexProvider(),
11
13
  openai: new OpenAIProvider(),
14
+ anthropic: new AnthropicProvider(),
15
+ gemini: new GeminiProvider(),
12
16
  };
13
17
  }
14
18
  return providers;
@@ -23,7 +27,7 @@ export const ProviderFactory = {
23
27
  if (await ps[requested].isAvailable()) return ps[requested];
24
28
  }
25
29
 
26
- for (const name of ['cortex', 'openai']) {
30
+ for (const name of ['cortex', 'openai', 'anthropic', 'gemini']) {
27
31
  if (await ps[name].isAvailable()) return ps[name];
28
32
  }
29
33
 
@@ -45,11 +45,21 @@ export class OpenAIProvider extends BaseProvider {
45
45
  }
46
46
  }
47
47
 
48
+ // If the configured model is a local (ollama) model, use a cloud default so
49
+ // we never send a qwen model name to the OpenAI API.
50
+ _resolveModel(options) {
51
+ const requested = options.model || Config.get('model');
52
+ if (Catalog.isLocalModel(requested) || !Catalog.isCloudModel(requested)) {
53
+ return Catalog.getRealModel(Catalog.getDefaultCloudModel());
54
+ }
55
+ return Catalog.getRealModel(requested);
56
+ }
57
+
48
58
  async *stream(messages, options = {}) {
49
59
  const key = Config.get('openaiApiKey');
50
60
  if (!key) throw new Error('Cloud API key not configured');
51
61
 
52
- const model = Catalog.getRealModel(options.model || Config.get('model'));
62
+ const model = this._resolveModel(options);
53
63
  const systemPrompt = options.systemPrompt || '';
54
64
 
55
65
  const apiMessages = [];
package/src/ui/banner.js CHANGED
@@ -3,10 +3,13 @@ import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { Branding } from './branding.js';
5
5
  import { Terminal } from './terminal.js';
6
+ import { Config } from '../config.js';
6
7
 
7
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
9
 
9
10
  export function loadSystemPrompt() {
11
+ const custom = Config.get('systemPrompt');
12
+ if (custom) return custom;
10
13
  try {
11
14
  const jsonPath = join(__dirname, '..', 'prompts', 'system.json');
12
15
  const data = JSON.parse(readFileSync(jsonPath, 'utf-8'));
@@ -20,16 +23,18 @@ export function showBanner(providerName, model, providerDisplayName) {
20
23
  const C = Branding.colors;
21
24
  const width = 44;
22
25
  const border = ` ╔${'═'.repeat(width)}╗`;
26
+ const visible = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
27
+ const pad = (s) => s + ' '.repeat(Math.max(1, width - visible(s).length));
23
28
 
24
29
  console.log();
25
30
  console.log(`${C.primary}${Branding.BANNER}${C.reset}`);
26
- console.log(` ${C.bold}${C.accent}▸ Vierratale AI ▸ Cortex Engine${C.reset}`);
31
+ console.log(` ${C.bold}${C.accent}▸ Vierratale AI ▸ ${providerDisplayName || 'Cortex'} Engine${C.reset}`);
27
32
  console.log();
28
33
  console.log(border);
29
- console.log(` ${C.bold}${C.primary}Model${C.reset}${' '.repeat(width - 12)}║`);
30
- console.log(` ${C.accent}${model}${C.reset}${' '.repeat(Math.max(1, width - 5 - model.length))}║`);
31
- console.log(` ${C.bold}${C.primary}Engine${C.reset}${' '.repeat(width - 13)}║`);
32
- console.log(` ${C.accent}${providerDisplayName || 'Cortex'}${C.reset}${' '.repeat(Math.max(1, width - 7 - (providerDisplayName || 'Cortex').length))}║`);
34
+ console.log(` ║${pad(' ' + C.bold + C.primary + 'Model' + C.reset)}║`);
35
+ console.log(` ║${pad(' ' + C.accent + model + C.reset)}║`);
36
+ console.log(` ║${pad(' ' + C.bold + C.primary + 'Engine' + C.reset)}║`);
37
+ console.log(` ║${pad(' ' + C.accent + (providerDisplayName || 'Cortex') + C.reset)}║`);
33
38
  console.log(` ╚${'═'.repeat(width)}╝`);
34
39
  console.log();
35
40
  console.log(` ${C.dim}Type /help for commands · /quit to exit${C.reset}`);
@@ -1,6 +1,6 @@
1
1
  export const Branding = {
2
2
  APP_NAME: 'VierrataleAI',
3
- VERSION: '0.1.0-beta.5',
3
+ VERSION: '0.1.0-beta.6',
4
4
 
5
5
  colors: {
6
6
  primary: '\x1b[38;2;124;58;237m',