vigthoria-cli 1.0.0

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.
Files changed (75) hide show
  1. package/README.md +413 -0
  2. package/dist/commands/auth.d.ts +24 -0
  3. package/dist/commands/auth.d.ts.map +1 -0
  4. package/dist/commands/auth.js +194 -0
  5. package/dist/commands/auth.js.map +1 -0
  6. package/dist/commands/chat.d.ts +64 -0
  7. package/dist/commands/chat.d.ts.map +1 -0
  8. package/dist/commands/chat.js +596 -0
  9. package/dist/commands/chat.js.map +1 -0
  10. package/dist/commands/config.d.ts +25 -0
  11. package/dist/commands/config.d.ts.map +1 -0
  12. package/dist/commands/config.js +291 -0
  13. package/dist/commands/config.js.map +1 -0
  14. package/dist/commands/edit.d.ts +28 -0
  15. package/dist/commands/edit.d.ts.map +1 -0
  16. package/dist/commands/edit.js +257 -0
  17. package/dist/commands/edit.js.map +1 -0
  18. package/dist/commands/explain.d.ts +21 -0
  19. package/dist/commands/explain.d.ts.map +1 -0
  20. package/dist/commands/explain.js +98 -0
  21. package/dist/commands/explain.js.map +1 -0
  22. package/dist/commands/generate.d.ts +25 -0
  23. package/dist/commands/generate.d.ts.map +1 -0
  24. package/dist/commands/generate.js +155 -0
  25. package/dist/commands/generate.js.map +1 -0
  26. package/dist/commands/review.d.ts +24 -0
  27. package/dist/commands/review.d.ts.map +1 -0
  28. package/dist/commands/review.js +153 -0
  29. package/dist/commands/review.js.map +1 -0
  30. package/dist/index.d.ts +16 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +205 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/utils/api.d.ts +88 -0
  35. package/dist/utils/api.d.ts.map +1 -0
  36. package/dist/utils/api.js +431 -0
  37. package/dist/utils/api.js.map +1 -0
  38. package/dist/utils/config.d.ts +57 -0
  39. package/dist/utils/config.d.ts.map +1 -0
  40. package/dist/utils/config.js +167 -0
  41. package/dist/utils/config.js.map +1 -0
  42. package/dist/utils/files.d.ts +31 -0
  43. package/dist/utils/files.d.ts.map +1 -0
  44. package/dist/utils/files.js +217 -0
  45. package/dist/utils/files.js.map +1 -0
  46. package/dist/utils/logger.d.ts +23 -0
  47. package/dist/utils/logger.d.ts.map +1 -0
  48. package/dist/utils/logger.js +104 -0
  49. package/dist/utils/logger.js.map +1 -0
  50. package/dist/utils/session.d.ts +61 -0
  51. package/dist/utils/session.d.ts.map +1 -0
  52. package/dist/utils/session.js +172 -0
  53. package/dist/utils/session.js.map +1 -0
  54. package/dist/utils/tools.d.ts +145 -0
  55. package/dist/utils/tools.d.ts.map +1 -0
  56. package/dist/utils/tools.js +781 -0
  57. package/dist/utils/tools.js.map +1 -0
  58. package/install.sh +248 -0
  59. package/package.json +52 -0
  60. package/src/commands/auth.ts +225 -0
  61. package/src/commands/chat.ts +690 -0
  62. package/src/commands/config.ts +297 -0
  63. package/src/commands/edit.ts +310 -0
  64. package/src/commands/explain.ts +115 -0
  65. package/src/commands/generate.ts +177 -0
  66. package/src/commands/review.ts +186 -0
  67. package/src/index.ts +221 -0
  68. package/src/types/marked-terminal.d.ts +31 -0
  69. package/src/utils/api.ts +531 -0
  70. package/src/utils/config.ts +224 -0
  71. package/src/utils/files.ts +212 -0
  72. package/src/utils/logger.ts +125 -0
  73. package/src/utils/session.ts +167 -0
  74. package/src/utils/tools.ts +933 -0
  75. package/tsconfig.json +20 -0
@@ -0,0 +1,31 @@
1
+ declare module 'marked-terminal' {
2
+ import { MarkedExtension } from 'marked';
3
+
4
+ interface MarkedTerminalOptions {
5
+ code?: (code: string) => string;
6
+ blockquote?: (text: string) => string;
7
+ html?: (html: string) => string;
8
+ heading?: (text: string, level: number) => string;
9
+ firstHeading?: (text: string) => string;
10
+ hr?: () => string;
11
+ listitem?: (text: string) => string;
12
+ list?: (body: string, ordered: boolean) => string;
13
+ paragraph?: (text: string) => string;
14
+ table?: (header: string, body: string) => string;
15
+ tablerow?: (content: string) => string;
16
+ tablecell?: (content: string, flags: { header: boolean; align: string }) => string;
17
+ strong?: (text: string) => string;
18
+ em?: (text: string) => string;
19
+ codespan?: (text: string) => string;
20
+ br?: () => string;
21
+ del?: (text: string) => string;
22
+ link?: (href: string, title: string, text: string) => string;
23
+ image?: (href: string, title: string, text: string) => string;
24
+ reflowText?: boolean;
25
+ width?: number;
26
+ showSectionPrefix?: boolean;
27
+ tab?: number;
28
+ }
29
+
30
+ export function markedTerminal(options?: MarkedTerminalOptions): MarkedExtension;
31
+ }
@@ -0,0 +1,531 @@
1
+ /**
2
+ * API Client for Vigthoria Backend
3
+ * Connects to coder.vigthoria.io API endpoints
4
+ */
5
+
6
+ import axios, { AxiosInstance, AxiosError } from 'axios';
7
+ import WebSocket from 'ws';
8
+ import { Config } from './config.js';
9
+ import { Logger } from './logger.js';
10
+
11
+ export interface ChatMessage {
12
+ role: 'user' | 'assistant' | 'system';
13
+ content: string;
14
+ }
15
+
16
+ export interface ChatResponse {
17
+ id: string;
18
+ message: string;
19
+ model: string;
20
+ usage?: {
21
+ prompt_tokens: number;
22
+ completion_tokens: number;
23
+ total_tokens: number;
24
+ };
25
+ }
26
+
27
+ export interface StreamChunk {
28
+ type: 'content' | 'done' | 'error';
29
+ content?: string;
30
+ error?: string;
31
+ }
32
+
33
+ export interface VigthoriUser {
34
+ id: string;
35
+ username: string;
36
+ email: string;
37
+ isAdmin: boolean;
38
+ subscription: {
39
+ plan: string;
40
+ projectLimit: number;
41
+ storageLimit: number;
42
+ viagen6Access: boolean;
43
+ aiModelsLimit: number;
44
+ prioritySupport: boolean;
45
+ teamCollaboration: boolean;
46
+ adminAccess: boolean;
47
+ };
48
+ }
49
+
50
+ export class APIClient {
51
+ private client: AxiosInstance;
52
+ private modelRouterClient: AxiosInstance;
53
+ private config: Config;
54
+ private logger: Logger;
55
+ private ws: WebSocket | null = null;
56
+
57
+ constructor(config: Config, logger: Logger) {
58
+ this.config = config;
59
+ this.logger = logger;
60
+
61
+ // Main Vigthoria Coder API (coder.vigthoria.io)
62
+ this.client = axios.create({
63
+ baseURL: config.get('apiUrl'),
64
+ timeout: 120000,
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ 'User-Agent': 'Vigthoria-CLI/1.0.0',
68
+ },
69
+ });
70
+
71
+ // Model Router API for AI operations
72
+ this.modelRouterClient = axios.create({
73
+ baseURL: 'https://coder.vigthoria.io',
74
+ timeout: 120000,
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ 'User-Agent': 'Vigthoria-CLI/1.0.0',
78
+ },
79
+ });
80
+
81
+ // Add auth interceptor
82
+ this.client.interceptors.request.use((req) => {
83
+ const token = this.config.get('authToken');
84
+ if (token) {
85
+ req.headers.Authorization = `Bearer ${token}`;
86
+ req.headers.Cookie = `vigthoria-auth-token=${token}`;
87
+ }
88
+ return req;
89
+ });
90
+
91
+ this.modelRouterClient.interceptors.request.use((req) => {
92
+ const token = this.config.get('authToken');
93
+ if (token) {
94
+ req.headers.Authorization = `Bearer ${token}`;
95
+ req.headers.Cookie = `vigthoria-auth-token=${token}`;
96
+ }
97
+ return req;
98
+ });
99
+
100
+ // Add response interceptor for token refresh
101
+ this.client.interceptors.response.use(
102
+ (res) => res,
103
+ async (error: AxiosError) => {
104
+ if (error.response?.status === 401) {
105
+ const refreshed = await this.refreshToken();
106
+ if (refreshed && error.config) {
107
+ return this.client.request(error.config);
108
+ }
109
+ }
110
+ throw error;
111
+ }
112
+ );
113
+ }
114
+
115
+ // Authentication - Uses Vigthoria Coder /api/login endpoint
116
+ async login(email: string, password: string): Promise<boolean> {
117
+ try {
118
+ const response = await this.client.post('/api/login', { email, password });
119
+
120
+ if (!response.data.success) {
121
+ throw new Error(response.data.error || 'Login failed');
122
+ }
123
+
124
+ const { token, user } = response.data as { token: string; user: VigthoriUser };
125
+
126
+ this.config.setAuth({
127
+ token,
128
+ userId: user.id,
129
+ email: user.email,
130
+ });
131
+
132
+ // Set subscription from user data
133
+ this.config.setSubscription({
134
+ plan: user.subscription?.plan || 'developer',
135
+ status: 'active',
136
+ expiresAt: undefined,
137
+ });
138
+
139
+ return true;
140
+ } catch (error) {
141
+ const axiosError = error as AxiosError;
142
+ const message = (axiosError.response?.data as { error?: string })?.error || (error as Error).message;
143
+ this.logger.error('Login failed:', message);
144
+ return false;
145
+ }
146
+ }
147
+
148
+ async loginWithToken(token: string): Promise<boolean> {
149
+ try {
150
+ // Validate token by making a request to user info endpoint
151
+ this.config.set('authToken', token);
152
+
153
+ const response = await this.client.get('/api/user/profile', {
154
+ headers: {
155
+ Authorization: `Bearer ${token}`,
156
+ Cookie: `vigthoria-auth-token=${token}`,
157
+ },
158
+ });
159
+
160
+ if (response.data && response.data.user) {
161
+ const user = response.data.user;
162
+
163
+ this.config.setAuth({
164
+ token,
165
+ userId: user.id,
166
+ email: user.email,
167
+ });
168
+
169
+ this.config.setSubscription({
170
+ plan: user.subscription?.plan || user.subscription_plan || 'developer',
171
+ status: 'active',
172
+ expiresAt: undefined,
173
+ });
174
+
175
+ return true;
176
+ }
177
+
178
+ return false;
179
+ } catch (error) {
180
+ this.config.clearAuth();
181
+ this.logger.error('Token validation failed:', (error as Error).message);
182
+ return false;
183
+ }
184
+ }
185
+
186
+ private async refreshToken(): Promise<boolean> {
187
+ const refreshToken = this.config.get('refreshToken');
188
+ if (!refreshToken) return false;
189
+
190
+ try {
191
+ const response = await this.client.post('/api/token/refresh', {
192
+ refresh_token: refreshToken
193
+ });
194
+ const { token, access_token, refresh_token } = response.data;
195
+
196
+ this.config.set('authToken', token || access_token);
197
+ if (refresh_token) {
198
+ this.config.set('refreshToken', refresh_token);
199
+ }
200
+
201
+ return true;
202
+ } catch {
203
+ this.config.clearAuth();
204
+ return false;
205
+ }
206
+ }
207
+
208
+ async getSubscriptionStatus(): Promise<void> {
209
+ try {
210
+ const response = await this.client.get('/api/user/subscription');
211
+ const data = response.data;
212
+
213
+ this.config.setSubscription({
214
+ plan: data.plan || data.subscription_plan || 'developer',
215
+ status: data.status || 'active',
216
+ expiresAt: data.expiresAt || data.expires_at
217
+ });
218
+ } catch (error) {
219
+ this.logger.debug('Failed to get subscription status:', (error as Error).message);
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Chat API - Supports multiple inference strategies
225
+ *
226
+ * For authenticated users: Uses Vigthoria Cloud API
227
+ * For local users: Uses local Ollama or Model Router
228
+ */
229
+ async chat(messages: ChatMessage[], model: string, useLocal: boolean = false): Promise<ChatResponse> {
230
+ const resolvedModel = this.resolveModelId(model);
231
+
232
+ // If authenticated and not forcing local, use Vigthoria Cloud API
233
+ if (!useLocal && this.config.isAuthenticated()) {
234
+ try {
235
+ const response = await this.client.post('/api/ai/chat', {
236
+ messages,
237
+ model: resolvedModel,
238
+ maxTokens: this.config.get('preferences').maxTokens,
239
+ temperature: 0.7,
240
+ });
241
+
242
+ if (response.data.success !== false) {
243
+ return {
244
+ id: response.data.id || `vigthoria-${Date.now()}`,
245
+ message: response.data.response || response.data.message || response.data.content,
246
+ model: response.data.model || model,
247
+ usage: response.data.usage,
248
+ };
249
+ }
250
+ } catch (error) {
251
+ this.logger.debug('Vigthoria Cloud API failed, trying fallbacks...');
252
+ }
253
+ }
254
+
255
+ // Strategy 1: Try local Model Router's Vigthoria chat endpoint
256
+ try {
257
+ const response = await axios.post('http://localhost:4009/api/vigthoria/chat', {
258
+ messages,
259
+ model: resolvedModel,
260
+ maxTokens: this.config.get('preferences').maxTokens,
261
+ temperature: 0.7,
262
+ stream: false,
263
+ }, { timeout: 120000 });
264
+
265
+ if (response.data.success !== false) {
266
+ return {
267
+ id: response.data.id || `router-${Date.now()}`,
268
+ message: response.data.response || response.data.message || response.data.content,
269
+ model: response.data.model || model,
270
+ usage: response.data.usage,
271
+ };
272
+ }
273
+ } catch (error) {
274
+ this.logger.debug('Model router failed, trying Ollama directly...');
275
+ }
276
+
277
+ // Strategy 2: Try Ollama directly (for local development/testing)
278
+ try {
279
+ const ollamaModel = this.resolveToOllamaModel(model);
280
+ const prompt = this.formatMessagesForOllama(messages);
281
+
282
+ const response = await axios.post('http://localhost:11434/api/generate', {
283
+ model: ollamaModel,
284
+ prompt,
285
+ stream: false,
286
+ }, { timeout: 120000 });
287
+
288
+ return {
289
+ id: `ollama-${Date.now()}`,
290
+ message: response.data.response,
291
+ model: ollamaModel,
292
+ usage: {
293
+ prompt_tokens: response.data.prompt_eval_count || 0,
294
+ completion_tokens: response.data.eval_count || 0,
295
+ total_tokens: (response.data.prompt_eval_count || 0) + (response.data.eval_count || 0),
296
+ },
297
+ };
298
+ } catch (error) {
299
+ this.logger.debug('Ollama failed...');
300
+ }
301
+
302
+ // Strategy 3: Try ViAgen6 AI endpoint (authenticated fallback)
303
+ if (this.config.isAuthenticated()) {
304
+ try {
305
+ const response = await this.client.post('/viagen6/api/ai/generate', {
306
+ prompt: messages.map(m => `${m.role}: ${m.content}`).join('\n'),
307
+ model: resolvedModel,
308
+ max_tokens: this.config.get('preferences').maxTokens,
309
+ });
310
+
311
+ return {
312
+ id: `viagen6-${Date.now()}`,
313
+ message: response.data.code || response.data.response || response.data.content,
314
+ model: model,
315
+ };
316
+ } catch (fallbackError) {
317
+ // Continue to error
318
+ }
319
+ }
320
+
321
+ throw new Error('All AI backends unavailable. Please check your connection or authentication.');
322
+ }
323
+
324
+ // Map CLI model names to Ollama model names (for local fallback)
325
+ private resolveToOllamaModel(model: string): string {
326
+ const ollamaMap: Record<string, string> = {
327
+ 'fast': 'qwen3:0.6b',
328
+ 'mini': 'smollm2:135m',
329
+ 'code': 'qwen2.5-coder:7b',
330
+ 'balanced': 'phi3:mini',
331
+ 'creative': 'gemma3:latest',
332
+ 'vigthoria-fast-1.7b': 'qwen3:0.6b',
333
+ 'vigthoria-mini-0.6b': 'smollm2:135m',
334
+ 'vigthoria-v2-code-8b': 'qwen2.5-coder:7b',
335
+ 'vigthoria-balanced-4b': 'phi3:mini',
336
+ 'vigthoria-creative-9b-v4': 'gemma3:latest',
337
+ };
338
+ return ollamaMap[model] || model;
339
+ }
340
+
341
+ // Format messages for Ollama's generate API
342
+ private formatMessagesForOllama(messages: ChatMessage[]): string {
343
+ return messages
344
+ .filter(m => m.role !== 'system' || messages.indexOf(m) === 0)
345
+ .map(m => {
346
+ if (m.role === 'system') return `System: ${m.content}\n\n`;
347
+ if (m.role === 'user') return `User: ${m.content}\n`;
348
+ return `Assistant: ${m.content}\n`;
349
+ })
350
+ .join('') + 'Assistant:';
351
+ }
352
+
353
+ // Streaming chat
354
+ async *chatStream(
355
+ messages: ChatMessage[],
356
+ model: string
357
+ ): AsyncGenerator<StreamChunk> {
358
+ const wsUrl = this.config.get('wsUrl');
359
+ const token = this.config.get('authToken');
360
+
361
+ return new Promise((resolve, reject) => {
362
+ const ws = new WebSocket(`${wsUrl}/chat`, {
363
+ headers: { Authorization: `Bearer ${token}` },
364
+ });
365
+
366
+ ws.on('open', () => {
367
+ ws.send(JSON.stringify({
368
+ type: 'chat',
369
+ messages,
370
+ model: this.resolveModelId(model),
371
+ stream: true,
372
+ }));
373
+ });
374
+
375
+ ws.on('message', (data) => {
376
+ const chunk = JSON.parse(data.toString());
377
+ // Yield chunk - but this pattern won't work directly
378
+ // Need to use event emitter pattern instead
379
+ });
380
+
381
+ ws.on('error', (error) => {
382
+ reject(error);
383
+ });
384
+
385
+ ws.on('close', () => {
386
+ resolve(undefined);
387
+ });
388
+ });
389
+ }
390
+
391
+ // Non-streaming alternative with callback
392
+ async chatWithCallback(
393
+ messages: ChatMessage[],
394
+ model: string,
395
+ onChunk: (chunk: string) => void,
396
+ onDone: () => void,
397
+ onError: (error: Error) => void
398
+ ): Promise<void> {
399
+ const wsUrl = this.config.get('wsUrl');
400
+ const token = this.config.get('authToken');
401
+
402
+ return new Promise((resolve, reject) => {
403
+ const ws = new WebSocket(`${wsUrl}/chat`, {
404
+ headers: { Authorization: `Bearer ${token}` },
405
+ });
406
+
407
+ ws.on('open', () => {
408
+ ws.send(JSON.stringify({
409
+ type: 'chat',
410
+ messages,
411
+ model: this.resolveModelId(model),
412
+ stream: true,
413
+ }));
414
+ });
415
+
416
+ ws.on('message', (data) => {
417
+ try {
418
+ const chunk = JSON.parse(data.toString());
419
+ if (chunk.type === 'content') {
420
+ onChunk(chunk.content);
421
+ } else if (chunk.type === 'done') {
422
+ onDone();
423
+ ws.close();
424
+ resolve();
425
+ } else if (chunk.type === 'error') {
426
+ onError(new Error(chunk.error));
427
+ ws.close();
428
+ reject(new Error(chunk.error));
429
+ }
430
+ } catch (e) {
431
+ // Raw text chunk
432
+ onChunk(data.toString());
433
+ }
434
+ });
435
+
436
+ ws.on('error', (error) => {
437
+ onError(error as Error);
438
+ reject(error);
439
+ });
440
+
441
+ ws.on('close', () => {
442
+ resolve();
443
+ });
444
+ });
445
+ }
446
+
447
+ // Code operations
448
+ async generateCode(prompt: string, language: string, model: string): Promise<string> {
449
+ const response = await this.client.post('/ai/generate', {
450
+ prompt,
451
+ language,
452
+ model: this.resolveModelId(model),
453
+ });
454
+
455
+ return response.data.code;
456
+ }
457
+
458
+ async explainCode(code: string, language: string): Promise<string> {
459
+ const response = await this.client.post('/ai/explain', {
460
+ code,
461
+ language,
462
+ });
463
+
464
+ return response.data.explanation;
465
+ }
466
+
467
+ async reviewCode(code: string, language: string): Promise<{
468
+ score: number;
469
+ issues: { type: string; line: number; message: string; severity: string }[];
470
+ suggestions: string[];
471
+ }> {
472
+ const response = await this.client.post('/ai/review', {
473
+ code,
474
+ language,
475
+ });
476
+
477
+ return response.data;
478
+ }
479
+
480
+ async fixCode(code: string, language: string, fixType: string): Promise<{
481
+ fixed: string;
482
+ changes: { line: number; before: string; after: string; reason: string }[];
483
+ }> {
484
+ const response = await this.client.post('/ai/fix', {
485
+ code,
486
+ language,
487
+ fixType,
488
+ });
489
+
490
+ return response.data;
491
+ }
492
+
493
+ // Model resolution - maps short names to actual Vigthoria model IDs
494
+ private resolveModelId(shortName: string): string {
495
+ const modelMap: Record<string, string> = {
496
+ // Vigthoria Native Models
497
+ 'fast': 'vigthoria-fast-1.7b',
498
+ 'mini': 'vigthoria-mini-0.6b',
499
+ 'code': 'vigthoria-v2-code-8b',
500
+ 'balanced': 'vigthoria-balanced-4b',
501
+ 'creative': 'vigthoria-creative-9b-v4',
502
+ 'music': 'vigthoria-music-master-4b',
503
+ // Aliases
504
+ 'pro': 'vigthoria-balanced-4b',
505
+ 'ultra': 'vigthoria-creative-9b-v4',
506
+ // Legacy Vigthoria models
507
+ 'master': 'vigthoria_master:latest',
508
+ 'c1': 'vigthoria_c1_m:latest',
509
+ 'm1': 'vigthoria_m1_m:latest',
510
+ // External models
511
+ 'qwen-coder': 'qwen2.5-coder:7b',
512
+ 'qwen-coder-32b': 'qwen2.5-coder:32b',
513
+ 'deepseek': 'deepseek-coder-v2:latest',
514
+ 'deepseek-r1': 'deepseek-r1:8b',
515
+ 'llama3': 'llama3:8b-instruct-q4_0',
516
+ 'mixtral': 'mixtral:8x7b-instruct-v0.1-q4_K_M',
517
+ };
518
+
519
+ return modelMap[shortName] || shortName;
520
+ }
521
+
522
+ // Health check
523
+ async healthCheck(): Promise<boolean> {
524
+ try {
525
+ await this.client.get('/health');
526
+ return true;
527
+ } catch {
528
+ return false;
529
+ }
530
+ }
531
+ }