vigthoria-cli 1.6.20 → 1.6.22
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/dist/commands/chat.d.ts +8 -0
- package/dist/commands/chat.js +129 -12
- package/dist/commands/deploy.js +16 -8
- package/dist/commands/edit.js +22 -5
- package/dist/commands/generate.d.ts +5 -0
- package/dist/commands/generate.js +30 -7
- package/dist/commands/repo.js +14 -7
- package/dist/index.js +9 -1
- package/dist/utils/api.d.ts +12 -0
- package/dist/utils/api.js +161 -27
- package/dist/utils/bridge-client.d.ts +110 -0
- package/dist/utils/bridge-client.js +278 -0
- package/dist/utils/logger.d.ts +9 -1
- package/dist/utils/logger.js +13 -2
- package/dist/utils/tools.js +4 -0
- package/package.json +2 -1
package/dist/utils/api.d.ts
CHANGED
|
@@ -217,6 +217,13 @@ export declare class APIClient {
|
|
|
217
217
|
private getVigFlowAccessToken;
|
|
218
218
|
private getVigFlowHeaders;
|
|
219
219
|
private withVigFlow;
|
|
220
|
+
/**
|
|
221
|
+
* Build the correct sub-path for VigFlow endpoints.
|
|
222
|
+
* Local servers (e.g. localhost:5060) need `/api/…` prefix.
|
|
223
|
+
* The remote gateway URL already ends with `/api/vigflow`, so appending
|
|
224
|
+
* another `/api/…` would double the prefix and cause 404s.
|
|
225
|
+
*/
|
|
226
|
+
private vigFlowEndpoint;
|
|
220
227
|
listVigFlowTemplates(options?: {
|
|
221
228
|
category?: string;
|
|
222
229
|
search?: string;
|
|
@@ -339,6 +346,11 @@ export declare class APIClient {
|
|
|
339
346
|
}[];
|
|
340
347
|
suggestions: string[];
|
|
341
348
|
}>;
|
|
349
|
+
/**
|
|
350
|
+
* Lightweight client-side heuristic scan: catches common code smells
|
|
351
|
+
* AND logic/arithmetic bugs so review never returns "score 30, no issues".
|
|
352
|
+
*/
|
|
353
|
+
private heuristicCodeIssues;
|
|
342
354
|
fixCode(code: string, language: string, fixType: string): Promise<{
|
|
343
355
|
fixed: string;
|
|
344
356
|
changes: {
|
package/dist/utils/api.js
CHANGED
|
@@ -307,13 +307,19 @@ class APIClient {
|
|
|
307
307
|
}
|
|
308
308
|
getVigFlowBaseUrls() {
|
|
309
309
|
const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
|
|
310
|
+
// Put the remote gateway first, since local VigFlow servers are
|
|
311
|
+
// rarely running for end-user CLI installations. This avoids
|
|
312
|
+
// wasting connection-attempt time on 127.0.0.1 and hitting the
|
|
313
|
+
// remote gateway only after the local attempts have already
|
|
314
|
+
// errored — which surfaces as a confusing "last error" 404 in
|
|
315
|
+
// some setups.
|
|
310
316
|
const urls = [
|
|
311
317
|
process.env.VIGTHORIA_VIGFLOW_URL,
|
|
312
318
|
process.env.VIGFLOW_URL,
|
|
313
319
|
process.env.WORKFLOW_BUILDER_URL,
|
|
320
|
+
`${configuredApiUrl}/api/vigflow`,
|
|
314
321
|
'http://127.0.0.1:5060',
|
|
315
322
|
'http://127.0.0.1:5050',
|
|
316
|
-
`${configuredApiUrl}/api/vigflow`,
|
|
317
323
|
].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
|
|
318
324
|
return [...new Set(urls)];
|
|
319
325
|
}
|
|
@@ -793,6 +799,19 @@ class APIClient {
|
|
|
793
799
|
}
|
|
794
800
|
throw lastError || new Error(`No VigFlow backend available for ${operation}.`);
|
|
795
801
|
}
|
|
802
|
+
/**
|
|
803
|
+
* Build the correct sub-path for VigFlow endpoints.
|
|
804
|
+
* Local servers (e.g. localhost:5060) need `/api/…` prefix.
|
|
805
|
+
* The remote gateway URL already ends with `/api/vigflow`, so appending
|
|
806
|
+
* another `/api/…` would double the prefix and cause 404s.
|
|
807
|
+
*/
|
|
808
|
+
vigFlowEndpoint(baseUrl, subPath) {
|
|
809
|
+
if (/\/api\/vigflow\/?$/i.test(baseUrl)) {
|
|
810
|
+
// Remote gateway – subPath like '/templates' is enough
|
|
811
|
+
return `${baseUrl.replace(/\/$/, '')}${subPath}`;
|
|
812
|
+
}
|
|
813
|
+
return `${baseUrl}/api${subPath}`;
|
|
814
|
+
}
|
|
796
815
|
async listVigFlowTemplates(options = {}) {
|
|
797
816
|
return this.withVigFlow('list templates', async (baseUrl, headers) => {
|
|
798
817
|
const query = new URLSearchParams();
|
|
@@ -802,7 +821,7 @@ class APIClient {
|
|
|
802
821
|
if (options.search) {
|
|
803
822
|
query.set('search', options.search);
|
|
804
823
|
}
|
|
805
|
-
const url = `${baseUrl
|
|
824
|
+
const url = `${this.vigFlowEndpoint(baseUrl, '/templates')}${query.size > 0 ? `?${query.toString()}` : ''}`;
|
|
806
825
|
const response = await axios_1.default.get(url, {
|
|
807
826
|
headers,
|
|
808
827
|
timeout: 30000,
|
|
@@ -813,7 +832,7 @@ class APIClient {
|
|
|
813
832
|
}
|
|
814
833
|
async listVigFlowWorkflows() {
|
|
815
834
|
return this.withVigFlow('list workflows', async (baseUrl, headers) => {
|
|
816
|
-
const response = await axios_1.default.get(
|
|
835
|
+
const response = await axios_1.default.get(this.vigFlowEndpoint(baseUrl, '/workflows'), {
|
|
817
836
|
headers,
|
|
818
837
|
timeout: 30000,
|
|
819
838
|
});
|
|
@@ -865,7 +884,7 @@ class APIClient {
|
|
|
865
884
|
}
|
|
866
885
|
async useVigFlowTemplate(templateId, options = {}) {
|
|
867
886
|
return this.withVigFlow('use template', async (baseUrl, headers) => {
|
|
868
|
-
const response = await axios_1.default.post(`${baseUrl
|
|
887
|
+
const response = await axios_1.default.post(`${this.vigFlowEndpoint(baseUrl, `/templates/${encodeURIComponent(templateId)}/use`)}`, {
|
|
869
888
|
name: options.name,
|
|
870
889
|
variables: options.variables || {},
|
|
871
890
|
}, {
|
|
@@ -881,7 +900,7 @@ class APIClient {
|
|
|
881
900
|
}
|
|
882
901
|
async runVigFlowWorkflow(workflowId, options = {}) {
|
|
883
902
|
return this.withVigFlow('run workflow', async (baseUrl, headers) => {
|
|
884
|
-
const response = await axios_1.default.post(`${baseUrl
|
|
903
|
+
const response = await axios_1.default.post(`${this.vigFlowEndpoint(baseUrl, `/executions/run/${encodeURIComponent(workflowId)}`)}`, {
|
|
885
904
|
data: options.data || {},
|
|
886
905
|
options: options.executionOptions || {},
|
|
887
906
|
}, {
|
|
@@ -897,7 +916,7 @@ class APIClient {
|
|
|
897
916
|
}
|
|
898
917
|
async getVigFlowExecutionStatus(executionId) {
|
|
899
918
|
return this.withVigFlow('execution status', async (baseUrl, headers) => {
|
|
900
|
-
const response = await axios_1.default.get(`${baseUrl
|
|
919
|
+
const response = await axios_1.default.get(`${this.vigFlowEndpoint(baseUrl, `/executions/${encodeURIComponent(executionId)}`)}`, {
|
|
901
920
|
headers,
|
|
902
921
|
timeout: 30000,
|
|
903
922
|
});
|
|
@@ -918,6 +937,11 @@ class APIClient {
|
|
|
918
937
|
const localWorkspaceSummary = this.buildLocalWorkspaceSummary(localWorkspacePath);
|
|
919
938
|
const requestedModel = String(resolvedContext.model || resolvedContext.requestedModel || 'agent');
|
|
920
939
|
const resolvedModel = this.resolvePermittedModelId(requestedModel);
|
|
940
|
+
// When the server cannot directly access the workspace (e.g. Windows
|
|
941
|
+
// client), use the local path as a hint and flag that the workspace
|
|
942
|
+
// files are provided inline in localWorkspaceSummary.workspaceFiles.
|
|
943
|
+
const effectiveWorkspacePath = serverWorkspacePath || localWorkspacePath || null;
|
|
944
|
+
const needsHydration = !serverWorkspacePath && !!localWorkspacePath;
|
|
921
945
|
const payload = {
|
|
922
946
|
workspace: resolvedContext.workspace || null,
|
|
923
947
|
activeFile: resolvedContext.activeFile || null,
|
|
@@ -931,12 +955,16 @@ class APIClient {
|
|
|
931
955
|
executionSurface: resolvedContext.executionSurface || 'cli',
|
|
932
956
|
clientSurface: resolvedContext.clientSurface || 'cli',
|
|
933
957
|
localMachineCapable: resolvedContext.localMachineCapable !== false,
|
|
934
|
-
workspacePath:
|
|
935
|
-
projectPath:
|
|
936
|
-
targetPath:
|
|
958
|
+
workspacePath: effectiveWorkspacePath,
|
|
959
|
+
projectPath: effectiveWorkspacePath,
|
|
960
|
+
targetPath: effectiveWorkspacePath,
|
|
937
961
|
localWorkspacePath: localWorkspacePath || null,
|
|
938
962
|
localWorkspaceName: localWorkspacePath ? path_1.default.basename(localWorkspacePath) : null,
|
|
939
963
|
localWorkspaceSummary,
|
|
964
|
+
// Signal to the server that the workspace filesystem is not locally
|
|
965
|
+
// accessible — it must hydrate a temp directory from the provided
|
|
966
|
+
// workspaceFiles before the agent starts using tools.
|
|
967
|
+
workspaceHydrationRequired: needsHydration,
|
|
940
968
|
contextId: resolvedContext.contextId,
|
|
941
969
|
traceId: resolvedContext.traceId,
|
|
942
970
|
mcpContextId: resolvedContext.mcpContextId || null,
|
|
@@ -2302,11 +2330,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2302
2330
|
return result.message;
|
|
2303
2331
|
}
|
|
2304
2332
|
if (Array.isArray(data?.events)) {
|
|
2305
|
-
const completionEvent = [...data.events].reverse().find((event) => event && event.type === 'complete' && typeof event.summary === 'string');
|
|
2333
|
+
const completionEvent = [...data.events].reverse().find((event) => event && event.type === 'complete' && typeof event.summary === 'string' && event.summary.trim());
|
|
2306
2334
|
if (completionEvent) {
|
|
2307
2335
|
return completionEvent.summary;
|
|
2308
2336
|
}
|
|
2309
|
-
const messageEvent = [...data.events].reverse().find((event) => event && event.type === 'message' && typeof event.content === 'string');
|
|
2337
|
+
const messageEvent = [...data.events].reverse().find((event) => event && event.type === 'message' && typeof event.content === 'string' && event.content.trim());
|
|
2310
2338
|
if (messageEvent) {
|
|
2311
2339
|
return messageEvent.content;
|
|
2312
2340
|
}
|
|
@@ -2333,7 +2361,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2333
2361
|
const toolResults = [];
|
|
2334
2362
|
const filesRead = [];
|
|
2335
2363
|
const filesWritten = [];
|
|
2336
|
-
|
|
2364
|
+
const assistantFragments = [];
|
|
2337
2365
|
for (const event of events) {
|
|
2338
2366
|
if (!event)
|
|
2339
2367
|
continue;
|
|
@@ -2352,16 +2380,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2352
2380
|
}
|
|
2353
2381
|
}
|
|
2354
2382
|
if (event.type === 'assistant' && typeof event.content === 'string' && event.content.trim()) {
|
|
2355
|
-
|
|
2383
|
+
assistantFragments.push(event.content.trim());
|
|
2356
2384
|
}
|
|
2357
2385
|
// Some servers emit 'text' events for incremental assistant text
|
|
2358
2386
|
if (event.type === 'text' && typeof event.content === 'string' && event.content.trim()) {
|
|
2359
|
-
|
|
2387
|
+
assistantFragments.push(event.content.trim());
|
|
2388
|
+
}
|
|
2389
|
+
// Some servers emit content_block_delta for streamed text
|
|
2390
|
+
if (event.type === 'content_block_delta' && typeof event.delta?.text === 'string' && event.delta.text.trim()) {
|
|
2391
|
+
assistantFragments.push(event.delta.text.trim());
|
|
2360
2392
|
}
|
|
2361
2393
|
}
|
|
2362
|
-
//
|
|
2363
|
-
|
|
2364
|
-
|
|
2394
|
+
// Concatenate ALL assistant text fragments in order — keeps full
|
|
2395
|
+
// multi-turn reasoning instead of only the last fragment.
|
|
2396
|
+
const fullAssistantText = assistantFragments.join('\n\n').trim();
|
|
2397
|
+
if (fullAssistantText.length > 20) {
|
|
2398
|
+
return fullAssistantText;
|
|
2365
2399
|
}
|
|
2366
2400
|
// Otherwise build a summary from tool evidence
|
|
2367
2401
|
const sections = [];
|
|
@@ -2722,6 +2756,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2722
2756
|
throw new Error(errors.join(' | '));
|
|
2723
2757
|
}
|
|
2724
2758
|
formatOperatorResponse(data = {}) {
|
|
2759
|
+
// If the server returned a direct answer field, prefer it (for lookup tasks)
|
|
2760
|
+
if (typeof data.answer === 'string' && data.answer.trim()) {
|
|
2761
|
+
return data.answer.trim();
|
|
2762
|
+
}
|
|
2763
|
+
if (typeof data.result === 'string' && data.result.trim()) {
|
|
2764
|
+
return data.result.trim();
|
|
2765
|
+
}
|
|
2725
2766
|
const lines = [];
|
|
2726
2767
|
if (data.summary) {
|
|
2727
2768
|
lines.push(String(data.summary).trim());
|
|
@@ -3200,13 +3241,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3200
3241
|
// Prepend a forceful scope-enforcement instruction so the model
|
|
3201
3242
|
// doesn't expand a small task into an oversized glossy page.
|
|
3202
3243
|
const scopedPrompt = [
|
|
3203
|
-
'IMPORTANT — SCOPE CONSTRAINTS:',
|
|
3204
|
-
'1.
|
|
3205
|
-
'2. If the
|
|
3206
|
-
'3.
|
|
3207
|
-
'4.
|
|
3208
|
-
'
|
|
3209
|
-
'
|
|
3244
|
+
'IMPORTANT — MANDATORY SCOPE CONSTRAINTS (violation = failure):',
|
|
3245
|
+
'1. Output ONLY what the user explicitly asked for. Nothing more.',
|
|
3246
|
+
'2. If the prompt is ≤ 15 words, produce ≤ 80 lines of code maximum.',
|
|
3247
|
+
'3. If the user says "tiny", "small", "simple", "minimal", or "basic", produce ≤ 50 lines.',
|
|
3248
|
+
'4. NEVER add any of these unless the user EXPLICITLY requests them:',
|
|
3249
|
+
' - Hero sections, CTAs, testimonials, pricing tables, footers, navbars',
|
|
3250
|
+
' - CSS animations, gradients, neon effects, glass-morphism, particles',
|
|
3251
|
+
' - Google Fonts, Font Awesome, external CDN links, icon libraries',
|
|
3252
|
+
' - Responsive breakpoints, media queries (unless asked)',
|
|
3253
|
+
' - Multiple pages or components when one was requested',
|
|
3254
|
+
'5. Prefer inline styles or a small <style> block. No CSS frameworks.',
|
|
3255
|
+
'6. Return raw code only — no markdown fences, no explanations, no comments about what could be added.',
|
|
3256
|
+
'7. Match the complexity of the request: a "hello world" is 1-5 lines, a "button" is 5-15 lines.',
|
|
3210
3257
|
'',
|
|
3211
3258
|
prompt,
|
|
3212
3259
|
].join('\n');
|
|
@@ -3243,22 +3290,109 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3243
3290
|
const response = await this.client.post('/api/ai/review', {
|
|
3244
3291
|
code,
|
|
3245
3292
|
language,
|
|
3293
|
+
instructions: [
|
|
3294
|
+
'Return concrete, line-specific issues with severity.',
|
|
3295
|
+
'Every issue MUST reference a line number.',
|
|
3296
|
+
'If the score is below 50, you MUST list at least 2 specific issues.',
|
|
3297
|
+
'Prioritize REAL BUGS over style issues:',
|
|
3298
|
+
'- Wrong arithmetic operators (+ instead of -, * instead of /, etc.)',
|
|
3299
|
+
'- Logic errors (function named "add" using subtraction, wrong comparisons)',
|
|
3300
|
+
'- Off-by-one errors, incorrect return values',
|
|
3301
|
+
'- Type mismatches, null/undefined access',
|
|
3302
|
+
'Only report style issues (console.log, naming) AFTER listing all real bugs.',
|
|
3303
|
+
].join(' '),
|
|
3246
3304
|
});
|
|
3247
3305
|
const raw = response.data ?? {};
|
|
3248
3306
|
const score = typeof raw.score === 'number' ? raw.score : 0;
|
|
3249
3307
|
const issues = Array.isArray(raw.issues) ? raw.issues : [];
|
|
3250
3308
|
const suggestions = Array.isArray(raw.suggestions) ? raw.suggestions : [];
|
|
3251
|
-
//
|
|
3309
|
+
// Always run client-side heuristics and merge any findings the
|
|
3310
|
+
// server missed. This ensures arithmetic/logic bugs are surfaced
|
|
3311
|
+
// even when the server only reports style issues like console.log.
|
|
3312
|
+
const heuristic = this.heuristicCodeIssues(code, language);
|
|
3313
|
+
for (const h of heuristic) {
|
|
3314
|
+
// Avoid duplicating issues the server already reported on the same line
|
|
3315
|
+
const isDuplicate = issues.some((existing) => existing.line === h.line && existing.type === h.type);
|
|
3316
|
+
if (!isDuplicate) {
|
|
3317
|
+
issues.push(h);
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
// Sort: errors first, then warnings, then info
|
|
3321
|
+
const severityOrder = { error: 0, warning: 1, info: 2 };
|
|
3322
|
+
issues.sort((a, b) => (severityOrder[a.severity] ?? 3) - (severityOrder[b.severity] ?? 3));
|
|
3323
|
+
// Prevent contradictory output: low score but zero issues.
|
|
3252
3324
|
if (score < 50 && issues.length === 0) {
|
|
3253
3325
|
issues.push({
|
|
3254
3326
|
type: 'quality',
|
|
3255
|
-
line:
|
|
3256
|
-
message: `The analysis returned a low quality score (${score}/100) but did not enumerate specific issues.
|
|
3327
|
+
line: 1,
|
|
3328
|
+
message: `The analysis returned a low quality score (${score}/100) but did not enumerate specific issues. Re-run the review or inspect the file manually.`,
|
|
3257
3329
|
severity: 'warning',
|
|
3258
3330
|
});
|
|
3259
3331
|
}
|
|
3260
3332
|
return { score, issues, suggestions };
|
|
3261
3333
|
}
|
|
3334
|
+
/**
|
|
3335
|
+
* Lightweight client-side heuristic scan: catches common code smells
|
|
3336
|
+
* AND logic/arithmetic bugs so review never returns "score 30, no issues".
|
|
3337
|
+
*/
|
|
3338
|
+
heuristicCodeIssues(code, language) {
|
|
3339
|
+
const issues = [];
|
|
3340
|
+
const lines = code.split('\n');
|
|
3341
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3342
|
+
const line = lines[i];
|
|
3343
|
+
const lineNum = i + 1;
|
|
3344
|
+
// console.log left in production code
|
|
3345
|
+
if (/\bconsole\.(log|debug|info)\b/.test(line) && !/\/\//.test(line.slice(0, line.indexOf('console')))) {
|
|
3346
|
+
issues.push({ type: 'quality', line: lineNum, message: 'console.log/debug statement — remove or replace with proper logging.', severity: 'warning' });
|
|
3347
|
+
}
|
|
3348
|
+
// TODO/FIXME/HACK comments
|
|
3349
|
+
if (/\b(TODO|FIXME|HACK|XXX)\b/.test(line)) {
|
|
3350
|
+
issues.push({ type: 'maintainability', line: lineNum, message: `Unresolved ${line.match(/\b(TODO|FIXME|HACK|XXX)\b/)?.[0]} comment.`, severity: 'info' });
|
|
3351
|
+
}
|
|
3352
|
+
// Empty catch blocks
|
|
3353
|
+
if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(line)) {
|
|
3354
|
+
issues.push({ type: 'error-handling', line: lineNum, message: 'Empty catch block — errors are silently swallowed.', severity: 'warning' });
|
|
3355
|
+
}
|
|
3356
|
+
// Very long lines (> 200 chars)
|
|
3357
|
+
if (line.length > 200) {
|
|
3358
|
+
issues.push({ type: 'style', line: lineNum, message: `Line exceeds 200 characters (${line.length}).`, severity: 'info' });
|
|
3359
|
+
}
|
|
3360
|
+
// Arithmetic / logic bugs: function named 'add' but using subtraction
|
|
3361
|
+
if (/\breturn\s+\w+\s*-\s*\w+/.test(line)) {
|
|
3362
|
+
// Check if the enclosing function name implies addition
|
|
3363
|
+
for (let j = i; j >= Math.max(0, i - 5); j--) {
|
|
3364
|
+
if (/function\s+add\b/i.test(lines[j]) || /\badd\s*[=(]/.test(lines[j])) {
|
|
3365
|
+
issues.push({ type: 'logic', line: lineNum, message: 'Subtraction operator in a function named "add" — likely should be addition (+).', severity: 'error' });
|
|
3366
|
+
break;
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
// Multiplication where addition is expected
|
|
3371
|
+
if (/\breturn\s+\w+\s*\*\s*\w+/.test(line)) {
|
|
3372
|
+
for (let j = i; j >= Math.max(0, i - 5); j--) {
|
|
3373
|
+
if (/function\s+add\b/i.test(lines[j]) || /\badd\s*[=(]/.test(lines[j])) {
|
|
3374
|
+
issues.push({ type: 'logic', line: lineNum, message: 'Multiplication operator in a function named "add" — likely should be addition (+).', severity: 'error' });
|
|
3375
|
+
break;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
// Division where subtraction/addition is expected in subtract
|
|
3380
|
+
if (/\breturn\s+\w+\s*\+\s*\w+/.test(line)) {
|
|
3381
|
+
for (let j = i; j >= Math.max(0, i - 5); j--) {
|
|
3382
|
+
if (/function\s+subtract\b/i.test(lines[j]) || /\bsubtract\s*[=(]/.test(lines[j])) {
|
|
3383
|
+
issues.push({ type: 'logic', line: lineNum, message: 'Addition operator in a function named "subtract" — likely should be subtraction (-).', severity: 'error' });
|
|
3384
|
+
break;
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
// Off-by-one: comparing with === where <= or >= may be needed
|
|
3389
|
+
// (not implemented yet — would require more complex AST analysis)
|
|
3390
|
+
// Limit to 15 heuristic issues
|
|
3391
|
+
if (issues.length >= 15)
|
|
3392
|
+
break;
|
|
3393
|
+
}
|
|
3394
|
+
return issues;
|
|
3395
|
+
}
|
|
3262
3396
|
async fixCode(code, language, fixType) {
|
|
3263
3397
|
// Client-side syntax pre-check: detect obvious errors and include
|
|
3264
3398
|
// them in the request so the model has concrete signals.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vigthoria CLI → DevTools Bridge Telemetry Client
|
|
3
|
+
*
|
|
4
|
+
* Connects the local CLI to the remote bridge server in "commando" mode,
|
|
5
|
+
* streaming real-time activity (commands, tool calls, model responses,
|
|
6
|
+
* file edits, errors) and receiving admin-issued commands.
|
|
7
|
+
*
|
|
8
|
+
* Design principles:
|
|
9
|
+
* - Fire-and-forget: never blocks the CLI main flow
|
|
10
|
+
* - Auto-reconnects with exponential back-off
|
|
11
|
+
* - Opt-in via --bridge <url> flag
|
|
12
|
+
* - Sensitive data (API keys, tokens) is never transmitted
|
|
13
|
+
*/
|
|
14
|
+
export type TelemetryEventType = 'cli:start' | 'cli:command' | 'cli:prompt' | 'cli:model-response' | 'cli:tool-call' | 'cli:tool-result' | 'cli:file-edit' | 'cli:error' | 'cli:end' | 'cli:mode-change' | 'cli:heartbeat';
|
|
15
|
+
export interface TelemetryEvent {
|
|
16
|
+
type: TelemetryEventType;
|
|
17
|
+
payload: Record<string, unknown>;
|
|
18
|
+
ts: number;
|
|
19
|
+
clientId: string;
|
|
20
|
+
}
|
|
21
|
+
export interface AdminCommand {
|
|
22
|
+
type: 'admin:command';
|
|
23
|
+
action: string;
|
|
24
|
+
params?: Record<string, unknown>;
|
|
25
|
+
requestId?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface BridgeClientOptions {
|
|
28
|
+
bridgeUrl: string;
|
|
29
|
+
apiKey?: string;
|
|
30
|
+
machineLabel?: string;
|
|
31
|
+
onAdminCommand?: (cmd: AdminCommand) => void;
|
|
32
|
+
}
|
|
33
|
+
/** Get the active bridge client (may be null if --bridge was not used). */
|
|
34
|
+
export declare function getBridgeClient(): BridgeClient | null;
|
|
35
|
+
export declare class BridgeClient {
|
|
36
|
+
private ws;
|
|
37
|
+
private url;
|
|
38
|
+
private apiKey;
|
|
39
|
+
private machineLabel;
|
|
40
|
+
private clientId;
|
|
41
|
+
private connected;
|
|
42
|
+
private reconnectTimer;
|
|
43
|
+
private heartbeatTimer;
|
|
44
|
+
private queue;
|
|
45
|
+
private maxQueueSize;
|
|
46
|
+
private reconnectDelay;
|
|
47
|
+
private destroyed;
|
|
48
|
+
private onAdminCommand?;
|
|
49
|
+
constructor(opts: BridgeClientOptions);
|
|
50
|
+
connect(): Promise<void>;
|
|
51
|
+
destroy(): void;
|
|
52
|
+
get isConnected(): boolean;
|
|
53
|
+
/** CLI session started (command, flags, cwd). */
|
|
54
|
+
emitStart(data: {
|
|
55
|
+
command: string;
|
|
56
|
+
flags: Record<string, unknown>;
|
|
57
|
+
cwd: string;
|
|
58
|
+
}): void;
|
|
59
|
+
/** User entered a prompt / message. */
|
|
60
|
+
emitPrompt(data: {
|
|
61
|
+
prompt: string;
|
|
62
|
+
mode: string;
|
|
63
|
+
model: string;
|
|
64
|
+
}): void;
|
|
65
|
+
/** Model response received (summary only, not full content). */
|
|
66
|
+
emitModelResponse(data: {
|
|
67
|
+
model: string;
|
|
68
|
+
chars: number;
|
|
69
|
+
hasToolCalls: boolean;
|
|
70
|
+
preview: string;
|
|
71
|
+
}): void;
|
|
72
|
+
/** Tool is being called. */
|
|
73
|
+
emitToolCall(data: {
|
|
74
|
+
tool: string;
|
|
75
|
+
args: Record<string, string>;
|
|
76
|
+
}): void;
|
|
77
|
+
/** Tool finished executing. */
|
|
78
|
+
emitToolResult(data: {
|
|
79
|
+
tool: string;
|
|
80
|
+
success: boolean;
|
|
81
|
+
preview: string;
|
|
82
|
+
}): void;
|
|
83
|
+
/** File was written or edited. */
|
|
84
|
+
emitFileEdit(data: {
|
|
85
|
+
file: string;
|
|
86
|
+
action: 'write' | 'edit';
|
|
87
|
+
linesChanged: number;
|
|
88
|
+
}): void;
|
|
89
|
+
/** Error occurred. */
|
|
90
|
+
emitError(data: {
|
|
91
|
+
message: string;
|
|
92
|
+
code?: string;
|
|
93
|
+
}): void;
|
|
94
|
+
/** Mode changed (agent / operator / chat). */
|
|
95
|
+
emitModeChange(data: {
|
|
96
|
+
mode: string;
|
|
97
|
+
model: string;
|
|
98
|
+
}): void;
|
|
99
|
+
/** Session ended. */
|
|
100
|
+
emitEnd(data: {
|
|
101
|
+
reason: string;
|
|
102
|
+
}): void;
|
|
103
|
+
private emit;
|
|
104
|
+
private sendRaw;
|
|
105
|
+
private bufferEvent;
|
|
106
|
+
private flushQueue;
|
|
107
|
+
private scheduleReconnect;
|
|
108
|
+
private startHeartbeat;
|
|
109
|
+
private stopHeartbeat;
|
|
110
|
+
}
|