vigthoria-cli 1.10.1 → 1.10.36

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.
@@ -10,6 +10,7 @@ export interface VigthoriaCLIConfig {
10
10
  refreshToken: string | null;
11
11
  userId: string | null;
12
12
  email: string | null;
13
+ v3ServiceKey: string | null;
13
14
  subscription: {
14
15
  plan: string | null;
15
16
  status: string | null;
@@ -27,6 +28,14 @@ export interface VigthoriaCLIConfig {
27
28
  rootPath: string | null;
28
29
  ignorePatterns: string[];
29
30
  };
31
+ persona: 'default' | 'wiener_grant';
32
+ hubModelPrefs?: {
33
+ enabledCloudModels: string[];
34
+ defaultCloudModel?: string;
35
+ defaultLocalModel?: string;
36
+ balance?: number;
37
+ fetchedAt?: string;
38
+ };
30
39
  }
31
40
  export interface AvailableModelDescriptor {
32
41
  id: string;
@@ -68,7 +77,9 @@ export declare class Config {
68
77
  expiresAt?: string;
69
78
  }): void;
70
79
  getAvailableModels(): AvailableModelDescriptor[];
80
+ refreshHubModelPreferences(): Promise<void>;
71
81
  isCloudModel(modelId: string): boolean;
82
+ filterModelsByHubPreferences(models: AvailableModelDescriptor[], enabledCloudModels?: string[]): AvailableModelDescriptor[];
72
83
  isComplexTask(prompt: string): boolean;
73
84
  shouldUseCloudForHeavyTask(prompt: string): boolean;
74
85
  }
@@ -12,19 +12,21 @@ const defaultConfig = {
12
12
  refreshToken: null,
13
13
  userId: null,
14
14
  email: null,
15
+ v3ServiceKey: null,
15
16
  subscription: {
16
17
  plan: null,
17
18
  status: null,
18
19
  expiresAt: null,
19
20
  },
20
21
  preferences: {
21
- defaultModel: 'agent',
22
+ defaultModel: 'code',
22
23
  theme: 'dark',
23
24
  autoApplyFixes: false,
24
25
  showDiffs: true,
25
26
  contextLines: 3,
26
27
  maxTokens: 4096,
27
28
  },
29
+ persona: 'default',
28
30
  project: {
29
31
  rootPath: null,
30
32
  ignorePatterns: [
@@ -63,6 +65,7 @@ export class Config {
63
65
  refreshToken: { type: ['string', 'null'] },
64
66
  userId: { type: ['string', 'null'] },
65
67
  email: { type: ['string', 'null'] },
68
+ v3ServiceKey: { type: ['string', 'null'] },
66
69
  subscription: {
67
70
  type: 'object',
68
71
  properties: {
@@ -82,6 +85,7 @@ export class Config {
82
85
  maxTokens: { type: 'number' },
83
86
  },
84
87
  },
88
+ persona: { type: 'string', enum: ['default', 'wiener_grant'] },
85
89
  project: {
86
90
  type: 'object',
87
91
  properties: {
@@ -182,30 +186,64 @@ export class Config {
182
186
  const sub = this.store.get('subscription');
183
187
  const plan = (sub.plan || '').toLowerCase();
184
188
  // ═══════════════════════════════════════════════════════════════
185
- // VIGTHORIA LOCAL - Self-hosted operational models
189
+ // Vigthoria server infrastructure operational models
186
190
  // ═══════════════════════════════════════════════════════════════
187
191
  const models = [
188
- { id: 'agent', name: 'Vigthoria V3 Code Agent', description: 'Blackwell autonomous agent workflow', tier: 'local', backendModel: 'vigthoria-v3-code-35b' },
189
- { id: 'code', name: 'Vigthoria v3 Code 35B', description: 'Native 35B coding model on Blackwell', tier: 'local', backendModel: 'vigthoria-v3-code-35b' },
192
+ { id: 'code', name: 'Vigthoria v3 Code 35B', description: 'Native 35B coding model on Blackwell (V3 pipeline)', tier: 'local', backendModel: 'vigthoria-v3-code-35b' },
190
193
  { id: 'code-35b', name: 'Vigthoria v3 Code 35B', description: 'Same flagship model as code', tier: 'local', backendModel: 'vigthoria-v3-code-35b' },
191
194
  { id: 'code-9b', name: 'Vigthoria v3 Code 9B', description: 'Efficient coding specialist for quick tasks', tier: 'local', backendModel: 'vigthoria-v3-code-9b' },
192
195
  { id: 'balanced', name: 'Vigthoria Balanced 4B', description: 'Balanced general-purpose local model', tier: 'local', backendModel: 'vigthoria-v3-balanced-4b' },
193
196
  { id: 'balanced-4b', name: 'Vigthoria Balanced 4B', description: 'Efficient 4B general-purpose model (Qwen3.5-4B based)', tier: 'local', backendModel: 'vigthoria-v3-balanced-4b' },
194
197
  ];
195
- // ═══════════════════════════════════════════════════════════════
196
- // VIGTHORIA CLOUD - Premium cloud models (Pro subscription)
197
- // For complex multi-file tasks, large refactoring, architecture
198
- // ═══════════════════════════════════════════════════════════════
199
198
  if (this.hasCloudAccess()) {
200
- models.push({ id: 'cloud', name: 'Vigthoria Cloud Pro', description: 'High-capability cloud model for complex tasks', tier: 'cloud', backendModel: 'vigthoria-cloud-pro' }, { id: 'cloud-reason', name: 'Vigthoria Cloud K2', description: 'Reasoning-focused cloud model', tier: 'cloud', backendModel: 'vigthoria-cloud-k2' }, { id: 'ultra', name: 'Vigthoria Cloud Ultra', description: 'Maximum capability cloud routing', tier: 'cloud', backendModel: 'vigthoria-cloud-ultra' });
199
+ models.push({ id: 'cloud-fast', name: 'Vigthoria Cloud Fast', description: 'Fast cloud responses for lighter work', tier: 'cloud', backendModel: 'vigthoria-cloud-fast' }, { id: 'cloud-balanced', name: 'Vigthoria Cloud Balanced', description: 'Default quality/cost balance for chat and coding', tier: 'cloud', backendModel: 'vigthoria-cloud-balanced' }, { id: 'cloud-code', name: 'Vigthoria Cloud Code', description: 'Economical cloud coding and completion', tier: 'cloud', backendModel: 'vigthoria-cloud-code' }, { id: 'cloud-power', name: 'Vigthoria Cloud Power', description: 'Premium general intelligence for demanding work', tier: 'cloud', backendModel: 'vigthoria-cloud-power' }, { id: 'cloud-maximum', name: 'Vigthoria Cloud Maximum', description: 'Maximum power for complex architecture and reviews', tier: 'cloud', backendModel: 'vigthoria-cloud-maximum' },
200
+ // Legacy aliases
201
+ { id: 'cloud', name: 'Vigthoria Cloud Balanced', description: 'Legacy alias for cloud-balanced', tier: 'cloud', backendModel: 'vigthoria-cloud-balanced' }, { id: 'cloud-reason', name: 'Vigthoria Cloud Balanced', description: 'Legacy alias for cloud-balanced', tier: 'cloud', backendModel: 'vigthoria-cloud-balanced' }, { id: 'ultra', name: 'Vigthoria Cloud Maximum', description: 'Legacy alias for cloud-maximum', tier: 'cloud', backendModel: 'vigthoria-cloud-maximum' });
202
+ }
203
+ const hubPrefs = this.store.get('hubModelPrefs');
204
+ if (hubPrefs?.enabledCloudModels?.length) {
205
+ return this.filterModelsByHubPreferences(models, hubPrefs.enabledCloudModels);
201
206
  }
202
207
  return models;
203
208
  }
209
+ async refreshHubModelPreferences() {
210
+ const token = this.store.get('authToken');
211
+ const apiUrl = String(this.store.get('apiUrl') || '').replace(/\/$/, '');
212
+ if (!token || !apiUrl)
213
+ return;
214
+ try {
215
+ const response = await fetch(`${apiUrl}/api/user/credits/model-preferences`, {
216
+ headers: { Authorization: `Bearer ${token}` },
217
+ signal: AbortSignal.timeout(8000),
218
+ });
219
+ if (!response.ok)
220
+ return;
221
+ const prefs = await response.json();
222
+ if (prefs?.success === false)
223
+ return;
224
+ this.store.set('hubModelPrefs', {
225
+ enabledCloudModels: Array.isArray(prefs.enabledCloudModels) ? prefs.enabledCloudModels : [],
226
+ defaultCloudModel: typeof prefs.defaultCloudModel === 'string' ? prefs.defaultCloudModel : undefined,
227
+ defaultLocalModel: typeof prefs.defaultLocalModel === 'string' ? prefs.defaultLocalModel : undefined,
228
+ balance: typeof prefs.balance === 'number' ? prefs.balance : undefined,
229
+ fetchedAt: new Date().toISOString(),
230
+ });
231
+ }
232
+ catch {
233
+ // Non-fatal: CLI can still run with default model catalog.
234
+ }
235
+ }
204
236
  // Check if a model is a "Cloud" tier model
205
237
  isCloudModel(modelId) {
206
- const cloudModels = ['cloud', 'cloud-reason', 'ultra'];
238
+ const cloudModels = ['cloud-fast', 'cloud-balanced', 'cloud-code', 'cloud-power', 'cloud-maximum', 'cloud', 'cloud-reason', 'ultra'];
207
239
  return cloudModels.includes(modelId) || modelId.includes('cloud');
208
240
  }
241
+ filterModelsByHubPreferences(models, enabledCloudModels) {
242
+ if (!enabledCloudModels || !enabledCloudModels.length)
243
+ return models;
244
+ const enabled = new Set(enabledCloudModels);
245
+ return models.filter((model) => model.tier !== 'cloud' || enabled.has(model.backendModel));
246
+ }
209
247
  // Check if task is complex enough to suggest Cloud upgrade
210
248
  isComplexTask(prompt) {
211
249
  const complexIndicators = [
@@ -221,6 +259,9 @@ export class Config {
221
259
  return complexIndicators.some(pattern => pattern.test(prompt));
222
260
  }
223
261
  shouldUseCloudForHeavyTask(prompt) {
224
- return this.hasOperatorAccess() && this.hasCloudAccess() && this.isComplexTask(prompt);
262
+ // DISABLED: Cloud routing is now EXPLICIT ONLY (--model cloud or /cloud command)
263
+ // Do NOT auto-route based on keywords. User must opt-in.
264
+ // If user wants cloud: they'll use --model cloud or request it explicitly.
265
+ return false;
225
266
  }
226
267
  }
@@ -0,0 +1,4 @@
1
+ export type PersonaMode = 'default' | 'wiener_grant';
2
+ export declare function normalizePersonaMode(value: unknown): PersonaMode | null;
3
+ export declare function isToneDownPrompt(prompt: string): boolean;
4
+ export declare function buildPersonaOverlay(mode: PersonaMode, prompt?: string): string;
@@ -0,0 +1,34 @@
1
+ const WIENER_GRANT_PROMPT = [
2
+ 'Optional persona overlay: Wiener Grantler mode.',
3
+ 'You remain Vigthoria: technically precise, helpful, safe, and comprehensive.',
4
+ 'Persona: theatrical Viennese grumpiness with dry humor, mild sarcasm, impatient charm, and light Austrian dialect flavor.',
5
+ 'Rules:',
6
+ '- Never reduce technical quality, completeness, or safety.',
7
+ '- Never use slurs, hate, racist or sexist stereotypes, protected-class insults, or demeaning nationality/ethnicity jokes.',
8
+ '- Do not seriously demean the user. Keep the bite playful and aimed at messy code, vague prompts, broken configs, or tooling chaos.',
9
+ '- Use dialect flavor sparingly enough that instructions, commands, and code remain fully readable.',
10
+ '- Always provide the actual fix, commands, code, or next steps.',
11
+ ].join('\n');
12
+ const TONE_DOWN_PROMPT = [
13
+ 'Tone-down rule active: this request appears safety-sensitive, destructive, auth/billing-related, security-related, legal/medical/financial, or production-critical.',
14
+ 'Use only a very light flavor line if appropriate, then prioritize plain clarity, caution, and exact steps.',
15
+ ].join('\n');
16
+ export function normalizePersonaMode(value) {
17
+ const normalized = String(value || '').trim().toLowerCase().replace(/_/g, '-');
18
+ if (!normalized || normalized === 'default' || normalized === 'off' || normalized === 'none')
19
+ return 'default';
20
+ if (normalized === 'wiener-grant' || normalized === 'wiener-grantler' || normalized === 'grant' || normalized === 'grantler')
21
+ return 'wiener_grant';
22
+ return null;
23
+ }
24
+ export function isToneDownPrompt(prompt) {
25
+ return /\b(delete|drop|destroy|wipe|rm\s+-rf|format|credential|secret|token|auth|login|billing|payment|wallet|security|vulnerability|exploit|incident|production|prod|deploy|legal|medical|financial|bank|tax)\b/i.test(prompt);
26
+ }
27
+ export function buildPersonaOverlay(mode, prompt = '') {
28
+ if (mode !== 'wiener_grant')
29
+ return '';
30
+ const parts = [WIENER_GRANT_PROMPT];
31
+ if (isToneDownPrompt(prompt))
32
+ parts.push(TONE_DOWN_PROMPT);
33
+ return parts.join('\n\n');
34
+ }
@@ -99,6 +99,9 @@ export declare class AgenticTools {
99
99
  constructor(logger: Logger, cwd: string, permissionCallback: (action: string, options?: {
100
100
  batchApproval?: boolean;
101
101
  }) => Promise<boolean | 'batch' | 'persist'>, autoApprove?: boolean);
102
+ /** Rebind tool execution to a different local workspace root (e.g. prompt path override). */
103
+ setWorkspaceRoot(cwd: string): void;
104
+ getWorkspaceRoot(): string;
102
105
  private getErrorMessage;
103
106
  private assertToolCall;
104
107
  /**
@@ -183,6 +186,8 @@ export declare class AgenticTools {
183
186
  */
184
187
  private formatPermissionRequest;
185
188
  private sleep;
189
+ private bridgeToolsEnabled;
190
+ private tryBridgeReadFile;
186
191
  /**
187
192
  * Read file with enhanced error handling and suggestions
188
193
  */
@@ -449,10 +449,20 @@ export class AgenticTools {
449
449
  throw new Error('AgenticTools initialization failed: autoApprove must be a boolean.');
450
450
  }
451
451
  this.logger = logger;
452
- this.cwd = cwd;
452
+ this.cwd = path.resolve(cwd);
453
453
  this.permissionCallback = permissionCallback;
454
454
  this.autoApprove = autoApprove;
455
455
  }
456
+ /** Rebind tool execution to a different local workspace root (e.g. prompt path override). */
457
+ setWorkspaceRoot(cwd) {
458
+ if (typeof cwd !== 'string' || cwd.trim().length === 0) {
459
+ throw new Error('AgenticTools.setWorkspaceRoot failed: cwd must be a non-empty string.');
460
+ }
461
+ this.cwd = path.resolve(cwd);
462
+ }
463
+ getWorkspaceRoot() {
464
+ return this.cwd;
465
+ }
456
466
  getErrorMessage(error) {
457
467
  if (error instanceof Error) {
458
468
  return error.message;
@@ -1234,10 +1244,52 @@ export class AgenticTools {
1234
1244
  return new Promise(resolve => setTimeout(resolve, ms));
1235
1245
  }
1236
1246
  // Tool implementations with enhanced error handling and undo support
1247
+ bridgeToolsEnabled() {
1248
+ const flag = String(process.env.VIGTHORIA_BRIDGE_TOOLS || '').trim().toLowerCase();
1249
+ return flag === '1' || flag === 'true' || flag === 'yes' || flag === 'on';
1250
+ }
1251
+ tryBridgeReadFile(args) {
1252
+ if (!this.bridgeToolsEnabled()) {
1253
+ return null;
1254
+ }
1255
+ const bridgeBase = String(process.env.VIGTHORIA_BRIDGE_HTTP
1256
+ || process.env.VIGTHORIA_DESKTOP_BRIDGE_URL
1257
+ || 'http://127.0.0.1:49160').replace(/\/$/, '');
1258
+ const targetPath = args.path || '';
1259
+ if (!targetPath) {
1260
+ return null;
1261
+ }
1262
+ try {
1263
+ const response = execSync(`curl -sS -m 15 -X POST "${bridgeBase}/tools/read_file" -H "Content-Type: application/json" -d ${JSON.stringify(JSON.stringify({
1264
+ path: targetPath,
1265
+ start_line: args.start_line ? Number(args.start_line) : undefined,
1266
+ end_line: args.end_line ? Number(args.end_line) : undefined,
1267
+ }))}`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
1268
+ const payload = JSON.parse(response);
1269
+ if (payload?.success === true) {
1270
+ return {
1271
+ success: true,
1272
+ output: String(payload.output || payload.content || ''),
1273
+ metadata: { source: 'desktop-bridge', path: targetPath },
1274
+ };
1275
+ }
1276
+ if (payload?.error) {
1277
+ this.logger.debug(`Desktop bridge read failed for ${targetPath}: ${payload.error}`);
1278
+ }
1279
+ }
1280
+ catch (error) {
1281
+ this.logger.debug(`Desktop bridge read unavailable for ${targetPath}: ${this.formatExternalToolError('read_file', 'bridge delegation', error)}`);
1282
+ }
1283
+ return null;
1284
+ }
1237
1285
  /**
1238
1286
  * Read file with enhanced error handling and suggestions
1239
1287
  */
1240
1288
  readFile(args) {
1289
+ const bridgeRead = this.tryBridgeReadFile(args);
1290
+ if (bridgeRead) {
1291
+ return bridgeRead;
1292
+ }
1241
1293
  const filePath = this.resolvePath(args.path);
1242
1294
  if (!fs.existsSync(filePath)) {
1243
1295
  // Try to find similar files
@@ -37,6 +37,17 @@ const IGNORE_PATTERNS = [
37
37
  '**/.vigthoria/**',
38
38
  '**/coverage/**',
39
39
  '**/*.pyc',
40
+ // CRITICAL: Windows AppData & system directories (prevents EPERM errors)
41
+ '**/Anwendungsdaten/**', // German AppData
42
+ '**/AppData/**', // English AppData
43
+ '**/Local Settings/**', // Legacy Windows
44
+ '**/Application Data/**', // Legacy Windows
45
+ '**/$RECYCLE.BIN/**', // Windows Recycle Bin
46
+ '**/System Volume Information/**',
47
+ '**/Temp/**',
48
+ '**/tmp/**',
49
+ '**/OneDrive/**', // OneDrive cache
50
+ '**/MicrosoftEdgeBackups/**',
40
51
  ];
41
52
  const BINARY_EXTENSIONS = new Set([
42
53
  '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.webp', '.bmp',
@@ -103,24 +114,54 @@ export class WorkspaceWatcher {
103
114
  start() {
104
115
  if (this.watcher)
105
116
  return;
106
- this.watcher = chokidar.watch(this.workspaceRoot, {
107
- ignored: IGNORE_PATTERNS,
108
- persistent: true,
109
- ignoreInitial: true,
110
- awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 },
111
- });
112
- this.watcher
113
- .on('ready', () => {
114
- this._ready = true;
115
- logger.debug('Workspace watcher ready');
116
- })
117
- .on('add', (filePath) => this._handleChange(filePath, 'write'))
118
- .on('change', (filePath) => this._handleChange(filePath, 'write'))
119
- .on('unlink', (filePath) => this._handleChange(filePath, 'delete'));
117
+ try {
118
+ this.watcher = chokidar.watch(this.workspaceRoot, {
119
+ ignored: IGNORE_PATTERNS,
120
+ persistent: false,
121
+ ignoreInitial: true,
122
+ awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 },
123
+ // Windows-specific settings to prevent EPERM on system directories
124
+ usePolling: false,
125
+ interval: 100,
126
+ binaryInterval: 300,
127
+ depth: 50, // Limit traversal depth
128
+ });
129
+ this.watcher
130
+ .on('ready', () => {
131
+ this._ready = true;
132
+ logger.debug('Workspace watcher ready');
133
+ })
134
+ .on('add', (filePath) => this._handleChange(filePath, 'write'))
135
+ .on('change', (filePath) => this._handleChange(filePath, 'write'))
136
+ .on('unlink', (filePath) => this._handleChange(filePath, 'delete'))
137
+ // CRITICAL: Handle permission errors gracefully (Windows AppData EPERM)
138
+ .on('error', (error) => {
139
+ if (error?.code === 'EPERM' || error?.errno === -4048) {
140
+ logger.warn(`Workspace watcher permission error (ignored): ${error.message}`);
141
+ // Continue without watcher; don't crash
142
+ }
143
+ else {
144
+ logger.error(`Workspace watcher error: ${error?.message || error}`);
145
+ }
146
+ });
147
+ }
148
+ catch (err) {
149
+ // Handle initialization errors (e.g., EPERM during chokidar.watch setup)
150
+ if (err?.code === 'EPERM' || err?.errno === -4048 || err?.message?.includes('EPERM')) {
151
+ logger.warn(`Failed to initialize workspace watcher (EPERM): ${err.message}`);
152
+ logger.info('Continuing without file watcher; local file changes will not sync in real-time');
153
+ this.watcher = null;
154
+ }
155
+ else {
156
+ logger.error(`Failed to initialize workspace watcher: ${err.message}`);
157
+ throw err;
158
+ }
159
+ }
120
160
  }
121
161
  stop() {
122
162
  if (this.watcher) {
123
- this.watcher.close();
163
+ // close() returns a Promise; fire-and-forget so handles are released
164
+ this.watcher.close().catch(() => { });
124
165
  this.watcher = null;
125
166
  this._ready = false;
126
167
  }
package/install.ps1 CHANGED
@@ -5,7 +5,7 @@
5
5
  $ErrorActionPreference = "Stop"
6
6
 
7
7
  # Configuration
8
- $CLI_VERSION = "1.9.22"
8
+ $CLI_VERSION = "1.10.18"
9
9
  $INSTALL_DIR = "$env:USERPROFILE\.vigthoria"
10
10
  $NPM_PACKAGE = "vigthoria-cli"
11
11
  $GIT_PACKAGE_URL = "git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
package/install.sh CHANGED
@@ -26,7 +26,7 @@ else
26
26
  fi
27
27
 
28
28
  # Configuration
29
- CLI_VERSION="1.9.22"
29
+ CLI_VERSION="1.10.18"
30
30
  INSTALL_DIR="$HOME/.vigthoria"
31
31
  REPO_URL="https://market.vigthoria.io/vigthoria/vigthoria-cli"
32
32
  GIT_PACKAGE_URL="git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.10.1",
3
+ "version": "1.10.36",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -65,7 +65,9 @@
65
65
  "test:model:governance": "npm run build && node scripts/test-model-governance-no-agent.js",
66
66
  "validate:no-go": "bash scripts/release/validate-no-go-gates.sh",
67
67
  "test:legion:billing:e2e": "npm run build && node scripts/test-legion-godmode-billing-e2e.js",
68
- "test:windows:v3-sync": "npm run build && node scripts/test-windows-v3-sync-recovery.js"
68
+ "test:windows:v3-sync": "npm run build && node scripts/test-windows-v3-sync-recovery.js",
69
+ "test:persona": "npm run build && node scripts/test-persona-mode.mjs",
70
+ "test:error-wording": "npm run build && node scripts/test-error-wording.mjs"
69
71
  },
70
72
  "keywords": [
71
73
  "ai",