@vierratale/ai 0.1.0-beta.1 → 0.1.0-beta.11

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,47 @@ 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
- vierrataleai --provider ollama # Use local models
20
- vierrataleai --provider openai # Use cloud models
21
- vierrataleai --model vierratale-pro # Use specific model
22
+ vierrataleai --provider cortex # Use local 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)
26
+ vierrataleai --model VRTL-6.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
+ | VRTL-1.lite | qwen3:0.6b |
36
+ | VRTL-2.fast | gemma3:1b |
37
+ | VRTL-3.small | llama3.2:1b |
38
+ | VRTL-4.balanced | qwen2.5:1.5b |
39
+ | VRTL-5.plus | qwen3:1.7b |
40
+ | VRTL-6.pro | qwen2.5:3b |
41
+
42
+ Cloud models are available when the matching provider + API key is configured:
43
+
44
+ | Model | Backend |
45
+ |-------|---------|
46
+ | VRTL-7.cloud-mini | gpt-4o-mini (OpenAI) |
47
+ | VRTL-8.cloud | gpt-4o (OpenAI) |
48
+ | VRTL-9.cloud-fast | claude-3-5-haiku-20241022 (Anthropic) |
49
+ | VRTL-10.cloud-pro | claude-sonnet-4-20250514 (Anthropic) |
50
+ | VRTL-11.cloud-lite | gemini-2.0-flash (Gemini) |
51
+ | VRTL-12.cloud-plus | gemini-1.5-pro (Gemini) |
52
+
53
+ > Legacy `vierratale-*` model names (e.g. `vierratale-fast`, `vierratale-cloud`)
54
+
55
+ > still work and are stored as VRTL names.
36
56
 
37
57
  ## Commands
38
58
 
@@ -40,17 +60,139 @@ vierrataleai --model vierratale-pro # Use specific model
40
60
  - `/model [name]` - Switch model
41
61
  - `/provider [name]` - Switch provider
42
62
  - `/models` - List available models
63
+ - `/system [text]` - Set a custom system prompt / persona
64
+ - `/search <query>` - Search the web and summarize with AI
65
+ - `/fetch <url>` - Open a link and summarize its content
66
+ - `/log [n|path]` - Show the last n log entries (default 30) or the log file path
43
67
  - `/clear` - Clear screen
68
+ - `/new` - Start a new conversation (clear memory)
44
69
  - `/quit` - Exit
45
70
 
71
+ ## Smart Search
72
+
73
+ Asking a question (Who/What/When/Where/Why/How...?) automatically searches the
74
+ web and summarizes the results with AI. Bare mentions of specific topics also
75
+ trigger a search — e.g. `timun mas from indonesia`, `the legend of roro
76
+ jonggrang`. For example:
77
+
78
+ ```
79
+ > Who is Cristiano Ronaldo?
80
+ > timun mas from indonesia
81
+ ```
82
+
83
+ triggers a live web search and answers from the results. If no web results come
84
+ back, the AI answers from its own knowledge instead of going quiet. Use
85
+ `/search <query>` to force a search explicitly.
86
+
87
+ Story requests about any subject work the same way — no matter how they're
88
+ phrased (`a story about X`, `the legend of X`, `tell me about X`, `narrate the
89
+ tale of X`, `write X into a txt file`). The subject is pulled out of the
90
+ sentence, searched first, and the reply is saved to a `<subject>.txt` file.
91
+ A generic `tell me a story` (no subject) is told directly from the model — and
92
+ is also saved to a `.txt` file.
93
+
94
+ Greetings (`hi`, `hello`, `hey`, `good morning`, ...) are answered instantly
95
+ with a short fixed reply — they never reach the model.
96
+
97
+ ## Writing Files
98
+
99
+ Ask the AI to create code, configs, scripts, web pages, or a whole project. When it
100
+ responds, it lists each file with a `FILE: <path>` header followed by a code
101
+ fence (and `FOLDER: <path>` for empty folders) — just running in the chat writes
102
+ them to your disk in the current working directory. Any file type is supported,
103
+ including JSON config files and HTML pages. Nested folders are created automatically, existing
104
+ files are skipped unless you confirm to overwrite, and nothing is ever written
105
+ outside the working directory.
106
+
107
+ If the AI ever answers with the code but forgets the `FILE:` header (or writes an
108
+ HTML page without a fence), the assistant rescues it: any `FILE: <path>` line in
109
+ the reply is honored even without a fence, otherwise the request is used to
110
+ infer a filename and the code block is saved anyway. Filename inference covers
111
+ many formats, not just web pages:
112
+
113
+ | Request contains… | Saved as |
114
+ |--------------------|----------|
115
+ | `html` / `webpage` / `website` / `page` | `index.html` |
116
+ | `css` | `style.css` |
117
+ | `python` / `py` | `script.py` |
118
+ | `flask` / `django` / `fastapi` / `streamlit` / `app` | `app.py` |
119
+ | `javascript` / `js` / `script` | `script.js` |
120
+ | `typescript` / `ts` | `script.ts` |
121
+ | `bash` / `zsh` / `shell` | `script.sh` |
122
+ | `json` / `yaml` / `yml` / `csv` / `sql` | `output.json` / `.yml` / `.csv` / `.sql` |
123
+
124
+ When the request has no hint at all, the fence language in the reply picks the
125
+ name (```python → `script.py`, ```css → `style.css`, …). Saving also kicks in
126
+ whenever the reply itself contains a `FILE:`/`FOLDER:` line, or when a reply is
127
+ basically just one substantial code block, even if the request phrasing wasn't
128
+ matched.
129
+
130
+ For file requests, a short format instruction is silently attached to your
131
+ message so the model emits complete `FILE:` blocks with closed fences. And if
132
+ the model ever answers a file request *without any code* (free-style prose,
133
+ interrupted before a fence), nothing is saved and you get a notice telling you
134
+ to ask again — a `.py` file full of chatter is never created.
135
+
136
+ Example of what the AI emits:
137
+
138
+ ```
139
+ FILE: config.json
140
+ ```json
141
+ {
142
+ "name": "my-app",
143
+ "port": 3000
144
+ }
145
+ ```
146
+
147
+ FILE: index.html
148
+ ```html
149
+ <!doctype html>
150
+ <html><body>Hello</body></html>
151
+ ```
152
+
153
+ FILE: src/hello.js
154
+ ```js
155
+ console.log('Hello VierrataleAI');
156
+ ```
157
+
158
+ ## Chat UI
159
+
160
+ The chat runs in a clean, minimal full-screen interface: your turns are labelled
161
+ `You` and the assistant's `AI`, with plain wrapped text in between. Code blocks
162
+ are the exception — they render inside a green bordered box with their language
163
+ tag (`╭─ html ─╮`), so files stand out at a glance. The latest reply stays pinned
164
+ to the bottom of the terminal while it streams in (no screen flashing), and
165
+ internal search/fetch context stays hidden.
166
+
167
+ ## Automatic Logging
168
+
169
+ Everything that happens in a session is written automatically to a per-day log
170
+ file in `~/.config/vierrataleai/logs/` (e.g. `vierrataleai-20260101.log`):
171
+ app/session start, every user message, every AI reply, every file write or
172
+ skip, and any errors — with a timestamp. The logs are plain text and safe to
173
+ grep. To inspect them from inside the chat:
174
+
175
+ ```
176
+ /log # show the last 30 entries
177
+ /log 100 # show the last 100 entries
178
+ /log path # print the log file path
179
+ ```
180
+
181
+ ## Conversation Memory
182
+
183
+ Your conversation is saved automatically and restored on the next launch, so
184
+ you can continue where you left off. Use `/new` to start fresh.
185
+
46
186
  ## Configuration
47
187
 
48
188
  Config file: `~/.config/vierrataleai/config.json`
49
189
 
50
190
  Environment variables:
51
- - `VIERRATALE_PROVIDER` - Default provider
191
+ - `VIERRATALE_PROVIDER` - Default provider (auto/cortex/openai/anthropic/gemini)
52
192
  - `VIERRATALE_MODEL` - Default model
53
- - `OPENAI_API_KEY` - OpenAI API key (for cloud models)
193
+ - `OPENAI_API_KEY` - OpenAI API key (openai provider)
194
+ - `ANTHROPIC_API_KEY` - Anthropic API key (anthropic provider)
195
+ - `GEMINI_API_KEY` - Google AI API key (gemini provider)
54
196
 
55
197
  ## License
56
198
 
@@ -1,3 +1,76 @@
1
1
  #!/usr/bin/env node
2
+ // VierrataleAI launcher + installer.
3
+ //
4
+ // Running without flags starts the assistant (same as before).
5
+ // Running with --install (or --setup / install) installs the engine + model and
6
+ // creates a global "vierrataleai" command, then starts the assistant.
7
+ //
8
+ // Installation progress is always the fake animated bar — real engine/model
9
+ // output is deliberately hidden so first-run feels quick and clean.
2
10
  import { run } from '../src/cli.js';
3
- run();
11
+ import { Installer } from '../src/installer.js';
12
+ import { Config } from '../src/config.js';
13
+ import { Branding } from '../src/ui/branding.js';
14
+ import { Terminal } from '../src/ui/terminal.js';
15
+
16
+ const C = Branding.colors;
17
+
18
+ const INSTALL_STEPS = [
19
+ 'Checking the AI engine',
20
+ 'Installing dependencies',
21
+ 'Downloading the model',
22
+ 'Linking the vierrataleai command',
23
+ 'Optimizing for this device',
24
+ ];
25
+
26
+ function delay(ms) {
27
+ return new Promise((r) => setTimeout(r, ms));
28
+ }
29
+
30
+ async function fakeProgress(steps, work) {
31
+ const frames = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
32
+ let i = 0;
33
+ const tty = process.stdout.isTTY;
34
+ const timer = setInterval(() => {
35
+ if (!tty) return;
36
+ i++;
37
+ const stage = steps[Math.floor((i / 10) % steps.length)];
38
+ const pct = String(8 + ((i * 7) % 85)).padStart(3, ' ');
39
+ const bar = frames[i % frames.length];
40
+ const fillLen = 1 + (i % 20);
41
+ const fill = '█'.repeat(fillLen) + '░'.repeat(Math.max(0, 20 - fillLen));
42
+ process.stdout.write(`\r ${C.accent}${bar}${C.reset} ${C.bold}${pct}%${C.reset} ${C.dim}${stage}${C.reset} [${C.accent}${fill}${C.reset}]`);
43
+ }, 110);
44
+ try {
45
+ return await work;
46
+ } finally {
47
+ clearInterval(timer);
48
+ if (tty) process.stdout.write('\r\x1b[2K');
49
+ process.stdout.write(` ${C.success}✔${C.reset} ${C.bold}${C.dim}100%${C.reset} ${C.dim}Ready${C.reset}\n`);
50
+ }
51
+ }
52
+
53
+ async function main() {
54
+ const args = process.argv.slice(2);
55
+ const doInstall = args.includes('--install') || args.includes('--setup') || args.includes('install');
56
+
57
+ Config.load();
58
+
59
+ if (doInstall) {
60
+ console.log(`\n ${C.bold}${C.primary}${Branding.APP_NAME} ${Branding.VERSION}${C.reset} ${C.dim}— installer${C.reset}\n`);
61
+ const result = await fakeProgress(INSTALL_STEPS, Installer.install());
62
+ if (!result.command) {
63
+ Terminal.printInfo('Could not create a global "vierrataleai" command; run it via the bin script.');
64
+ } else {
65
+ Terminal.printSuccess(`Engine + model installed; "vierrataleai" command available at ${result.command}`);
66
+ }
67
+ console.log();
68
+ }
69
+
70
+ return run();
71
+ }
72
+
73
+ main().catch((err) => {
74
+ console.error(err);
75
+ process.exit(1);
76
+ });
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "@vierratale/ai",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.11",
4
4
  "description": "VierrataleAI - Intelligent terminal assistant",
5
5
  "type": "module",
6
+ "files": [
7
+ "bin/",
8
+ "scripts/",
9
+ "src/",
10
+ "README.md"
11
+ ],
6
12
  "bin": {
7
13
  "vierrataleai": "./bin/vierrataleai.js"
8
14
  },
@@ -11,7 +17,9 @@
11
17
  ".": "./src/index.js"
12
18
  },
13
19
  "scripts": {
14
- "start": "node bin/vierrataleai.js"
20
+ "start": "node bin/vierrataleai.js",
21
+ "test": "node --test \"test/*.test.js\"",
22
+ "postinstall": "node scripts/postinstall.js"
15
23
  },
16
24
  "keywords": ["ai", "chatbot", "terminal", "assistant", "llm"],
17
25
  "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
@@ -1,16 +1,36 @@
1
+ // Model catalog. Display names use the countable VRTL branding:
2
+ // VRTL-<number>.<word>, e.g. VRTL-2.fast, VRTL-7.cloud-mini.
1
3
  const MODELS = {
2
- 'qwen2.5-coder:0.5b': 'vierratale-lite',
3
- 'qwen2.5-coder:1.5b': 'vierratale-fast',
4
- 'qwen2.5-coder:3b': 'vierratale-small',
5
- 'qwen2.5-coder:7b': 'vierratale-balanced',
6
- 'qwen2.5-coder:14b': 'vierratale-plus',
7
- 'qwen2.5-coder:32b': 'vierratale-pro',
8
- 'gpt-4o-mini': 'vierratale-cloud-mini',
9
- 'gpt-4o': 'vierratale-cloud',
10
- 'claude-3-5-haiku-20241022': 'vierratale-cloud-fast',
11
- 'claude-sonnet-4-20250514': 'vierratale-cloud-pro',
12
- 'gemini-2.0-flash': 'vierratale-cloud-lite',
13
- 'gemini-1.5-pro': 'vierratale-cloud-plus',
4
+ 'qwen3:0.6b': 'VRTL-1.lite',
5
+ 'gemma3:1b': 'VRTL-2.fast',
6
+ 'llama3.2:1b': 'VRTL-3.small',
7
+ 'qwen2.5:1.5b': 'VRTL-4.balanced',
8
+ 'qwen2.5-coder:1.5b': 'VRTL-4.coder',
9
+ 'qwen3:1.7b': 'VRTL-5.plus',
10
+ 'qwen2.5:3b': 'VRTL-6.pro',
11
+ 'gpt-4o-mini': 'VRTL-7.cloud-mini',
12
+ 'gpt-4o': 'VRTL-8.cloud',
13
+ 'claude-3-5-haiku-20241022': 'VRTL-9.cloud-fast',
14
+ 'claude-sonnet-4-20250514': 'VRTL-10.cloud-pro',
15
+ 'gemini-2.0-flash': 'VRTL-11.cloud-lite',
16
+ 'gemini-1.5-pro': 'VRTL-12.cloud-plus',
17
+ };
18
+
19
+ // Older `vierratale-<word>` names, accepted everywhere for backwards
20
+ // compatibility (existing config files may still contain them).
21
+ const LEGACY_DISPLAY = {
22
+ 'vierratale-lite': 'VRTL-1.lite',
23
+ 'vierratale-fast': 'VRTL-2.fast',
24
+ 'vierratale-small': 'VRTL-3.small',
25
+ 'vierratale-balanced': 'VRTL-4.balanced',
26
+ 'vierratale-plus': 'VRTL-5.plus',
27
+ 'vierratale-pro': 'VRTL-6.pro',
28
+ 'vierratale-cloud-mini': 'VRTL-7.cloud-mini',
29
+ 'vierratale-cloud': 'VRTL-8.cloud',
30
+ 'vierratale-cloud-fast': 'VRTL-9.cloud-fast',
31
+ 'vierratale-cloud-pro': 'VRTL-10.cloud-pro',
32
+ 'vierratale-cloud-lite': 'VRTL-11.cloud-lite',
33
+ 'vierratale-cloud-plus': 'VRTL-12.cloud-plus',
14
34
  };
15
35
 
16
36
  const REVERSE = Object.fromEntries(
@@ -18,26 +38,48 @@ const REVERSE = Object.fromEntries(
18
38
  );
19
39
 
20
40
  const LOCAL_MODELS = [
21
- 'vierratale-lite',
22
- 'vierratale-fast',
23
- 'vierratale-small',
24
- 'vierratale-balanced',
25
- 'vierratale-plus',
26
- 'vierratale-pro',
41
+ 'VRTL-1.lite',
42
+ 'VRTL-2.fast',
43
+ 'VRTL-3.small',
44
+ 'VRTL-4.balanced',
45
+ 'VRTL-4.coder',
46
+ 'VRTL-5.plus',
47
+ 'VRTL-6.pro',
27
48
  ];
28
49
 
29
50
  const CLOUD_MODELS = [
30
- 'vierratale-cloud-mini',
31
- 'vierratale-cloud',
32
- 'vierratale-cloud-fast',
33
- 'vierratale-cloud-pro',
34
- 'vierratale-cloud-lite',
35
- 'vierratale-cloud-plus',
51
+ 'VRTL-7.cloud-mini',
52
+ 'VRTL-8.cloud',
53
+ 'VRTL-9.cloud-fast',
54
+ 'VRTL-10.cloud-pro',
55
+ 'VRTL-11.cloud-lite',
56
+ 'VRTL-12.cloud-plus',
36
57
  ];
37
58
 
59
+ const TIERS = {
60
+ 'VRTL-1.lite': 'minimal',
61
+ 'VRTL-2.fast': 'fast',
62
+ 'VRTL-3.small': 'small',
63
+ 'VRTL-4.balanced': 'balanced',
64
+ 'VRTL-4.coder': 'coder',
65
+ 'VRTL-5.plus': 'plus',
66
+ 'VRTL-6.pro': 'professional',
67
+ 'VRTL-7.cloud-mini': 'cloud-mini',
68
+ 'VRTL-8.cloud': 'cloud',
69
+ 'VRTL-9.cloud-fast': 'cloud-fast',
70
+ 'VRTL-10.cloud-pro': 'cloud-pro',
71
+ 'VRTL-11.cloud-lite': 'cloud-lite',
72
+ 'VRTL-12.cloud-plus': 'cloud-plus',
73
+ };
74
+
38
75
  export const Catalog = {
76
+ // Map any known name (new or legacy) to the current VRTL display name.
77
+ normalize(displayName) {
78
+ return LEGACY_DISPLAY[displayName] || displayName;
79
+ },
80
+
39
81
  getRealModel(displayName) {
40
- return REVERSE[displayName] || displayName;
82
+ return REVERSE[this.normalize(displayName)] || displayName;
41
83
  },
42
84
 
43
85
  getDisplayName(realModel) {
@@ -57,34 +99,41 @@ export const Catalog = {
57
99
  },
58
100
 
59
101
  getDefaultModel() {
60
- return 'vierratale-fast';
102
+ return 'VRTL-2.fast';
103
+ },
104
+
105
+ getDefaultCloudModel() {
106
+ return 'VRTL-7.cloud-mini';
107
+ },
108
+
109
+ isLocalModel(displayName) {
110
+ return LOCAL_MODELS.includes(this.normalize(displayName));
111
+ },
112
+
113
+ isCloudModel(displayName) {
114
+ return CLOUD_MODELS.includes(this.normalize(displayName));
115
+ },
116
+
117
+ getVendorForCloudModel(displayName) {
118
+ const real = REVERSE[this.normalize(displayName)];
119
+ if (real && real.startsWith('claude-')) return 'anthropic';
120
+ if (real && real.startsWith('gemini-')) return 'gemini';
121
+ if (real && real.startsWith('gpt-')) return 'openai';
122
+ return 'openai';
61
123
  },
62
124
 
63
125
  getModelInfo(displayName) {
64
- const real = REVERSE[displayName];
126
+ const name = this.normalize(displayName);
127
+ const real = REVERSE[name];
65
128
  if (!real) return null;
66
- const isLocal = LOCAL_MODELS.includes(displayName);
67
- const isCloud = CLOUD_MODELS.includes(displayName);
68
- const tiers = {
69
- 'vierratale-lite': 'minimal',
70
- 'vierratale-fast': 'fast',
71
- 'vierratale-small': 'small',
72
- 'vierratale-balanced': 'balanced',
73
- 'vierratale-plus': 'plus',
74
- 'vierratale-pro': 'professional',
75
- 'vierratale-cloud-mini': 'cloud-mini',
76
- 'vierratale-cloud': 'cloud',
77
- 'vierratale-cloud-fast': 'cloud-fast',
78
- 'vierratale-cloud-pro': 'cloud-pro',
79
- 'vierratale-cloud-lite': 'cloud-lite',
80
- 'vierratale-cloud-plus': 'cloud-plus',
81
- };
129
+ const isLocal = LOCAL_MODELS.includes(name);
130
+ const isCloud = CLOUD_MODELS.includes(name);
82
131
  return {
83
132
  displayName,
84
133
  realModel: real,
85
134
  isLocal,
86
135
  isCloud,
87
- tier: tiers[displayName] || 'unknown',
136
+ tier: TIERS[name] || 'unknown',
88
137
  };
89
138
  },
90
- };
139
+ };