@vierratale/ai 0.1.0-beta.13 → 0.1.0-beta.15

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vierratale
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -22,7 +22,7 @@ npx @vierratale/ai
22
22
  ```
23
23
 
24
24
  The first launch detects and, if needed, installs and starts the local engine
25
- (**Cortex** - "secretly" Ollama under the hood), then pulls the model you use.
25
+ (**Cortex**), then pulls the model you use.
26
26
 
27
27
  ## Usage
28
28
 
@@ -32,36 +32,44 @@ vierrataleai --provider cortex # Use local models
32
32
  vierrataleai --provider openai # Use cloud models (OpenAI)
33
33
  vierrataleai --provider anthropic # Use cloud models (Claude)
34
34
  vierrataleai --provider gemini # Use cloud models (Gemini)
35
- vierrataleai --model VRTL-6.pro # Use specific model
35
+ vierrataleai --model VTL-3.7-Ultra # Use specific model
36
36
  ```
37
37
 
38
38
  ## Models
39
39
 
40
40
  Local models run through the **Cortex** engine:
41
41
 
42
- | Model | Backend |
43
- |-------|---------|
44
- | VRTL-1.lite | qwen3:0.6b |
45
- | VRTL-2.fast | gemma3:1b |
46
- | VRTL-3.small | llama3.2:1b |
47
- | VRTL-4.balanced | qwen2.5:1.5b |
48
- | VRTL-5.plus | qwen3:1.7b |
49
- | VRTL-6.pro | qwen2.5:3b |
42
+ | Model | Tier |
43
+ |-------|------|
44
+ | VTL-2.5-Mini | minimal |
45
+ | VTL-2.7-Flash | fast |
46
+ | VTL-2.9-Core | small |
47
+ | VTL-3.2-Orbit | small |
48
+ | VTL-3.1-Plus | balanced |
49
+ | VTL-3.3-Pro | coder |
50
+ | VTL-3.5-Reason | reasoning |
51
+ | VTL-3.7-Ultra | ultra |
52
+ | VTL-4.7-Gusto | balanced |
53
+ | VTL-5.4-Tempo | balanced |
54
+ | VTL-5.2-Pinnacle | pro |
55
+ | VTL-5.9-Sovereign | pro |
50
56
 
51
57
  Cloud models are available when the matching provider + API key is configured:
52
58
 
53
- | Model | Backend |
54
- |-------|---------|
55
- | VRTL-7.cloud-mini | gpt-4o-mini (OpenAI) |
56
- | VRTL-8.cloud | gpt-4o (OpenAI) |
57
- | VRTL-9.cloud-fast | claude-3-5-haiku-20241022 (Anthropic) |
58
- | VRTL-10.cloud-pro | claude-sonnet-4-20250514 (Anthropic) |
59
- | VRTL-11.cloud-lite | gemini-2.0-flash (Gemini) |
60
- | VRTL-12.cloud-plus | gemini-1.5-pro (Gemini) |
61
-
62
- > Legacy `vierratale-*` model names (e.g. `vierratale-fast`, `vierratale-cloud`)
63
-
64
- > still work and are stored as VRTL names.
59
+ | Model | Tier |
60
+ |-------|------|
61
+ | VTL-4.0 | cloud-mini |
62
+ | VTL-4.2-Omni | cloud |
63
+ | VTL-4.5-Plus | cloud-fast |
64
+ | VTL-5.0-Pro | cloud-pro |
65
+ | VTL-6.0-Reason | cloud-lite |
66
+ | VTL-7.0-Omnij | cloud-plus |
67
+ | VTL-8.0-Fabric | cloud-fabric |
68
+ | VTL-9.0-Forge | cloud-forge |
69
+ | VTL-10.0-Singularity | cloud-singularity |
70
+
71
+ > Legacy `vierratale-*` and old `VRTL-*` model names (e.g. `vierratale-fast`,
72
+ > `vierratale-cloud`) still work and map to the VTL names.
65
73
 
66
74
  ## Commands
67
75
 
@@ -9,11 +9,9 @@
9
9
  // Installation progress is always the fake animated bar — real engine/model
10
10
  // output is deliberately hidden so first-run feels quick and clean.
11
11
  import { run } from '../src/cli.js';
12
- import { Installer } from '../src/installer.js';
12
+ import { Engine } from '../src/engine.js';
13
13
  import { Config } from '../src/config.js';
14
14
  import { Branding } from '../src/ui/branding.js';
15
- import { Terminal } from '../src/ui/terminal.js';
16
- import { dirname } from 'path';
17
15
 
18
16
  const C = Branding.colors;
19
17
 
@@ -63,19 +61,11 @@ async function main() {
63
61
 
64
62
  if (doInstall) {
65
63
  console.log(`\n ${C.bold}${C.primary}${Branding.APP_NAME} ${Branding.VERSION}${C.reset} ${C.dim}— installer${C.reset}\n`);
66
- const result = await fakeProgress(INSTALL_STEPS, Installer.install());
67
- if (!result.command) {
68
- Terminal.printInfo('Could not create a global command; run it via the bin script.');
69
- } else {
70
- Terminal.printSuccess(`Engine + model installed; "vierrataleai" (or "vierratale") command available at ${result.commandAlias || result.command}`);
71
- if (!result.commandOnPath) {
72
- Terminal.printInfo(`"vierrataleai" won't resolve in a new shell: add ${dirname(result.command)} to PATH, e.g. export PATH="$HOME/.local/bin:$PATH", then reopen the terminal.`);
73
- }
74
- }
64
+ await fakeProgress(INSTALL_STEPS, Engine.ensure());
75
65
  console.log();
76
66
  } else if (!quick) {
77
67
  // Auto-detect: install anything that's missing (engine, model) before starting.
78
- await fakeProgress(READY_STEPS, Installer.ensure());
68
+ await fakeProgress(READY_STEPS, Engine.ensure());
79
69
  console.log();
80
70
  }
81
71
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vierratale/ai",
3
- "version": "0.1.0-beta.13",
3
+ "version": "0.1.0-beta.15",
4
4
  "description": "VierrataleAI - Intelligent terminal assistant",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,6 +18,7 @@
18
18
  "bin",
19
19
  "src",
20
20
  "scripts",
21
+ "LICENSE",
21
22
  "README.md"
22
23
  ],
23
24
  "keywords": [
@@ -25,9 +25,9 @@ try {
25
25
  execSync('curl -fsSL https://ollama.com/install.sh | sh', { stdio: 'inherit' });
26
26
  } else {
27
27
  console.log(
28
- '\n[vierrataleai] The local engine (ollama) was not detected.\n' +
28
+ '\n[vierrataleai] The local engine was not detected.\n' +
29
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'
30
+ ' - Or run ./install.sh (in the project repo) to set up the engine.\n'
31
31
  );
32
32
  }
33
33
  } catch {
package/src/catalog.js CHANGED
@@ -1,36 +1,59 @@
1
- // Model catalog. Display names use the countable VRTL branding:
2
- // VRTL-<number>.<word>, e.g. VRTL-2.fast, VRTL-7.cloud-mini.
1
+ // Model catalog. Display names use the opaque VTL branding:
2
+ // VTL-<number>.<name>, e.g. VTL-2.7-Flash, VTL-4.0.
3
+ // The real engine tags are mapped to display names here and kept internal.
3
4
  const MODELS = {
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',
5
+ 'qwen3:0.6b': 'VTL-2.5-Mini',
6
+ 'gemma3:1b': 'VTL-2.7-Flash',
7
+ 'llama3.2:1b': 'VTL-2.9-Core',
8
+ 'llama3.2:3b': 'VTL-3.2-Orbit',
9
+ 'qwen2.5:1.5b': 'VTL-3.1-Plus',
10
+ 'qwen2.5-coder:1.5b': 'VTL-3.3-Pro',
11
+ 'qwen3:1.7b': 'VTL-3.5-Reason',
12
+ 'qwen2.5:3b': 'VTL-3.7-Ultra',
13
+ 'gemma3:4b': 'VTL-4.7-Gusto',
14
+ 'phi4-mini': 'VTL-5.4-Tempo',
15
+ 'glm4:9b': 'VTL-5.2-Pinnacle',
16
+ 'qwen3:4b': 'VTL-5.9-Sovereign',
17
+ 'gpt-4o-mini': 'VTL-4.0',
18
+ 'gpt-4o': 'VTL-4.2-Omni',
19
+ 'claude-3-5-haiku-20241022': 'VTL-4.5-Plus',
20
+ 'claude-sonnet-4-20250514': 'VTL-5.0-Pro',
21
+ 'gemini-2.0-flash': 'VTL-6.0-Reason',
22
+ 'gemini-1.5-pro': 'VTL-7.0-Omnij',
23
+ 'kimi-k2.6:cloud': 'VTL-8.0-Fabric',
24
+ 'kimi-k2.7-code:cloud': 'VTL-9.0-Forge',
25
+ 'kimi-k3': 'VTL-10.0-Singularity',
17
26
  };
18
27
 
19
- // Older `vierratale-<word>` names, accepted everywhere for backwards
20
- // compatibility (existing config files may still contain them).
28
+ // Legacy display names (older `VRTL-*` and `vierratale-<word>`), accepted
29
+ // everywhere for backwards compatibility (existing config files may contain
30
+ // them). All map onto the current VTL display names.
21
31
  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',
32
+ 'VRTL-1.lite': 'VTL-2.5-Mini',
33
+ 'VRTL-2.fast': 'VTL-2.7-Flash',
34
+ 'VRTL-3.small': 'VTL-2.9-Core',
35
+ 'VRTL-4.balanced': 'VTL-3.1-Plus',
36
+ 'VRTL-4.coder': 'VTL-3.3-Pro',
37
+ 'VRTL-5.plus': 'VTL-3.5-Reason',
38
+ 'VRTL-6.pro': 'VTL-3.7-Ultra',
39
+ 'VRTL-7.cloud-mini': 'VTL-4.0',
40
+ 'VRTL-8.cloud': 'VTL-4.2-Omni',
41
+ 'VRTL-9.cloud-fast': 'VTL-4.5-Plus',
42
+ 'VRTL-10.cloud-pro': 'VTL-5.0-Pro',
43
+ 'VRTL-11.cloud-lite': 'VTL-6.0-Reason',
44
+ 'VRTL-12.cloud-plus': 'VTL-7.0-Omnij',
45
+ 'vierratale-lite': 'VTL-2.5-Mini',
46
+ 'vierratale-fast': 'VTL-2.7-Flash',
47
+ 'vierratale-small': 'VTL-2.9-Core',
48
+ 'vierratale-balanced': 'VTL-3.1-Plus',
49
+ 'vierratale-plus': 'VTL-3.5-Reason',
50
+ 'vierratale-pro': 'VTL-3.7-Ultra',
51
+ 'vierratale-cloud-mini': 'VTL-4.0',
52
+ 'vierratale-cloud': 'VTL-4.2-Omni',
53
+ 'vierratale-cloud-fast': 'VTL-4.5-Plus',
54
+ 'vierratale-cloud-pro': 'VTL-5.0-Pro',
55
+ 'vierratale-cloud-lite': 'VTL-6.0-Reason',
56
+ 'vierratale-cloud-plus': 'VTL-7.0-Omnij',
34
57
  };
35
58
 
36
59
  const REVERSE = Object.fromEntries(
@@ -38,42 +61,58 @@ const REVERSE = Object.fromEntries(
38
61
  );
39
62
 
40
63
  const LOCAL_MODELS = [
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',
64
+ 'VTL-2.5-Mini',
65
+ 'VTL-2.7-Flash',
66
+ 'VTL-2.9-Core',
67
+ 'VTL-3.2-Orbit',
68
+ 'VTL-3.1-Plus',
69
+ 'VTL-3.3-Pro',
70
+ 'VTL-3.5-Reason',
71
+ 'VTL-3.7-Ultra',
72
+ 'VTL-4.7-Gusto',
73
+ 'VTL-5.4-Tempo',
74
+ 'VTL-5.2-Pinnacle',
75
+ 'VTL-5.9-Sovereign',
48
76
  ];
49
77
 
50
78
  const CLOUD_MODELS = [
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',
79
+ 'VTL-4.0',
80
+ 'VTL-4.2-Omni',
81
+ 'VTL-4.5-Plus',
82
+ 'VTL-5.0-Pro',
83
+ 'VTL-6.0-Reason',
84
+ 'VTL-7.0-Omnij',
85
+ 'VTL-8.0-Fabric',
86
+ 'VTL-9.0-Forge',
87
+ 'VTL-10.0-Singularity',
57
88
  ];
58
89
 
59
90
  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',
91
+ 'VTL-2.5-Mini': 'minimal',
92
+ 'VTL-2.7-Flash': 'fast',
93
+ 'VTL-2.9-Core': 'small',
94
+ 'VTL-3.2-Orbit': 'small',
95
+ 'VTL-3.1-Plus': 'balanced',
96
+ 'VTL-3.3-Pro': 'coder',
97
+ 'VTL-3.5-Reason': 'reasoning',
98
+ 'VTL-3.7-Ultra': 'ultra',
99
+ 'VTL-4.7-Gusto': 'balanced',
100
+ 'VTL-5.4-Tempo': 'balanced',
101
+ 'VTL-5.2-Pinnacle': 'pro',
102
+ 'VTL-5.9-Sovereign': 'pro',
103
+ 'VTL-4.0': 'cloud-mini',
104
+ 'VTL-4.2-Omni': 'cloud',
105
+ 'VTL-4.5-Plus': 'cloud-fast',
106
+ 'VTL-5.0-Pro': 'cloud-pro',
107
+ 'VTL-6.0-Reason': 'cloud-lite',
108
+ 'VTL-7.0-Omnij': 'cloud-plus',
109
+ 'VTL-8.0-Fabric': 'cloud-fabric',
110
+ 'VTL-9.0-Forge': 'cloud-forge',
111
+ 'VTL-10.0-Singularity': 'cloud-singularity',
73
112
  };
74
113
 
75
114
  export const Catalog = {
76
- // Map any known name (new or legacy) to the current VRTL display name.
115
+ // Map any known name (new or legacy) to the current VTL display name.
77
116
  normalize(displayName) {
78
117
  return LEGACY_DISPLAY[displayName] || displayName;
79
118
  },
@@ -99,11 +138,11 @@ export const Catalog = {
99
138
  },
100
139
 
101
140
  getDefaultModel() {
102
- return 'VRTL-2.fast';
141
+ return 'VTL-2.7-Flash';
103
142
  },
104
143
 
105
144
  getDefaultCloudModel() {
106
- return 'VRTL-7.cloud-mini';
145
+ return 'VTL-4.0';
107
146
  },
108
147
 
109
148
  isLocalModel(displayName) {
@@ -136,4 +175,4 @@ export const Catalog = {
136
175
  tier: TIERS[name] || 'unknown',
137
176
  };
138
177
  },
139
- };
178
+ };
package/src/cli.js CHANGED
@@ -2,7 +2,7 @@ import { execFile } from 'child_process';
2
2
  import { promisify } from 'util';
3
3
  import { Config } from './config.js';
4
4
  import { Catalog } from './catalog.js';
5
- import { Installer } from './installer.js';
5
+ import { Engine } from './engine.js';
6
6
  import { ProviderFactory } from './providers/index.js';
7
7
  import { Branding } from './ui/branding.js';
8
8
  import { Terminal } from './ui/terminal.js';
@@ -41,7 +41,7 @@ Usage: vierrataleai [options]
41
41
 
42
42
  Options:
43
43
  --provider, -p <name> Provider: cortex, openai (default: auto-detect)
44
- --model, -m <name> Model: VRTL-1.lite/2.fast/4.balanced/6.pro/8.cloud
44
+ --model, -m <name> Model: e.g. VTL-2.7-Flash/3.5-Reason/6.0-Reason
45
45
  --clear (deprecated: screen is always cleared on start)
46
46
  --install, --setup Install engine + model + a "vierrataleai" command, then run
47
47
  --version, -v Show version
@@ -811,7 +811,7 @@ async function chat(provider, systemPrompt) {
811
811
  : Catalog.getCloudModels();
812
812
  const installedSet = new Set();
813
813
  if (provider.isLocal) {
814
- for (const m of await Installer.getInstalledModels(Config.get('engineHost'))) {
814
+ for (const m of await Engine.getInstalledModels(Config.get('engineHost'))) {
815
815
  installedSet.add(m);
816
816
  }
817
817
  }
@@ -830,8 +830,8 @@ async function chat(provider, systemPrompt) {
830
830
  const extras = [...installedSet].filter(
831
831
  (m) => !Catalog.getModelInfo(m) && m !== Config.getEffectiveModel(provider.name)
832
832
  );
833
- for (const m of extras) {
834
- chatUI.notify(` ${m} ✓ (raw engine model)`);
833
+ if (extras.length) {
834
+ chatUI.notify(` (+${extras.length} additional engine models available)`);
835
835
  }
836
836
  }
837
837
  chatUI.render(messages);
@@ -850,7 +850,7 @@ async function chat(provider, systemPrompt) {
850
850
  chatUI.setModel(model, provider.displayName);
851
851
  };
852
852
  if (provider.isLocal && Catalog.isCloudModel(canonical)) {
853
- chatUI.notify(`Cannot use cloud model "${canonical}" on ${provider.displayName} (local). Use /model VRTL-6.pro or /provider openai.`);
853
+ chatUI.notify(`Cannot use cloud model "${canonical}" on ${provider.displayName} (local). Use /model VTL-3.7-Ultra or /provider openai.`);
854
854
  } else if (!info) {
855
855
  if (provider.isLocal) {
856
856
  const switched = await ensureRawLocalModel(modelName, (m) => chatUI.notify(m));
@@ -993,7 +993,7 @@ async function chat(provider, systemPrompt) {
993
993
  const info = Catalog.getModelInfo(model);
994
994
  chatUI.clearNotices();
995
995
  chatUI.notify(
996
- `I'm running on ${model} (${info ? info.realModel : model}) via the ${provider.displayName} engine.`
996
+ `I'm running on ${model} via the ${provider.displayName} engine.`
997
997
  );
998
998
  chatUI.render(messages);
999
999
  continue;
@@ -1114,18 +1114,18 @@ async function ensureLocalModel(modelName, notify) {
1114
1114
  // dead-ends while at least one local model exists.
1115
1115
  async function ensureRawLocalModel(realModel, notify) {
1116
1116
  const host = Config.get('engineHost');
1117
- const resolved = await Installer.resolveModel(host, realModel);
1117
+ const resolved = await Engine.resolveModel(host, realModel);
1118
1118
  if (resolved.model) {
1119
1119
  return { ok: true, model: resolved.model, reason: resolved.reason };
1120
1120
  }
1121
1121
  if (notify) notify(`Downloading ${realModel}… (this can take a while)`);
1122
- const pulled = await Installer.pullModel(host, realModel);
1122
+ const pulled = await Engine.pullModel(host, realModel);
1123
1123
  if (pulled) {
1124
1124
  if (notify) notify(`Model ${realModel} ready.`);
1125
1125
  return { ok: true, model: realModel, reason: null };
1126
1126
  }
1127
1127
  if (notify) notify(`Could not download ${realModel}.`);
1128
- const first = await Installer.firstInstalled(host);
1128
+ const first = await Engine.firstInstalled(host);
1129
1129
  if (first) {
1130
1130
  return { ok: true, model: first, reason: `Could not download ${realModel}; using installed ${first}` };
1131
1131
  }
@@ -1264,7 +1264,7 @@ export async function run() {
1264
1264
 
1265
1265
  await Terminal.showProgress(
1266
1266
  ['Checking engine', 'Installing dependencies', 'Downloading model', 'Optimizing'],
1267
- () => Installer.ensure()
1267
+ () => Engine.ensure()
1268
1268
  );
1269
1269
 
1270
1270
  await ensureLocalModel(Config.get('model'));
package/src/config.js CHANGED
@@ -12,7 +12,7 @@ function configPaths() {
12
12
 
13
13
  const DEFAULTS = {
14
14
  provider: 'auto',
15
- model: 'VRTL-2.fast',
15
+ model: 'VTL-2.7-Flash',
16
16
  engineHost: 'http://127.0.0.1:11434',
17
17
  numCtx: 2048,
18
18
  temperature: 0.7,
package/src/engine.js ADDED
@@ -0,0 +1,150 @@
1
+ import { execSync, exec } from 'child_process';
2
+ import { existsSync } from 'fs';
3
+ import { Catalog } from './catalog.js';
4
+ import { Config } from './config.js';
5
+
6
+ function runSilent(cmd) {
7
+ return new Promise((resolve) => {
8
+ exec(cmd, { stdio: 'ignore' }, (err) => resolve(!err));
9
+ });
10
+ }
11
+
12
+ function isEngineRunning(host) {
13
+ return fetch(`${host}/api/tags`, { signal: AbortSignal.timeout(3000) })
14
+ .then((r) => r.ok)
15
+ .catch(() => false);
16
+ }
17
+
18
+ function isEngineInstalled() {
19
+ try {
20
+ execSync('which ollama 2>/dev/null', { stdio: 'ignore' });
21
+ return true;
22
+ } catch {}
23
+ const paths = ['/usr/local/bin/ollama', '/usr/bin/ollama'];
24
+ return paths.some((p) => existsSync(p));
25
+ }
26
+
27
+ async function installEngine() {
28
+ await runSilent('curl -fsSL https://ollama.com/install.sh | sh');
29
+ return isEngineInstalled();
30
+ }
31
+
32
+ async function getInstalledModels(host) {
33
+ try {
34
+ const resp = await fetch(`${host}/api/tags`);
35
+ if (!resp.ok) return [];
36
+ const data = await resp.json();
37
+ return (data.models || []).map((m) => m.name);
38
+ } catch {
39
+ return [];
40
+ }
41
+ }
42
+
43
+ async function pullModel(host, model) {
44
+ try {
45
+ const resp = await fetch(`${host}/api/pull`, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify({ name: model, stream: false }),
49
+ signal: AbortSignal.timeout(600000),
50
+ });
51
+ return resp.ok;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ function familyOf(model) {
58
+ return model
59
+ .split(':')[0]
60
+ .toLowerCase()
61
+ .replace(/-(coder|instruct|reasoning|chat|base|tiny|nano)$/, '');
62
+ }
63
+
64
+ function sizeOf(model) {
65
+ const size = model.split(':')[1] || '';
66
+ const match = size.match(/(\d+(?:\.\d+)?)(m|b)/i);
67
+ if (!match) return Number.POSITIVE_INFINITY;
68
+ const value = parseFloat(match[1]);
69
+ return match[2].toLowerCase() === 'b' ? value * 1000 : value;
70
+ }
71
+
72
+ function chooseModel(installed, realModel) {
73
+ if (installed.includes(realModel)) {
74
+ return { model: realModel, substituted: false, reason: null };
75
+ }
76
+ const fam = familyOf(realModel);
77
+ let best = null;
78
+ let bestDist = Number.POSITIVE_INFINITY;
79
+ for (const m of installed) {
80
+ if (familyOf(m) !== fam) continue;
81
+ const dist = Math.abs(sizeOf(m) - sizeOf(realModel)) + 0.5;
82
+ if (dist < bestDist) {
83
+ bestDist = dist;
84
+ best = m;
85
+ }
86
+ }
87
+ if (best) {
88
+ return {
89
+ model: best,
90
+ substituted: true,
91
+ reason: `"${realModel}" is not installed; using ${best} (same family)`,
92
+ };
93
+ }
94
+ return { model: null, substituted: true, reason: `"${realModel}" is not installed` };
95
+ }
96
+
97
+ async function resolveModel(host, realModel) {
98
+ const installed = await getInstalledModels(host);
99
+ return chooseModel(installed, realModel);
100
+ }
101
+
102
+ async function ensure() {
103
+ const host = Config.get('engineHost');
104
+
105
+ if (!isEngineInstalled()) {
106
+ await installEngine();
107
+ }
108
+
109
+ const running = await isEngineRunning(host);
110
+ if (!running) {
111
+ try {
112
+ const child = exec('ollama serve', {
113
+ stdio: 'ignore',
114
+ detached: true,
115
+ });
116
+ if (child.unref) child.unref();
117
+ for (let i = 0; i < 30; i++) {
118
+ await new Promise((r) => setTimeout(r, 1000));
119
+ if (await isEngineRunning(host)) break;
120
+ }
121
+ } catch {}
122
+ }
123
+
124
+ const realModel = Catalog.getRealModel(Config.get('model'));
125
+ const installed = await getInstalledModels(host);
126
+ const resolved = chooseModel(installed, realModel);
127
+ if (resolved.model == null) {
128
+ await pullModel(host, realModel);
129
+ }
130
+
131
+ return true;
132
+ }
133
+
134
+ async function isReady() {
135
+ const host = Config.get('engineHost');
136
+ return isEngineRunning(host);
137
+ }
138
+
139
+ export const Engine = {
140
+ ensure,
141
+ isReady,
142
+ pullModel,
143
+ getInstalledModels,
144
+ resolveModel,
145
+ chooseModel,
146
+ firstInstalled: async (host) => {
147
+ const installed = await getInstalledModels(host);
148
+ return installed[0] || null;
149
+ },
150
+ };
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { Catalog } from './catalog.js';
2
2
  export { Config } from './config.js';
3
- export { Installer } from './installer.js';
3
+ export { Engine } from './engine.js';
4
4
  export { CortexProvider } from './providers/cortex.js';
5
5
  export { OpenAIProvider } from './providers/openai.js';
6
6
  export { Branding } from './ui/branding.js';
@@ -285,14 +285,14 @@ export class CortexProvider extends BaseProvider {
285
285
  // engine host is deliberately kept out of the message; errors carry codes.
286
286
  async function describeEngineError(resp, model) {
287
287
  if (/not found|does not exist|model.*missing/i.test(String(resp.statusText))) {
288
- return `[ERR-0002] Model "${model}" is not installed on the engine. If you picked a cloud model, use /model VRTL-6.pro or /provider openai.`;
288
+ return `[ERR-0002] Model "${model}" is not installed on the engine. If you picked a cloud model, use /model VTL-3.7-Ultra or /provider openai.`;
289
289
  }
290
290
  let body = '';
291
291
  try {
292
292
  body = (await resp.text()).slice(0, 300);
293
293
  } catch {}
294
294
  if (/not found|does not exist|model.*missing/i.test(body)) {
295
- return `[ERR-0002] Model "${model}" is not installed on the engine. If you picked a cloud model, use /model VRTL-6.pro or /provider openai.`;
295
+ return `[ERR-0002] Model "${model}" is not installed on the engine. If you picked a cloud model, use /model VTL-3.7-Ultra or /provider openai.`;
296
296
  }
297
- return `[ERR-0003] The engine returned HTTP ${resp.status}. Check the engine status or run /model VRTL-2.fast.`;
297
+ return `[ERR-0003] The engine returned HTTP ${resp.status}. Check the engine status or run /model VTL-2.7-Flash.`;
298
298
  }
@@ -1,6 +1,6 @@
1
1
  export const Branding = {
2
2
  APP_NAME: 'VierrataleAI',
3
- VERSION: '0.1.0-beta.12',
3
+ VERSION: '0.1.0-beta.15',
4
4
 
5
5
  colors: {
6
6
  primary: '\x1b[38;2;124;58;237m',
package/src/installer.js DELETED
@@ -1,300 +0,0 @@
1
- import { execSync, exec } from 'child_process';
2
- import { existsSync, mkdirSync, symlinkSync, chmodSync, writeFileSync, lstatSync, readlinkSync, rmSync, realpathSync } from 'fs';
3
- import { join, dirname, resolve } from 'path';
4
- import { fileURLToPath } from 'url';
5
- import { homedir, platform } from 'os';
6
- import { Catalog } from './catalog.js';
7
- import { Config } from './config.js';
8
-
9
- function runSilent(cmd) {
10
- return new Promise((resolve) => {
11
- exec(cmd, { stdio: 'ignore' }, (err) => resolve(!err));
12
- });
13
- }
14
-
15
- function isEngineRunning(host) {
16
- return fetch(`${host}/api/tags`, { signal: AbortSignal.timeout(3000) })
17
- .then((r) => r.ok)
18
- .catch(() => false);
19
- }
20
-
21
- function isEngineInstalled() {
22
- try {
23
- execSync('which ollama 2>/dev/null', { stdio: 'ignore' });
24
- return true;
25
- } catch {}
26
- const paths = ['/usr/local/bin/ollama', '/usr/bin/ollama'];
27
- return paths.some((p) => existsSync(p));
28
- }
29
-
30
- async function installEngine() {
31
- await runSilent('curl -fsSL https://ollama.com/install.sh | sh');
32
- return isEngineInstalled();
33
- }
34
-
35
- async function pullModel(host, model) {
36
- try {
37
- const resp = await fetch(`${host}/api/pull`, {
38
- method: 'POST',
39
- headers: { 'Content-Type': 'application/json' },
40
- body: JSON.stringify({ name: model, stream: false }),
41
- signal: AbortSignal.timeout(600000),
42
- });
43
- return resp.ok;
44
- } catch {
45
- return false;
46
- }
47
- }
48
-
49
- async function getInstalledModels(host) {
50
- try {
51
- const resp = await fetch(`${host}/api/tags`);
52
- if (!resp.ok) return [];
53
- const data = await resp.json();
54
- return (data.models || []).map((m) => m.name);
55
- } catch {
56
- return [];
57
- }
58
- }
59
-
60
- // Scripts in the same model family share a base name before any variant
61
- // suffix (e.g. qwen2.5-coder:1.5b is the sibling of qwen2.5:1.5b).
62
- function familyOf(model) {
63
- return model
64
- .split(':')[0]
65
- .toLowerCase()
66
- .replace(/-(coder|instruct|reasoning|chat|base|tiny|nano)$/, '');
67
- }
68
-
69
- function sizeOf(model) {
70
- const size = model.split(':')[1] || '';
71
- const match = size.match(/(\d+(?:\.\d+)?)(m|b)/i);
72
- if (!match) return Number.POSITIVE_INFINITY;
73
- const value = parseFloat(match[1]);
74
- return match[2].toLowerCase() === 'b' ? value * 1000 : value;
75
- }
76
-
77
- // Find the already-installed model that best matches the requested one.
78
- // Prefers an exact match, then any same-family sibling (closest size).
79
- function chooseModel(installed, realModel) {
80
- if (installed.includes(realModel)) {
81
- return { model: realModel, substituted: false, reason: null };
82
- }
83
- const fam = familyOf(realModel);
84
- let best = null;
85
- let bestDist = Number.POSITIVE_INFINITY;
86
- for (const m of installed) {
87
- if (familyOf(m) !== fam) continue;
88
- const dist = Math.abs(sizeOf(m) - sizeOf(realModel)) + 0.5;
89
- if (dist < bestDist) {
90
- bestDist = dist;
91
- best = m;
92
- }
93
- }
94
- if (best) {
95
- return {
96
- model: best,
97
- substituted: true,
98
- reason: `"${realModel}" is not installed; using ${best} (same family)`,
99
- };
100
- }
101
- return { model: null, substituted: true, reason: `"${realModel}" is not installed` };
102
- }
103
-
104
- async function resolveModel(host, realModel) {
105
- const installed = await getInstalledModels(host);
106
- return chooseModel(installed, realModel);
107
- }
108
-
109
- // Best local bin dir for a globally-callable command on this platform.
110
- function localBinDir() {
111
- if (process.env.XDG_BIN_HOME) return process.env.XDG_BIN_HOME;
112
- if (process.env.HOME) return join(homedir(), '.local', 'bin');
113
- return '/usr/local/bin';
114
- }
115
-
116
- function isOnPath(dir) {
117
- const sep = platform() === 'win32' ? ';' : ':';
118
- let dirResolved;
119
- try {
120
- dirResolved = existsSync(dir) ? realpathSync(dir) : resolve(dir);
121
- } catch {
122
- dirResolved = resolve(dir);
123
- }
124
- return String(process.env.PATH || '')
125
- .split(sep)
126
- .some((entry) => {
127
- entry = entry.trim();
128
- if (!entry) return false;
129
- try {
130
- const e = existsSync(entry) ? realpathSync(entry) : resolve(entry);
131
- return e === dirResolved;
132
- } catch {
133
- return false;
134
- }
135
- });
136
- }
137
-
138
- // Prefer a bin dir that is already on PATH (and writable) so the installed
139
- // command is callable immediately; fall back to the local bin dir.
140
- function chooseBinDir() {
141
- const local = localBinDir();
142
- if (isOnPath(local)) return local;
143
- for (const candidate of ['/usr/local/bin', '/usr/bin']) {
144
- try {
145
- mkdirSync(candidate, { recursive: true, mode: 0o755 });
146
- const probe = join(candidate, `.vierrataleai-write-test-${process.pid}`);
147
- writeFileSync(probe, '');
148
- rmSync(probe, { force: true });
149
- return candidate;
150
- } catch {}
151
- }
152
- return local;
153
- }
154
-
155
- // Create (or refresh) a launcher link for `name` pointing at the node bin.
156
- function writeLauncher(dir, name, target) {
157
- const launcherPath = join(dir, name);
158
- if (platform() === 'win32') {
159
- if (!existsSync(launcherPath)) {
160
- writeFileSync(launcherPath, `@echo off\r\nnode "${target}" %*\r\n`);
161
- }
162
- return launcherPath;
163
- }
164
- // Prefer a real symlink; fall back to a small shim that calls the node bin.
165
- let live = false;
166
- try {
167
- live = lstatSync(launcherPath).isSymbolicLink() && readlinkSync(launcherPath) === target;
168
- } catch {}
169
- if (!live) {
170
- rmSync(launcherPath, { force: true });
171
- try {
172
- symlinkSync(target, launcherPath);
173
- chmodSync(launcherPath, 0o755);
174
- } catch {
175
- writeFileSync(launcherPath, `#!/bin/sh\nnode "${target}" "$@"\n`);
176
- chmodSync(launcherPath, 0o755);
177
- }
178
- }
179
- return launcherPath;
180
- }
181
-
182
- function installAppCommand() {
183
- try {
184
- const dir = chooseBinDir();
185
- mkdirSync(dir, { recursive: true, mode: 0o755 });
186
- const defaultTarget = join(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'vierrataleai.js');
187
- const target = process.argv[1] && existsSync(process.argv[1]) ? process.argv[1] : defaultTarget;
188
- // Both spellings run the assistant.
189
- const launcherPath = writeLauncher(dir, 'vierrataleai', target);
190
- writeLauncher(dir, 'vierratale', target);
191
- return launcherPath;
192
- } catch {
193
- return null;
194
- }
195
- }
196
-
197
- export const Installer = {
198
- async pullModel(host, model) {
199
- return pullModel(host, model);
200
- },
201
-
202
- async getInstalledModels(host) {
203
- return getInstalledModels(host);
204
- },
205
-
206
- async resolveModel(host, realModel) {
207
- return resolveModel(host, realModel);
208
- },
209
-
210
- async firstInstalled(host) {
211
- const installed = await getInstalledModels(host);
212
- return installed[0] || null;
213
- },
214
-
215
- chooseModel,
216
-
217
- installAppCommand,
218
- isOnPath,
219
-
220
- async ensure() {
221
- const host = Config.get('engineHost');
222
-
223
- if (!isEngineInstalled()) {
224
- await installEngine();
225
- }
226
-
227
- const running = await isEngineRunning(host);
228
- if (!running) {
229
- try {
230
- // No systemd available here, so launch the daemon directly, detached,
231
- // and poll until it responds instead of relying on a service manager.
232
- const child = exec('ollama serve', {
233
- stdio: 'ignore',
234
- detached: true,
235
- });
236
- if (child.unref) child.unref();
237
- for (let i = 0; i < 30; i++) {
238
- await new Promise((r) => setTimeout(r, 1000));
239
- if (await isEngineRunning(host)) break;
240
- }
241
- } catch {}
242
- }
243
-
244
- const realModel = Catalog.getRealModel(Config.get('model'));
245
- const installed = await getInstalledModels(host);
246
- const resolved = chooseModel(installed, realModel);
247
- const needsPull = resolved.model == null;
248
-
249
- if (needsPull) {
250
- await pullModel(host, realModel);
251
- }
252
-
253
- return true;
254
- },
255
-
256
- async isReady() {
257
- const host = Config.get('engineHost');
258
- return isEngineRunning(host);
259
- },
260
-
261
- // Full install: engine + model + a global "vierrataleai" command.
262
- async install({ includeCommand = true } = {}) {
263
- const host = Config.get('engineHost');
264
-
265
- if (!isEngineInstalled()) {
266
- await installEngine();
267
- }
268
-
269
- const running = await isEngineRunning(host);
270
- if (!running) {
271
- try {
272
- const child = exec('ollama serve', { stdio: 'ignore', detached: true });
273
- if (child.unref) child.unref();
274
- for (let i = 0; i < 30; i++) {
275
- await new Promise((r) => setTimeout(r, 1000));
276
- if (await isEngineRunning(host)) break;
277
- }
278
- } catch {}
279
- }
280
-
281
- const realModel = Catalog.getRealModel(Config.get('model'));
282
- const installed = await getInstalledModels(host);
283
- const resolved = chooseModel(installed, realModel);
284
- if (resolved.model == null) {
285
- await pullModel(host, realModel);
286
- }
287
-
288
- let command = null;
289
- let commandAlias = null;
290
- let commandOnPath = false;
291
- if (includeCommand) {
292
- command = installAppCommand();
293
- if (command) {
294
- commandAlias = join(dirname(command), 'vierratale');
295
- commandOnPath = isOnPath(dirname(command));
296
- }
297
- }
298
- return { installed: await isEngineInstalled(), running: await isEngineRunning(host), command, commandAlias, commandOnPath };
299
- },
300
- };