vigthoria-cli 1.6.30 → 1.6.31

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.
@@ -345,6 +345,7 @@ export declare class APIClient {
345
345
  private getSelfHostedFallbackModelId;
346
346
  chatStream(messages: ChatMessage[], model: string): AsyncGenerator<StreamChunk>;
347
347
  chatWithCallback(messages: ChatMessage[], model: string, onChunk: (chunk: string) => void, onDone: () => void, onError: (error: Error) => void): Promise<void>;
348
+ private chatComplete;
348
349
  generateCode(prompt: string, language: string, model: string): Promise<string>;
349
350
  /**
350
351
  * Ensure code has balanced curly braces by appending missing closing braces.
package/dist/utils/api.js CHANGED
@@ -352,20 +352,35 @@ class APIClient {
352
352
  if (!token) {
353
353
  return { valid: false, error: 'No auth token configured. Run: vigthoria login' };
354
354
  }
355
+ // Verify auth against the Model Router (api.vigthoria.io) which is
356
+ // the backend all AI commands actually use. Falls back to the Coder
357
+ // profile endpoint when the Model Router is unreachable so that
358
+ // offline/degraded scenarios don't block the user.
355
359
  try {
356
- await this.client.get('/api/user/profile', { timeout: 10000 });
360
+ await this.modelRouterClient.get('/v1/models', { timeout: 10000 });
357
361
  return { valid: true };
358
362
  }
359
- catch (error) {
360
- if (error instanceof CLIError && error.category === 'auth') {
363
+ catch (mrError) {
364
+ const mrAxErr = mrError;
365
+ if (mrAxErr.response?.status === 401 || mrAxErr.response?.status === 403) {
361
366
  return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
362
367
  }
363
- const axErr = error;
364
- if (axErr.response?.status === 401 || axErr.response?.status === 403) {
365
- return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
368
+ // Model Router unreachable — try Coder profile as fallback
369
+ try {
370
+ await this.client.get('/api/user/profile', { timeout: 10000 });
371
+ return { valid: true };
372
+ }
373
+ catch (error) {
374
+ if (error instanceof CLIError && error.category === 'auth') {
375
+ return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
376
+ }
377
+ const axErr = error;
378
+ if (axErr.response?.status === 401 || axErr.response?.status === 403) {
379
+ return { valid: false, error: 'Auth token expired or invalid. Run: vigthoria login' };
380
+ }
381
+ // Both unreachable — don't assume token is bad
382
+ return { valid: true };
366
383
  }
367
- // Network/timeout errors — don't assume token is bad
368
- return { valid: true };
369
384
  }
370
385
  }
371
386
  getV3AgentBaseUrls(preferLocal = false) {
@@ -3354,7 +3369,26 @@ document.addEventListener('DOMContentLoaded', () => {
3354
3369
  });
3355
3370
  });
3356
3371
  }
3357
- // Code operations - Using Vigthoria Centralized API
3372
+ // ─── Chat completion helper ────────────────────────────────────────
3373
+ // Routes all AI file commands through the Model Router
3374
+ // (/v1/chat/completions on api.vigthoria.io) which is the only
3375
+ // backend that reliably accepts our auth token.
3376
+ async chatComplete(systemPrompt, userPrompt, model, maxTokens) {
3377
+ const resolvedModel = model ? this.resolvePermittedModelId(model) : 'vigthoria-v3-code-30b';
3378
+ const response = await this.modelRouterClient.post('/v1/chat/completions', {
3379
+ model: resolvedModel,
3380
+ messages: [
3381
+ { role: 'system', content: systemPrompt },
3382
+ { role: 'user', content: userPrompt },
3383
+ ],
3384
+ max_tokens: maxTokens || this.config.get('preferences').maxTokens || 4096,
3385
+ temperature: 0.3,
3386
+ stream: false,
3387
+ });
3388
+ const content = response.data.choices?.[0]?.message?.content || response.data.choices?.[0]?.text || '';
3389
+ return typeof content === 'string' ? content : '';
3390
+ }
3391
+ // Code operations - Using Vigthoria Centralized API (via Model Router)
3358
3392
  async generateCode(prompt, language, model) {
3359
3393
  const isNonHtmlLang = !['html', 'css'].includes(language.toLowerCase());
3360
3394
  const wordCount = prompt.trim().split(/\s+/).length;
@@ -3390,24 +3424,18 @@ document.addEventListener('DOMContentLoaded', () => {
3390
3424
  lines.push('', prompt);
3391
3425
  return lines.join('\n');
3392
3426
  };
3393
- // First attempt
3394
- let response = await this.client.post('/api/ai/generate', {
3395
- prompt: buildScopedPrompt(false),
3396
- language,
3397
- model: this.resolvePermittedModelId(model),
3398
- });
3399
- let code = response.data.code || '';
3427
+ // First attempt — route through Model Router chat completions
3428
+ const systemPrompt = `You are a code generator. Output ONLY raw ${language} code. No markdown fences, no explanations, no commentary. Just the code.`;
3429
+ let code = await this.chatComplete(systemPrompt, buildScopedPrompt(false), model);
3430
+ // Strip markdown fences if model included them
3431
+ code = code.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim();
3400
3432
  // Client-side validation: reject DOM-polluted or over-engineered responses for non-HTML languages
3401
3433
  const needsRetry = isNonHtmlLang && (this.codeContainsDomPollution(code) ||
3402
3434
  this.codeIsOverEngineered(code, prompt));
3403
3435
  if (needsRetry) {
3404
- // Retry once with stronger constraint
3405
- response = await this.client.post('/api/ai/generate', {
3406
- prompt: buildScopedPrompt(true),
3407
- language,
3408
- model: this.resolvePermittedModelId(model),
3409
- });
3410
- code = response.data.code || '';
3436
+ // Retry once with stronger constraint — via Model Router
3437
+ code = await this.chatComplete(systemPrompt, buildScopedPrompt(true), model);
3438
+ code = code.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim();
3411
3439
  // If still polluted, strip DOM code client-side
3412
3440
  if (this.codeContainsDomPollution(code)) {
3413
3441
  code = this.stripDomPollution(code, language);
@@ -3525,43 +3553,55 @@ document.addEventListener('DOMContentLoaded', () => {
3525
3553
  }
3526
3554
  // Senior Developer Mode - Planning + Generation + Quality Check
3527
3555
  async generateProject(prompt, projectType, model) {
3528
- const response = await this.client.post('/api/ai/generate-project', {
3529
- prompt,
3530
- projectType,
3531
- model: this.resolvePermittedModelId(model),
3532
- }, {
3533
- timeout: 300000, // 5 minutes for complex generation
3534
- });
3535
- return {
3536
- code: response.data.code,
3537
- plan: response.data.plan,
3538
- quality: response.data.quality,
3539
- };
3556
+ const sysPrompt = [
3557
+ `You are a senior developer. Generate a complete ${projectType} project.`,
3558
+ 'Return a JSON object with these fields:',
3559
+ ' "code": the full source code as a string,',
3560
+ ' "plan": an object describing the architecture,',
3561
+ ' "quality": { "lineCount": number, "score": number (0-100) }',
3562
+ 'Return ONLY the JSON object, no markdown fences.',
3563
+ ].join('\n');
3564
+ const raw = await this.chatComplete(sysPrompt, prompt, model, 8192);
3565
+ try {
3566
+ const cleaned = raw.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim();
3567
+ const parsed = JSON.parse(cleaned);
3568
+ return {
3569
+ code: parsed.code || raw,
3570
+ plan: parsed.plan,
3571
+ quality: parsed.quality,
3572
+ };
3573
+ }
3574
+ catch {
3575
+ return { code: raw.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim() };
3576
+ }
3540
3577
  }
3541
3578
  async explainCode(code, language) {
3542
- const response = await this.client.post('/api/ai/explain', {
3543
- code,
3544
- language,
3545
- });
3546
- return response.data.explanation;
3579
+ const sysPrompt = `You are a code explainer. Explain the following ${language} code clearly and concisely. Focus on what it does, how it works, and any notable patterns or potential issues.`;
3580
+ return this.chatComplete(sysPrompt, code);
3547
3581
  }
3548
3582
  async reviewCode(code, language) {
3549
- const response = await this.client.post('/api/ai/review', {
3550
- code,
3551
- language,
3552
- instructions: [
3553
- 'Return concrete, line-specific issues with severity.',
3554
- 'Every issue MUST reference a line number.',
3555
- 'If the score is below 50, you MUST list at least 2 specific issues.',
3556
- 'Prioritize REAL BUGS over style issues:',
3557
- '- Wrong arithmetic operators (+ instead of -, * instead of /, etc.)',
3558
- '- Logic errors (function named "add" using subtraction, wrong comparisons)',
3559
- '- Off-by-one errors, incorrect return values',
3560
- '- Type mismatches, null/undefined access',
3561
- 'Only report style issues (console.log, naming) AFTER listing all real bugs.',
3562
- ].join(' '),
3563
- });
3564
- const raw = response.data ?? {};
3583
+ const sysPrompt = [
3584
+ `You are a senior code reviewer for ${language}. Analyze the code and return a JSON object with:`,
3585
+ ' "score": number 0-100,',
3586
+ ' "issues": [{ "type": string, "line": number, "message": string, "severity": "error"|"warning"|"info" }],',
3587
+ ' "suggestions": [string]',
3588
+ 'Rules:',
3589
+ '- Return concrete, line-specific issues with severity.',
3590
+ '- Every issue MUST reference a line number.',
3591
+ '- If the score is below 50, list at least 2 specific issues.',
3592
+ '- Prioritize REAL BUGS: wrong operators, logic errors, off-by-one, type mismatches.',
3593
+ '- Only report style issues AFTER listing all real bugs.',
3594
+ '- Return ONLY the JSON object, no markdown fences or extra text.',
3595
+ ].join('\n');
3596
+ let raw = {};
3597
+ try {
3598
+ const result = await this.chatComplete(sysPrompt, code);
3599
+ const cleaned = result.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim();
3600
+ raw = JSON.parse(cleaned);
3601
+ }
3602
+ catch {
3603
+ // If parsing fails, return minimal review
3604
+ }
3565
3605
  const score = typeof raw.score === 'number' ? raw.score : 0;
3566
3606
  const issues = Array.isArray(raw.issues) ? raw.issues : [];
3567
3607
  const suggestions = Array.isArray(raw.suggestions) ? raw.suggestions : [];
@@ -3729,12 +3769,25 @@ document.addEventListener('DOMContentLoaded', () => {
3729
3769
  : '';
3730
3770
  }
3731
3771
  const augmentedCode = preamble ? `${preamble}${code}` : code;
3732
- const response = await this.client.post('/api/ai/fix', {
3733
- code: augmentedCode,
3734
- language,
3735
- fixType,
3736
- });
3737
- const raw = response.data ?? {};
3772
+ const sysPrompt = [
3773
+ `You are a ${language} code fixer. Fix the code for: ${fixType}.`,
3774
+ 'Return a JSON object with:',
3775
+ ' "fixed": the corrected code as a string,',
3776
+ ' "changes": [{ "line": number, "before": string, "after": string, "reason": string }]',
3777
+ 'Rules:',
3778
+ '- Fix ONLY the issues related to the fix type.',
3779
+ '- Do not add comments, do not restructure beyond the minimal fix.',
3780
+ '- Return ONLY the JSON object, no markdown fences.',
3781
+ ].join('\n');
3782
+ let raw = {};
3783
+ try {
3784
+ const result = await this.chatComplete(sysPrompt, augmentedCode);
3785
+ const cleaned = result.replace(/^```[\w]*\n?/gm, '').replace(/\n?```$/gm, '').trim();
3786
+ raw = JSON.parse(cleaned);
3787
+ }
3788
+ catch {
3789
+ // If parsing fails, fall through to client-side handling
3790
+ }
3738
3791
  let fixed = typeof raw.fixed === 'string' ? raw.fixed : (typeof raw.code === 'string' ? raw.code : code);
3739
3792
  let changes = Array.isArray(raw.changes) ? raw.changes : [];
3740
3793
  // If server returned no changes but we found issues, strip
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.6.30",
3
+ "version": "1.6.31",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [