vigthoria-cli 1.6.16 → 1.6.18
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/auth.js +5 -5
- package/dist/commands/bridge.js +2 -2
- package/dist/commands/chat.d.ts +1 -0
- package/dist/commands/chat.js +102 -12
- package/dist/commands/deploy.js +9 -9
- package/dist/commands/edit.js +3 -3
- package/dist/commands/explain.js +2 -2
- package/dist/commands/generate.js +2 -2
- package/dist/commands/repo.js +10 -10
- package/dist/commands/review.js +2 -2
- package/dist/index.js +6 -1
- package/dist/utils/api.d.ts +5 -0
- package/dist/utils/api.js +172 -33
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.js +10 -0
- package/dist/utils/tools.d.ts +22 -0
- package/dist/utils/tools.js +246 -24
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -8,8 +8,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.AuthCommand = void 0;
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
-
const ora_1 = __importDefault(require("ora"));
|
|
12
11
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
13
13
|
const api_js_1 = require("../utils/api.js");
|
|
14
14
|
class AuthCommand {
|
|
15
15
|
config;
|
|
@@ -73,7 +73,7 @@ class AuthCommand {
|
|
|
73
73
|
validate: (input) => input.length >= 6 || 'Password must be at least 6 characters',
|
|
74
74
|
},
|
|
75
75
|
]);
|
|
76
|
-
const spinner = (0,
|
|
76
|
+
const spinner = (0, logger_js_1.createSpinner)('Logging in...').start();
|
|
77
77
|
const success = await this.api.login(credentials.email, credentials.password);
|
|
78
78
|
spinner.stop();
|
|
79
79
|
if (success) {
|
|
@@ -96,7 +96,7 @@ class AuthCommand {
|
|
|
96
96
|
await this.loginWithToken(token);
|
|
97
97
|
}
|
|
98
98
|
async loginWithToken(token) {
|
|
99
|
-
const spinner = (0,
|
|
99
|
+
const spinner = (0, logger_js_1.createSpinner)('Validating token...').start();
|
|
100
100
|
const success = await this.api.loginWithToken(token);
|
|
101
101
|
spinner.stop();
|
|
102
102
|
if (success) {
|
|
@@ -173,7 +173,7 @@ class AuthCommand {
|
|
|
173
173
|
});
|
|
174
174
|
console.log();
|
|
175
175
|
// API status
|
|
176
|
-
const spinner = (0,
|
|
176
|
+
const spinner = (0, logger_js_1.createSpinner)('Checking API status...').start();
|
|
177
177
|
const apiStatus = await this.api.getHealthStatus();
|
|
178
178
|
spinner.stop();
|
|
179
179
|
console.log(chalk_1.default.white('API Status:'));
|
|
@@ -198,7 +198,7 @@ class AuthCommand {
|
|
|
198
198
|
else {
|
|
199
199
|
console.log(chalk_1.default.gray(' Self-hosted Models: ') + chalk_1.default.gray('disabled'));
|
|
200
200
|
}
|
|
201
|
-
const capabilitySpinner = (0,
|
|
201
|
+
const capabilitySpinner = (0, logger_js_1.createSpinner)('Checking live capability truth...').start();
|
|
202
202
|
const capabilityStatus = await this.api.getCapabilityTruthStatus({
|
|
203
203
|
workspacePath: process.cwd(),
|
|
204
204
|
projectPath: process.cwd(),
|
package/dist/commands/bridge.js
CHANGED
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.BridgeCommand = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const
|
|
8
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
9
9
|
const api_js_1 = require("../utils/api.js");
|
|
10
10
|
class BridgeCommand {
|
|
11
11
|
api;
|
|
@@ -13,7 +13,7 @@ class BridgeCommand {
|
|
|
13
13
|
this.api = new api_js_1.APIClient(config, logger);
|
|
14
14
|
}
|
|
15
15
|
async status() {
|
|
16
|
-
const spinner = (0,
|
|
16
|
+
const spinner = (0, logger_js_1.createSpinner)('Checking DevTools Bridge...').start();
|
|
17
17
|
const bridge = await this.api.getDevtoolsBridgeStatus();
|
|
18
18
|
spinner.stop();
|
|
19
19
|
console.log();
|
package/dist/commands/chat.d.ts
CHANGED
package/dist/commands/chat.js
CHANGED
|
@@ -38,11 +38,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.ChatCommand = void 0;
|
|
40
40
|
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
-
const ora_1 = __importDefault(require("ora"));
|
|
42
41
|
const fs = __importStar(require("fs"));
|
|
43
42
|
const os = __importStar(require("os"));
|
|
44
43
|
const path = __importStar(require("path"));
|
|
45
44
|
const readline = __importStar(require("readline"));
|
|
45
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
46
46
|
const api_js_1 = require("../utils/api.js");
|
|
47
47
|
const tools_js_1 = require("../utils/tools.js");
|
|
48
48
|
const session_js_1 = require("../utils/session.js");
|
|
@@ -67,6 +67,8 @@ class ChatCommand {
|
|
|
67
67
|
currentModel = 'code';
|
|
68
68
|
modelExplicitlySelected = false;
|
|
69
69
|
autoApprove = false;
|
|
70
|
+
// Phase 5: Agent quality gate — track tool usage for evidence thresholds
|
|
71
|
+
agentToolEvidence = { discovery: 0, mutation: 0, searchFailed: 0 };
|
|
70
72
|
operatorMode = false;
|
|
71
73
|
workflowTarget = null;
|
|
72
74
|
savePlanToVigFlow = false;
|
|
@@ -195,6 +197,10 @@ class ChatCommand {
|
|
|
195
197
|
}
|
|
196
198
|
buildTaskShapingInstructions(prompt) {
|
|
197
199
|
const instructions = [];
|
|
200
|
+
// Platform-aware routing hints
|
|
201
|
+
if (os.platform() === 'win32') {
|
|
202
|
+
instructions.push('Platform: Windows. Use list_dir, glob, read_file, and the grep tool for searching.', 'The grep tool handles Windows automatically — do not use bash to call grep, findstr, or Select-String manually.', 'Do not use bash for Unix commands (cat, head, tail, awk, sed, wc).', 'Use read_file to inspect file contents instead of shell commands.', 'All file paths use forward slashes internally.');
|
|
203
|
+
}
|
|
198
204
|
if (this.isDiagnosticPrompt(prompt)) {
|
|
199
205
|
instructions.push('Diagnostic mode is active.', 'Treat this as a debugging task, not a generic code review or feature request.', 'Start with concrete evidence: logs, runtime errors, config, launch files, and exact symbol references.', 'If log files exist, inspect them before proposing fixes.', 'Do not claim a file, definition, asset, or symbol is missing until you verify that with tools.', 'If a prior diagnosis mentioned a missing symbol or YAML entry, re-check the actual files before repeating it.', 'Prefer grep plus read_file around the exact references involved in the failure.', 'Separate your reasoning into: Evidence, Confirmed Cause, and Remaining Hypotheses.', 'Do not suggest speculative fixes when the current evidence contradicts them.');
|
|
200
206
|
}
|
|
@@ -537,7 +543,7 @@ class ChatCommand {
|
|
|
537
543
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
538
544
|
const resolvedWorkflow = await this.api.resolveVigFlowWorkflow(selector);
|
|
539
545
|
const invocationMode = this.operatorMode ? 'operator' : this.agentMode ? 'agent' : 'chat';
|
|
540
|
-
const spinner = this.jsonOutput ? null : (0,
|
|
546
|
+
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: `Running workflow ${resolvedWorkflow.name}...`, spinner: 'clock' }).start();
|
|
541
547
|
try {
|
|
542
548
|
const execution = await this.api.runVigFlowWorkflow(resolvedWorkflow.id, {
|
|
543
549
|
data: {
|
|
@@ -606,7 +612,7 @@ class ChatCommand {
|
|
|
606
612
|
return;
|
|
607
613
|
}
|
|
608
614
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
609
|
-
const spinner = this.jsonOutput ? null : (0,
|
|
615
|
+
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
|
|
610
616
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
611
617
|
const workflowType = this.isDiagnosticPrompt(prompt) ? 'analysis_only' : 'full_autonomy';
|
|
612
618
|
try {
|
|
@@ -657,10 +663,11 @@ class ChatCommand {
|
|
|
657
663
|
async runSimplePrompt(prompt) {
|
|
658
664
|
this.lastActionableUserInput = prompt;
|
|
659
665
|
this.messages.push({ role: 'user', content: this.buildExecutionPrompt(prompt) });
|
|
660
|
-
const spinner = (0,
|
|
666
|
+
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking...', spinner: 'clock' }).start();
|
|
661
667
|
try {
|
|
662
668
|
const response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
|
|
663
|
-
spinner
|
|
669
|
+
if (spinner)
|
|
670
|
+
spinner.stop();
|
|
664
671
|
const finalText = response.message.trim();
|
|
665
672
|
if (finalText) {
|
|
666
673
|
console.log(finalText);
|
|
@@ -669,7 +676,8 @@ class ChatCommand {
|
|
|
669
676
|
this.saveSession();
|
|
670
677
|
}
|
|
671
678
|
catch (error) {
|
|
672
|
-
spinner
|
|
679
|
+
if (spinner)
|
|
680
|
+
spinner.fail('Failed to get response');
|
|
673
681
|
this.logger.error(error.message);
|
|
674
682
|
}
|
|
675
683
|
}
|
|
@@ -707,21 +715,37 @@ class ChatCommand {
|
|
|
707
715
|
}
|
|
708
716
|
this.lastActionableUserInput = prompt;
|
|
709
717
|
this.directToolContinuationCount = 0;
|
|
718
|
+
this.agentToolEvidence = { discovery: 0, mutation: 0, searchFailed: 0 };
|
|
710
719
|
this.tools.clearSessionApprovals();
|
|
711
720
|
this.ensureAgentSystemPrompt();
|
|
712
721
|
this.messages.push({ role: 'user', content: this.buildScopedUserPrompt(prompt) });
|
|
713
722
|
this.saveSession();
|
|
714
723
|
const maxTurns = 10;
|
|
715
724
|
for (let turn = 0; turn < maxTurns; turn += 1) {
|
|
716
|
-
const spinner = (0,
|
|
725
|
+
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: turn === 0 ? 'Planning...' : 'Continuing...', spinner: 'clock' }).start();
|
|
717
726
|
try {
|
|
718
727
|
const response = await this.api.chat(this.getMessagesForModel(), this.currentModel);
|
|
719
|
-
spinner
|
|
728
|
+
if (spinner)
|
|
729
|
+
spinner.stop();
|
|
720
730
|
const assistantMessage = response.message || '';
|
|
721
731
|
this.messages.push({ role: 'assistant', content: assistantMessage });
|
|
722
732
|
const toolCalls = this.extractToolCalls(assistantMessage);
|
|
723
733
|
const visibleText = this.stripToolPayloads(assistantMessage).trim();
|
|
724
734
|
if (toolCalls.length === 0) {
|
|
735
|
+
// Phase 5: Quality gate — if the agent tries to conclude on the first
|
|
736
|
+
// turn without any discovery, push it to gather evidence first.
|
|
737
|
+
if (turn === 0 && this.agentToolEvidence.discovery === 0 && this.isDiagnosticPrompt(prompt)) {
|
|
738
|
+
this.messages.push({
|
|
739
|
+
role: 'system',
|
|
740
|
+
content: [
|
|
741
|
+
'Quality gate: you concluded without using any discovery tools (list_dir, glob, read_file, grep).',
|
|
742
|
+
'Before answering a diagnostic or audit question, you MUST inspect the project with tools.',
|
|
743
|
+
'Use list_dir and read_file to gather concrete evidence, then provide your answer.',
|
|
744
|
+
].join('\n'),
|
|
745
|
+
});
|
|
746
|
+
this.directToolContinuationCount += 1;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
725
749
|
const finalContent = this.resolveDirectModeCompletion(prompt, visibleText);
|
|
726
750
|
if (this.jsonOutput) {
|
|
727
751
|
console.log(JSON.stringify({
|
|
@@ -750,7 +774,8 @@ class ChatCommand {
|
|
|
750
774
|
this.saveSession();
|
|
751
775
|
}
|
|
752
776
|
catch (error) {
|
|
753
|
-
spinner
|
|
777
|
+
if (spinner)
|
|
778
|
+
spinner.fail('Agent request failed');
|
|
754
779
|
if (this.jsonOutput) {
|
|
755
780
|
process.exitCode = 1;
|
|
756
781
|
console.log(JSON.stringify({
|
|
@@ -910,7 +935,7 @@ class ChatCommand {
|
|
|
910
935
|
this.v3IterationCount = 0;
|
|
911
936
|
this.v3ToolCallCount = 0;
|
|
912
937
|
this.v3LastActivity = Date.now();
|
|
913
|
-
const spinner = this.jsonOutput ? null : (0,
|
|
938
|
+
const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({
|
|
914
939
|
text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
|
|
915
940
|
spinner: 'clock',
|
|
916
941
|
}).start();
|
|
@@ -971,6 +996,18 @@ class ChatCommand {
|
|
|
971
996
|
}
|
|
972
997
|
this.logger.error(errorMessage);
|
|
973
998
|
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
999
|
+
if (this.jsonOutput) {
|
|
1000
|
+
process.exitCode = 1;
|
|
1001
|
+
console.log(JSON.stringify({
|
|
1002
|
+
success: false,
|
|
1003
|
+
mode: 'agent',
|
|
1004
|
+
model: routingPolicy.selectedModel,
|
|
1005
|
+
partial: false,
|
|
1006
|
+
content: '',
|
|
1007
|
+
error: errorMessage,
|
|
1008
|
+
metadata: { executionPath: 'v3-agent', previewGate },
|
|
1009
|
+
}, null, 2));
|
|
1010
|
+
}
|
|
974
1011
|
return true;
|
|
975
1012
|
}
|
|
976
1013
|
if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
|
|
@@ -1028,6 +1065,18 @@ class ChatCommand {
|
|
|
1028
1065
|
const errorMessage = `Agent mode requires the V3 workflow and will not fall back to the legacy CLI loop. ${error.message}`;
|
|
1029
1066
|
this.logger.error(errorMessage);
|
|
1030
1067
|
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
1068
|
+
if (this.jsonOutput) {
|
|
1069
|
+
process.exitCode = 1;
|
|
1070
|
+
console.log(JSON.stringify({
|
|
1071
|
+
success: false,
|
|
1072
|
+
mode: 'agent',
|
|
1073
|
+
model: routingPolicy.selectedModel,
|
|
1074
|
+
partial: false,
|
|
1075
|
+
content: '',
|
|
1076
|
+
error: errorMessage,
|
|
1077
|
+
metadata: { executionPath: 'v3-agent' },
|
|
1078
|
+
}, null, 2));
|
|
1079
|
+
}
|
|
1031
1080
|
return true;
|
|
1032
1081
|
}
|
|
1033
1082
|
}
|
|
@@ -1437,10 +1486,20 @@ class ChatCommand {
|
|
|
1437
1486
|
}
|
|
1438
1487
|
buildContinuationPrompt() {
|
|
1439
1488
|
const diagnosticMode = this.isDiagnosticPrompt(this.lastActionableUserInput);
|
|
1489
|
+
const { discovery, mutation, searchFailed } = this.agentToolEvidence;
|
|
1490
|
+
const evidenceLines = [];
|
|
1491
|
+
if (discovery < 2) {
|
|
1492
|
+
evidenceLines.push(`Quality gate: only ${discovery} discovery tool(s) used so far (list_dir, glob, read_file, grep). Use at least 2 before concluding.`);
|
|
1493
|
+
}
|
|
1494
|
+
if (searchFailed > 0) {
|
|
1495
|
+
evidenceLines.push(`Warning: ${searchFailed} search tool call(s) failed. Do not treat failed searches as evidence that something is missing.`);
|
|
1496
|
+
}
|
|
1440
1497
|
return [
|
|
1441
1498
|
`Tool results received for direct mode step ${this.directToolContinuationCount + 1}.`,
|
|
1442
1499
|
`Original user request: ${this.lastActionableUserInput}`,
|
|
1443
1500
|
`Project root boundary: ${this.currentProjectPath}`,
|
|
1501
|
+
`Evidence collected: ${discovery} discovery, ${mutation} mutation, ${searchFailed} search failures.`,
|
|
1502
|
+
...evidenceLines,
|
|
1444
1503
|
'Do not declare success until the exact user question has been answered with tool-backed evidence.',
|
|
1445
1504
|
'If a user is asking which file is correct or most recent, keep inspecting until you can justify the answer from actual results.',
|
|
1446
1505
|
diagnosticMode ? 'Because this is a debugging task, prefer logs, runtime evidence, and exact symbol references over generic fixes.' : 'Keep working from concrete tool results.',
|
|
@@ -1604,16 +1663,47 @@ class ChatCommand {
|
|
|
1604
1663
|
if (!this.jsonOutput) {
|
|
1605
1664
|
console.log(chalk_1.default.cyan(`⚙ Executing: ${call.tool}`));
|
|
1606
1665
|
}
|
|
1607
|
-
|
|
1666
|
+
let result = await this.tools.execute(call);
|
|
1667
|
+
// Phase 2: If a search tool failed (search_failed), retry with alternate approach
|
|
1668
|
+
const searchStatus = result.metadata?.searchStatus;
|
|
1669
|
+
if (call.tool === 'grep' && searchStatus === 'search_failed') {
|
|
1670
|
+
if (!this.jsonOutput) {
|
|
1671
|
+
console.log(chalk_1.default.yellow(`⚠ Search backend failed, retrying with alternate method...`));
|
|
1672
|
+
}
|
|
1673
|
+
// Force Node-native fallback by re-executing with a note
|
|
1674
|
+
const fallbackResult = await this.tools.execute({
|
|
1675
|
+
tool: 'grep',
|
|
1676
|
+
args: { ...call.args, _fallback: 'node-native' },
|
|
1677
|
+
});
|
|
1678
|
+
if (fallbackResult.success || fallbackResult.metadata?.searchStatus !== 'search_failed') {
|
|
1679
|
+
result = fallbackResult;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1608
1682
|
const summary = this.formatToolResult(call, result);
|
|
1609
1683
|
if (!this.jsonOutput) {
|
|
1610
1684
|
console.log(result.success ? chalk_1.default.gray(summary) : chalk_1.default.red(summary));
|
|
1611
1685
|
}
|
|
1612
1686
|
this.messages.push({ role: 'system', content: summary });
|
|
1687
|
+
// Phase 5: Track tool evidence for quality gates
|
|
1688
|
+
const finalStatus = result.metadata?.searchStatus;
|
|
1689
|
+
if (finalStatus === 'search_failed') {
|
|
1690
|
+
this.agentToolEvidence.searchFailed += 1;
|
|
1691
|
+
}
|
|
1692
|
+
else if (/^(read_file|list_dir|glob|grep|git|repo|fetch_url)$/.test(call.tool)) {
|
|
1693
|
+
this.agentToolEvidence.discovery += 1;
|
|
1694
|
+
}
|
|
1695
|
+
else if (/^(write_file|edit_file|bash|ssh_exec)$/.test(call.tool)) {
|
|
1696
|
+
this.agentToolEvidence.mutation += 1;
|
|
1697
|
+
}
|
|
1613
1698
|
}
|
|
1614
1699
|
}
|
|
1615
1700
|
formatToolResult(call, result) {
|
|
1616
|
-
const parts = [`Tool ${call.tool} ${result.success ? 'succeeded' : '
|
|
1701
|
+
const parts = [`Tool ${call.tool} ${result.success ? 'succeeded' : 'FAILED'}.`];
|
|
1702
|
+
// Include search status classification for the agent to reason about
|
|
1703
|
+
const searchStatus = result.metadata?.searchStatus;
|
|
1704
|
+
if (searchStatus) {
|
|
1705
|
+
parts.push(`Search status: ${searchStatus}`);
|
|
1706
|
+
}
|
|
1617
1707
|
if (result.output) {
|
|
1618
1708
|
parts.push(`Output:\n${this.truncateText(result.output)}`);
|
|
1619
1709
|
}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -53,7 +53,7 @@ exports.DeployCommand = void 0;
|
|
|
53
53
|
const chalk_1 = __importDefault(require("chalk"));
|
|
54
54
|
const fs = __importStar(require("fs"));
|
|
55
55
|
const path = __importStar(require("path"));
|
|
56
|
-
const
|
|
56
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
57
57
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
58
58
|
class DeployCommand {
|
|
59
59
|
config;
|
|
@@ -127,7 +127,7 @@ class DeployCommand {
|
|
|
127
127
|
* Deploy to preview URL (free)
|
|
128
128
|
*/
|
|
129
129
|
async deployToPreview(projectPath) {
|
|
130
|
-
const spinner = (0,
|
|
130
|
+
const spinner = (0, logger_js_1.createSpinner)('Deploying to preview...').start();
|
|
131
131
|
try {
|
|
132
132
|
const projectDir = projectPath || process.cwd();
|
|
133
133
|
const projectInfo = this.detectProjectInfo(projectDir);
|
|
@@ -160,7 +160,7 @@ class DeployCommand {
|
|
|
160
160
|
* Deploy to Vigthoria subdomain
|
|
161
161
|
*/
|
|
162
162
|
async deployToSubdomain(subdomain, projectPath) {
|
|
163
|
-
const spinner = (0,
|
|
163
|
+
const spinner = (0, logger_js_1.createSpinner)(`Deploying to ${subdomain}.vigthoria.io...`).start();
|
|
164
164
|
try {
|
|
165
165
|
// Validate subdomain format
|
|
166
166
|
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(subdomain) || subdomain.length < 3) {
|
|
@@ -213,7 +213,7 @@ class DeployCommand {
|
|
|
213
213
|
* Deploy to custom domain
|
|
214
214
|
*/
|
|
215
215
|
async deployToCustomDomain(domain, projectPath) {
|
|
216
|
-
const spinner = (0,
|
|
216
|
+
const spinner = (0, logger_js_1.createSpinner)(`Setting up ${domain}...`).start();
|
|
217
217
|
try {
|
|
218
218
|
const projectDir = projectPath || process.cwd();
|
|
219
219
|
const projectInfo = this.detectProjectInfo(projectDir);
|
|
@@ -303,7 +303,7 @@ class DeployCommand {
|
|
|
303
303
|
* Show hosting plans
|
|
304
304
|
*/
|
|
305
305
|
async showPlans() {
|
|
306
|
-
const spinner = (0,
|
|
306
|
+
const spinner = (0, logger_js_1.createSpinner)('Fetching hosting plans...').start();
|
|
307
307
|
try {
|
|
308
308
|
const response = await fetch(`${this.apiBase}/api/hosting/plans`, {
|
|
309
309
|
headers: this.getAuthHeaders()
|
|
@@ -344,7 +344,7 @@ class DeployCommand {
|
|
|
344
344
|
*/
|
|
345
345
|
async list() {
|
|
346
346
|
this.requireAuth();
|
|
347
|
-
const spinner = (0,
|
|
347
|
+
const spinner = (0, logger_js_1.createSpinner)('Fetching deployments...').start();
|
|
348
348
|
try {
|
|
349
349
|
const response = await fetch(`${this.apiBase}/api/hosting/domains`, {
|
|
350
350
|
headers: this.getAuthHeaders()
|
|
@@ -384,7 +384,7 @@ class DeployCommand {
|
|
|
384
384
|
*/
|
|
385
385
|
async status(domain) {
|
|
386
386
|
this.requireAuth();
|
|
387
|
-
const spinner = (0,
|
|
387
|
+
const spinner = (0, logger_js_1.createSpinner)('Checking status...').start();
|
|
388
388
|
try {
|
|
389
389
|
const endpoint = domain
|
|
390
390
|
? `${this.apiBase}/api/hosting/domain/${encodeURIComponent(domain)}/status`
|
|
@@ -411,7 +411,7 @@ class DeployCommand {
|
|
|
411
411
|
*/
|
|
412
412
|
async verify(domain) {
|
|
413
413
|
this.requireAuth();
|
|
414
|
-
const spinner = (0,
|
|
414
|
+
const spinner = (0, logger_js_1.createSpinner)(`Verifying DNS for ${domain}...`).start();
|
|
415
415
|
try {
|
|
416
416
|
const response = await fetch(`${this.apiBase}/api/hosting/domain/verify`, {
|
|
417
417
|
method: 'POST',
|
|
@@ -454,7 +454,7 @@ class DeployCommand {
|
|
|
454
454
|
console.log(chalk_1.default.yellow('\n⚠️ Removal cancelled.\n'));
|
|
455
455
|
return;
|
|
456
456
|
}
|
|
457
|
-
const spinner = (0,
|
|
457
|
+
const spinner = (0, logger_js_1.createSpinner)(`Removing ${domain}...`).start();
|
|
458
458
|
try {
|
|
459
459
|
const response = await fetch(`${this.apiBase}/api/hosting/domain/${encodeURIComponent(domain)}`, {
|
|
460
460
|
method: 'DELETE',
|
package/dist/commands/edit.js
CHANGED
|
@@ -8,8 +8,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.EditCommand = void 0;
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
-
const ora_1 = __importDefault(require("ora"));
|
|
12
11
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
13
13
|
const api_js_1 = require("../utils/api.js");
|
|
14
14
|
const files_js_1 = require("../utils/files.js");
|
|
15
15
|
class EditCommand {
|
|
@@ -52,7 +52,7 @@ class EditCommand {
|
|
|
52
52
|
instruction = answer.instruction;
|
|
53
53
|
}
|
|
54
54
|
// Generate edit
|
|
55
|
-
const spinner = (0,
|
|
55
|
+
const spinner = (0, logger_js_1.createSpinner)({
|
|
56
56
|
text: 'Generating changes...',
|
|
57
57
|
spinner: 'dots',
|
|
58
58
|
}).start();
|
|
@@ -109,7 +109,7 @@ Return the complete modified code:`,
|
|
|
109
109
|
this.logger.section(`Fixing: ${file.relativePath}`);
|
|
110
110
|
console.log(chalk_1.default.gray(`Fix type: ${options.type} | Language: ${file.language}`));
|
|
111
111
|
console.log();
|
|
112
|
-
const spinner = (0,
|
|
112
|
+
const spinner = (0, logger_js_1.createSpinner)({
|
|
113
113
|
text: `Analyzing for ${options.type} issues...`,
|
|
114
114
|
spinner: 'dots',
|
|
115
115
|
}).start();
|
package/dist/commands/explain.js
CHANGED
|
@@ -8,9 +8,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.ExplainCommand = void 0;
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
-
const ora_1 = __importDefault(require("ora"));
|
|
12
11
|
const marked_1 = require("marked");
|
|
13
12
|
const marked_terminal_1 = require("marked-terminal");
|
|
13
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
14
14
|
const api_js_1 = require("../utils/api.js");
|
|
15
15
|
const files_js_1 = require("../utils/files.js");
|
|
16
16
|
class ExplainCommand {
|
|
@@ -61,7 +61,7 @@ class ExplainCommand {
|
|
|
61
61
|
});
|
|
62
62
|
console.log(chalk_1.default.gray('─'.repeat(60)));
|
|
63
63
|
console.log();
|
|
64
|
-
const spinner = (0,
|
|
64
|
+
const spinner = (0, logger_js_1.createSpinner)({
|
|
65
65
|
text: 'Analyzing code...',
|
|
66
66
|
spinner: 'dots',
|
|
67
67
|
}).start();
|
|
@@ -10,8 +10,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.GenerateCommand = void 0;
|
|
12
12
|
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
-
const ora_1 = __importDefault(require("ora"));
|
|
14
13
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
14
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
15
15
|
const api_js_1 = require("../utils/api.js");
|
|
16
16
|
const files_js_1 = require("../utils/files.js");
|
|
17
17
|
class GenerateCommand {
|
|
@@ -40,7 +40,7 @@ class GenerateCommand {
|
|
|
40
40
|
console.log(chalk_1.default.cyan('Pro Mode: Planning → Generating → Quality Check'));
|
|
41
41
|
}
|
|
42
42
|
console.log();
|
|
43
|
-
const spinner = (0,
|
|
43
|
+
const spinner = (0, logger_js_1.createSpinner)({
|
|
44
44
|
text: proMode ? 'Phase 1: Planning project structure...' : 'Generating code...',
|
|
45
45
|
spinner: 'dots',
|
|
46
46
|
}).start();
|
package/dist/commands/repo.js
CHANGED
|
@@ -54,7 +54,7 @@ exports.RepoCommand = void 0;
|
|
|
54
54
|
const chalk_1 = __importDefault(require("chalk"));
|
|
55
55
|
const fs = __importStar(require("fs"));
|
|
56
56
|
const path = __importStar(require("path"));
|
|
57
|
-
const
|
|
57
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
58
58
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
59
59
|
const archiver_1 = __importDefault(require("archiver"));
|
|
60
60
|
const fs_1 = require("fs");
|
|
@@ -313,7 +313,7 @@ class RepoCommand {
|
|
|
313
313
|
console.log(chalk_1.default.red(`\n❌ Path does not exist: ${projectPath}\n`));
|
|
314
314
|
return;
|
|
315
315
|
}
|
|
316
|
-
const spinner = (0,
|
|
316
|
+
const spinner = (0, logger_js_1.createSpinner)('Analyzing project...').start();
|
|
317
317
|
try {
|
|
318
318
|
const projectInfo = this.detectProjectInfo(projectPath);
|
|
319
319
|
spinner.succeed(`Project detected: ${chalk_1.default.cyan(projectInfo.name)}`);
|
|
@@ -363,7 +363,7 @@ class RepoCommand {
|
|
|
363
363
|
console.log(chalk_1.default.yellow('\n⚠️ Push cancelled.\n'));
|
|
364
364
|
return;
|
|
365
365
|
}
|
|
366
|
-
const uploadSpinner = (0,
|
|
366
|
+
const uploadSpinner = (0, logger_js_1.createSpinner)('Preparing project files...').start();
|
|
367
367
|
const files = this.collectProjectFiles(projectPath);
|
|
368
368
|
if (files.length === 0) {
|
|
369
369
|
throw new Error('No readable text files found to push');
|
|
@@ -409,7 +409,7 @@ class RepoCommand {
|
|
|
409
409
|
*/
|
|
410
410
|
async pull(projectName, options = {}) {
|
|
411
411
|
this.requireAuth();
|
|
412
|
-
const spinner = (0,
|
|
412
|
+
const spinner = (0, logger_js_1.createSpinner)(`Fetching project: ${projectName}...`).start();
|
|
413
413
|
try {
|
|
414
414
|
const repo = await this.resolveRepoByName(projectName);
|
|
415
415
|
const response = await this.repoFetch('/api/repo/pull', {
|
|
@@ -437,7 +437,7 @@ class RepoCommand {
|
|
|
437
437
|
return;
|
|
438
438
|
}
|
|
439
439
|
}
|
|
440
|
-
const downloadSpinner = (0,
|
|
440
|
+
const downloadSpinner = (0, logger_js_1.createSpinner)('Downloading project files...').start();
|
|
441
441
|
// Create output directory
|
|
442
442
|
fs.mkdirSync(outputPath, { recursive: true });
|
|
443
443
|
// If we have a download URL, fetch and extract
|
|
@@ -496,7 +496,7 @@ class RepoCommand {
|
|
|
496
496
|
*/
|
|
497
497
|
async list(options = {}) {
|
|
498
498
|
this.requireAuth();
|
|
499
|
-
const spinner = (0,
|
|
499
|
+
const spinner = (0, logger_js_1.createSpinner)('Fetching your projects...').start();
|
|
500
500
|
try {
|
|
501
501
|
const repos = await this.getMyRepos();
|
|
502
502
|
const filteredRepos = options.visibility
|
|
@@ -596,7 +596,7 @@ class RepoCommand {
|
|
|
596
596
|
this.requireAuth();
|
|
597
597
|
const projectPath = process.cwd();
|
|
598
598
|
const projectInfo = this.detectProjectInfo(projectPath);
|
|
599
|
-
const spinner = (0,
|
|
599
|
+
const spinner = (0, logger_js_1.createSpinner)('Checking sync status...').start();
|
|
600
600
|
try {
|
|
601
601
|
const response = await fetch(`${this.apiBase}/api/repo/status/${encodeURIComponent(projectInfo.name)}`, {
|
|
602
602
|
method: 'GET',
|
|
@@ -644,7 +644,7 @@ class RepoCommand {
|
|
|
644
644
|
*/
|
|
645
645
|
async share(projectName, options = {}) {
|
|
646
646
|
this.requireAuth();
|
|
647
|
-
const spinner = (0,
|
|
647
|
+
const spinner = (0, logger_js_1.createSpinner)('Generating share link...').start();
|
|
648
648
|
try {
|
|
649
649
|
const response = await fetch(`${this.apiBase}/api/repo/share`, {
|
|
650
650
|
method: 'POST',
|
|
@@ -686,7 +686,7 @@ class RepoCommand {
|
|
|
686
686
|
console.log(chalk_1.default.yellow('\n⚠️ Delete cancelled.\n'));
|
|
687
687
|
return;
|
|
688
688
|
}
|
|
689
|
-
const spinner = (0,
|
|
689
|
+
const spinner = (0, logger_js_1.createSpinner)('Deleting project...').start();
|
|
690
690
|
try {
|
|
691
691
|
const response = await fetch(`${this.apiBase}/api/repo/projects/${encodeURIComponent(projectName)}`, {
|
|
692
692
|
method: 'DELETE',
|
|
@@ -712,7 +712,7 @@ class RepoCommand {
|
|
|
712
712
|
// Parse project URL or name
|
|
713
713
|
const projectIdMatch = projectUrl.match(/preview\/(\d+)/);
|
|
714
714
|
const projectId = projectIdMatch ? projectIdMatch[1] : projectUrl;
|
|
715
|
-
const spinner = (0,
|
|
715
|
+
const spinner = (0, logger_js_1.createSpinner)(`Cloning project...`).start();
|
|
716
716
|
try {
|
|
717
717
|
const response = await fetch(`${this.apiBase}/api/repo/clone/${projectId}`, {
|
|
718
718
|
method: 'GET',
|
package/dist/commands/review.js
CHANGED
|
@@ -8,9 +8,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.ReviewCommand = void 0;
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
-
const ora_1 = __importDefault(require("ora"));
|
|
12
11
|
const marked_1 = require("marked");
|
|
13
12
|
const marked_terminal_1 = require("marked-terminal");
|
|
13
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
14
14
|
const api_js_1 = require("../utils/api.js");
|
|
15
15
|
const files_js_1 = require("../utils/files.js");
|
|
16
16
|
class ReviewCommand {
|
|
@@ -42,7 +42,7 @@ class ReviewCommand {
|
|
|
42
42
|
this.logger.section(`Reviewing: ${file.relativePath}`);
|
|
43
43
|
console.log(chalk_1.default.gray(`Language: ${file.language} | Lines: ${file.lines}`));
|
|
44
44
|
console.log();
|
|
45
|
-
const spinner = (0,
|
|
45
|
+
const spinner = (0, logger_js_1.createSpinner)({
|
|
46
46
|
text: 'Analyzing code quality...',
|
|
47
47
|
spinner: 'dots',
|
|
48
48
|
}).start();
|
package/dist/index.js
CHANGED
|
@@ -346,12 +346,17 @@ async function main() {
|
|
|
346
346
|
});
|
|
347
347
|
// Fix command - Fix code issues
|
|
348
348
|
program
|
|
349
|
-
.command('fix
|
|
349
|
+
.command('fix [file]')
|
|
350
350
|
.alias('f')
|
|
351
351
|
.description('Fix issues in a file')
|
|
352
352
|
.option('-t, --type <type>', 'Fix type (bugs, style, security, performance)', 'bugs')
|
|
353
353
|
.option('--apply', 'Automatically apply fixes', false)
|
|
354
354
|
.action(async (file, options) => {
|
|
355
|
+
if (!file) {
|
|
356
|
+
logger.error('Usage: vigthoria fix <file> [--type bugs|style|security|performance] [--apply]');
|
|
357
|
+
process.exitCode = 1;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
355
360
|
const edit = new edit_js_1.EditCommand(config, logger);
|
|
356
361
|
await edit.fix(file, options);
|
|
357
362
|
});
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -237,6 +237,11 @@ export declare class APIClient {
|
|
|
237
237
|
private isLikelyWindowsPath;
|
|
238
238
|
private resolveServerBindableWorkspacePath;
|
|
239
239
|
private buildLocalWorkspaceSummary;
|
|
240
|
+
/**
|
|
241
|
+
* Collect text file contents from the workspace for V3 agent hydration.
|
|
242
|
+
* Budget: up to ~2 MB total, per-file cap 200 KB, skip binary extensions.
|
|
243
|
+
*/
|
|
244
|
+
collectWorkspaceFileContents(rootPath: string, filePaths: string[]): Record<string, string>;
|
|
240
245
|
hasAgentWorkspaceOutput(context?: Record<string, any>): boolean;
|
|
241
246
|
getAgentWorkspaceSnapshot(rootPath: string): {
|
|
242
247
|
fileCount: number;
|
package/dist/utils/api.js
CHANGED
|
@@ -593,26 +593,37 @@ class APIClient {
|
|
|
593
593
|
error: artifacts.error || 'Frontend preview gate could not collect frontend artifacts.',
|
|
594
594
|
};
|
|
595
595
|
}
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
const
|
|
596
|
+
// Cap artifact sizes to prevent 413 Payload Too Large
|
|
597
|
+
const PREVIEW_MAX_ARTIFACT_BYTES = 500 * 1024;
|
|
598
|
+
const html = artifacts.html.slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
|
|
599
|
+
const css = (artifacts.css || '').slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
|
|
600
|
+
const js = (artifacts.js || '').slice(0, PREVIEW_MAX_ARTIFACT_BYTES);
|
|
599
601
|
const errors = [];
|
|
600
602
|
for (const baseUrl of this.getTemplateServiceBaseUrls()) {
|
|
601
603
|
try {
|
|
604
|
+
const proofPayload = JSON.stringify({
|
|
605
|
+
vision: String(context.rawPrompt || message || '').slice(0, 2000),
|
|
606
|
+
html,
|
|
607
|
+
css,
|
|
608
|
+
js,
|
|
609
|
+
entryPath: artifacts.htmlPath,
|
|
610
|
+
workspaceName: path_1.default.basename(rootPath),
|
|
611
|
+
});
|
|
612
|
+
// Skip preview proof if payload is still too large (> 4 MB)
|
|
613
|
+
if (Buffer.byteLength(proofPayload, 'utf8') > 4 * 1024 * 1024) {
|
|
614
|
+
return {
|
|
615
|
+
required: true,
|
|
616
|
+
passed: true,
|
|
617
|
+
error: 'Preview proof skipped: payload exceeds size limit.',
|
|
618
|
+
};
|
|
619
|
+
}
|
|
602
620
|
const response = await fetch(`${baseUrl}/preview-proof`, {
|
|
603
621
|
method: 'POST',
|
|
604
622
|
headers: {
|
|
605
623
|
'Content-Type': 'application/json',
|
|
606
624
|
Accept: 'application/json',
|
|
607
625
|
},
|
|
608
|
-
body:
|
|
609
|
-
vision: String(context.rawPrompt || message || ''),
|
|
610
|
-
html,
|
|
611
|
-
css,
|
|
612
|
-
js,
|
|
613
|
-
entryPath: artifacts.htmlPath,
|
|
614
|
-
workspaceName: path_1.default.basename(rootPath),
|
|
615
|
-
}),
|
|
626
|
+
body: proofPayload,
|
|
616
627
|
});
|
|
617
628
|
if (!response.ok) {
|
|
618
629
|
const errorText = await response.text().catch(() => '');
|
|
@@ -1553,6 +1564,9 @@ menu {
|
|
|
1553
1564
|
if (fs_1.default.existsSync(readmePath)) {
|
|
1554
1565
|
summary.readmeExcerpt = fs_1.default.readFileSync(readmePath, 'utf8').slice(0, 2500);
|
|
1555
1566
|
}
|
|
1567
|
+
// Hydrate workspace: include actual file contents so the V3 server
|
|
1568
|
+
// can populate the remote workspace before the agent starts.
|
|
1569
|
+
summary.workspaceFiles = this.collectWorkspaceFileContents(rootPath, snapshot.paths);
|
|
1556
1570
|
return summary;
|
|
1557
1571
|
}
|
|
1558
1572
|
catch (error) {
|
|
@@ -1560,6 +1574,52 @@ menu {
|
|
|
1560
1574
|
return null;
|
|
1561
1575
|
}
|
|
1562
1576
|
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Collect text file contents from the workspace for V3 agent hydration.
|
|
1579
|
+
* Budget: up to ~2 MB total, per-file cap 200 KB, skip binary extensions.
|
|
1580
|
+
*/
|
|
1581
|
+
collectWorkspaceFileContents(rootPath, filePaths) {
|
|
1582
|
+
const MAX_TOTAL_BYTES = 2 * 1024 * 1024;
|
|
1583
|
+
const MAX_FILE_BYTES = 200 * 1024;
|
|
1584
|
+
const BINARY_EXTENSIONS = new Set([
|
|
1585
|
+
'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.webp', '.avif',
|
|
1586
|
+
'.mp3', '.mp4', '.wav', '.ogg', '.webm', '.flac', '.aac',
|
|
1587
|
+
'.zip', '.gz', '.tar', '.rar', '.7z', '.bz2',
|
|
1588
|
+
'.exe', '.dll', '.so', '.dylib', '.bin', '.dat',
|
|
1589
|
+
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
1590
|
+
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
1591
|
+
'.db', '.sqlite', '.sqlite3',
|
|
1592
|
+
'.pyc', '.pyo', '.class', '.o', '.obj',
|
|
1593
|
+
'.DS_Store', '.lock',
|
|
1594
|
+
]);
|
|
1595
|
+
const result = {};
|
|
1596
|
+
let totalBytes = 0;
|
|
1597
|
+
for (const relativePath of filePaths) {
|
|
1598
|
+
if (totalBytes >= MAX_TOTAL_BYTES)
|
|
1599
|
+
break;
|
|
1600
|
+
const ext = path_1.default.extname(relativePath).toLowerCase();
|
|
1601
|
+
if (BINARY_EXTENSIONS.has(ext))
|
|
1602
|
+
continue;
|
|
1603
|
+
if (/(^|[\/\\])\.(git|hg)([\/\\]|$)/.test(relativePath))
|
|
1604
|
+
continue;
|
|
1605
|
+
const absolutePath = path_1.default.join(rootPath, relativePath);
|
|
1606
|
+
try {
|
|
1607
|
+
const stat = fs_1.default.statSync(absolutePath);
|
|
1608
|
+
if (!stat.isFile() || stat.size > MAX_FILE_BYTES || stat.size === 0)
|
|
1609
|
+
continue;
|
|
1610
|
+
const content = fs_1.default.readFileSync(absolutePath, 'utf8');
|
|
1611
|
+
// Skip likely binary content (high ratio of non-printable chars)
|
|
1612
|
+
if (/[\x00-\x08\x0e-\x1f]/.test(content.slice(0, 512)))
|
|
1613
|
+
continue;
|
|
1614
|
+
result[relativePath] = content;
|
|
1615
|
+
totalBytes += Buffer.byteLength(content, 'utf8');
|
|
1616
|
+
}
|
|
1617
|
+
catch {
|
|
1618
|
+
// Skip unreadable files
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
return result;
|
|
1622
|
+
}
|
|
1563
1623
|
hasAgentWorkspaceOutput(context = {}) {
|
|
1564
1624
|
try {
|
|
1565
1625
|
const root = this.resolveAgentTargetPath(context);
|
|
@@ -2231,6 +2291,30 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2231
2291
|
serverWorkspaceRoot = event.workspace_root.trim();
|
|
2232
2292
|
}
|
|
2233
2293
|
this.captureV3AgentStreamMutation(event, streamedFiles, serverWorkspaceRoot);
|
|
2294
|
+
// Empty workspace guard: if the remote agent lists its root
|
|
2295
|
+
// and finds nothing while our local workspace has files, the
|
|
2296
|
+
// workspace was not hydrated. Abort early with a clear error
|
|
2297
|
+
// instead of letting the agent spin on an empty directory.
|
|
2298
|
+
if (event.type === 'tool_result'
|
|
2299
|
+
&& event.name === 'list_directory'
|
|
2300
|
+
&& event.success === true
|
|
2301
|
+
&& serverWorkspaceRoot
|
|
2302
|
+
&& typeof event.output === 'string') {
|
|
2303
|
+
const listOutput = event.output.trim();
|
|
2304
|
+
const looksEmpty = listOutput === `[${serverWorkspaceRoot}]`
|
|
2305
|
+
|| listOutput === `[${serverWorkspaceRoot}/]`
|
|
2306
|
+
|| listOutput === `[${serverWorkspaceRoot}]\\n`
|
|
2307
|
+
|| /^\[\/tmp\/vig-remote-server-[^\]]+\]\s*$/.test(listOutput);
|
|
2308
|
+
if (looksEmpty) {
|
|
2309
|
+
const localPath = this.resolveAgentTargetPath(context);
|
|
2310
|
+
const localHasFiles = localPath && fs_1.default.existsSync(localPath) && this.hasAgentWorkspaceOutput({ ...context, projectPath: localPath, targetPath: localPath });
|
|
2311
|
+
if (localHasFiles) {
|
|
2312
|
+
throw new Error('Remote workspace is empty — the V3 server did not receive your project files. '
|
|
2313
|
+
+ 'Your local workspace has files but the remote agent sees an empty directory. '
|
|
2314
|
+
+ 'This is a workspace sync failure. Falling back to local agent loop.');
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2234
2318
|
if (typeof context.onStreamEvent === 'function') {
|
|
2235
2319
|
try {
|
|
2236
2320
|
context.onStreamEvent(event);
|
|
@@ -3096,32 +3180,87 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3096
3180
|
}
|
|
3097
3181
|
}
|
|
3098
3182
|
async getV3AgentHealth() {
|
|
3099
|
-
const
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
}
|
|
3105
|
-
|
|
3183
|
+
const baseUrl = this.getV3AgentBaseUrls()[0];
|
|
3184
|
+
// Try multiple health endpoint patterns — the V3 backend may expose
|
|
3185
|
+
// different paths depending on whether it's local (8030) or remote.
|
|
3186
|
+
const candidates = [
|
|
3187
|
+
`${baseUrl}/api/v3-agent/health`,
|
|
3188
|
+
`${baseUrl}/api/health`,
|
|
3189
|
+
`${baseUrl}/health`,
|
|
3190
|
+
];
|
|
3191
|
+
const headers = await this.getV3AgentHeaders();
|
|
3192
|
+
for (const endpoint of candidates) {
|
|
3193
|
+
try {
|
|
3194
|
+
const controller = new AbortController();
|
|
3195
|
+
const timer = setTimeout(() => controller.abort(), 8000);
|
|
3196
|
+
const response = await fetch(endpoint, {
|
|
3197
|
+
method: 'GET',
|
|
3198
|
+
headers,
|
|
3199
|
+
signal: controller.signal,
|
|
3200
|
+
});
|
|
3201
|
+
clearTimeout(timer);
|
|
3202
|
+
if (response.ok) {
|
|
3203
|
+
const data = await response.json().catch(() => ({}));
|
|
3204
|
+
return {
|
|
3205
|
+
name: 'V3 Agent',
|
|
3206
|
+
endpoint,
|
|
3207
|
+
ok: true,
|
|
3208
|
+
details: { health: data },
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
// 404 means this path doesn't exist — try next candidate
|
|
3212
|
+
if (response.status === 404)
|
|
3213
|
+
continue;
|
|
3214
|
+
// 405 Method Not Allowed — endpoint exists but rejects GET.
|
|
3215
|
+
// Treat as reachable since the run endpoint (POST) will work.
|
|
3216
|
+
if (response.status === 405) {
|
|
3217
|
+
return {
|
|
3218
|
+
name: 'V3 Agent',
|
|
3219
|
+
endpoint,
|
|
3220
|
+
ok: true,
|
|
3221
|
+
details: { health: { reachable: true, note: 'Health endpoint returned 405 but run endpoint is available' } },
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
3224
|
+
// Other error
|
|
3106
3225
|
throw new Error(`V3 health ${response.status}`);
|
|
3107
3226
|
}
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3227
|
+
catch (error) {
|
|
3228
|
+
if (error instanceof Error && error.message.startsWith('V3 health')) {
|
|
3229
|
+
return {
|
|
3230
|
+
name: 'V3 Agent',
|
|
3231
|
+
endpoint,
|
|
3232
|
+
ok: false,
|
|
3233
|
+
error: error.message,
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
// Network/timeout error — try next candidate
|
|
3237
|
+
}
|
|
3116
3238
|
}
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3239
|
+
// Last resort: probe the run endpoint with OPTIONS
|
|
3240
|
+
const runUrl = this.getV3AgentRunUrl(baseUrl);
|
|
3241
|
+
try {
|
|
3242
|
+
const controller = new AbortController();
|
|
3243
|
+
const timer = setTimeout(() => controller.abort(), 5000);
|
|
3244
|
+
const probe = await fetch(runUrl, { method: 'OPTIONS', headers, signal: controller.signal });
|
|
3245
|
+
clearTimeout(timer);
|
|
3246
|
+
if (probe.ok || probe.status === 204 || probe.status === 405) {
|
|
3247
|
+
return {
|
|
3248
|
+
name: 'V3 Agent',
|
|
3249
|
+
endpoint: runUrl,
|
|
3250
|
+
ok: true,
|
|
3251
|
+
details: { health: { reachable: true, method: 'OPTIONS' } },
|
|
3252
|
+
};
|
|
3253
|
+
}
|
|
3124
3254
|
}
|
|
3255
|
+
catch {
|
|
3256
|
+
// final attempt failed
|
|
3257
|
+
}
|
|
3258
|
+
return {
|
|
3259
|
+
name: 'V3 Agent',
|
|
3260
|
+
endpoint: candidates[0],
|
|
3261
|
+
ok: false,
|
|
3262
|
+
error: 'No V3 agent health endpoint responded.',
|
|
3263
|
+
};
|
|
3125
3264
|
}
|
|
3126
3265
|
async getHyperLoopHealth() {
|
|
3127
3266
|
const endpoint = process.env.VIGTHORIA_HYPERLOOP_URL || 'http://127.0.0.1:8020/api/hyperloop/health';
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Logger utility for Vigthoria CLI
|
|
3
3
|
*/
|
|
4
|
+
import { type Options as OraOptions, type Ora } from 'ora';
|
|
5
|
+
export type { Ora };
|
|
6
|
+
/**
|
|
7
|
+
* Create an ora spinner that writes to stderr so it never
|
|
8
|
+
* pollutes stdout JSON output or triggers PowerShell error styling.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createSpinner(textOrOpts: string | OraOptions): Ora;
|
|
4
11
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success';
|
|
5
12
|
export declare class Logger {
|
|
6
13
|
private verbose;
|
package/dist/utils/logger.js
CHANGED
|
@@ -7,7 +7,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
7
7
|
};
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.Logger = void 0;
|
|
10
|
+
exports.createSpinner = createSpinner;
|
|
10
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const ora_1 = __importDefault(require("ora"));
|
|
13
|
+
/**
|
|
14
|
+
* Create an ora spinner that writes to stderr so it never
|
|
15
|
+
* pollutes stdout JSON output or triggers PowerShell error styling.
|
|
16
|
+
*/
|
|
17
|
+
function createSpinner(textOrOpts) {
|
|
18
|
+
const opts = typeof textOrOpts === 'string' ? { text: textOrOpts } : textOrOpts;
|
|
19
|
+
return (0, ora_1.default)({ ...opts, stream: process.stderr });
|
|
20
|
+
}
|
|
11
21
|
class Logger {
|
|
12
22
|
verbose = false;
|
|
13
23
|
setVerbose(verbose) {
|
package/dist/utils/tools.d.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { Logger } from './logger.js';
|
|
17
17
|
export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
18
|
+
export type SearchStatus = 'search_matches_found' | 'search_no_matches' | 'search_failed';
|
|
18
19
|
export interface ToolResult {
|
|
19
20
|
success: boolean;
|
|
20
21
|
output?: string;
|
|
@@ -23,6 +24,7 @@ export interface ToolResult {
|
|
|
23
24
|
canRetry?: boolean;
|
|
24
25
|
undoable?: boolean;
|
|
25
26
|
metadata?: {
|
|
27
|
+
searchStatus?: SearchStatus;
|
|
26
28
|
[key: string]: any;
|
|
27
29
|
};
|
|
28
30
|
}
|
|
@@ -145,6 +147,26 @@ export declare class AgenticTools {
|
|
|
145
147
|
*/
|
|
146
148
|
private bash;
|
|
147
149
|
private grep;
|
|
150
|
+
/**
|
|
151
|
+
* Windows grep: rg > Select-String > Node-native
|
|
152
|
+
*/
|
|
153
|
+
private grepWindows;
|
|
154
|
+
/**
|
|
155
|
+
* Unix grep: rg > system grep (BSD/GNU)
|
|
156
|
+
*/
|
|
157
|
+
private grepUnix;
|
|
158
|
+
/**
|
|
159
|
+
* Search using ripgrep (rg) — works on all platforms
|
|
160
|
+
*/
|
|
161
|
+
private grepWithRg;
|
|
162
|
+
/**
|
|
163
|
+
* Search using PowerShell Select-String (Windows)
|
|
164
|
+
*/
|
|
165
|
+
private grepWithSelectString;
|
|
166
|
+
/**
|
|
167
|
+
* Pure Node.js recursive file search — reliable on all platforms, no external deps
|
|
168
|
+
*/
|
|
169
|
+
private grepNodeNative;
|
|
148
170
|
private listDir;
|
|
149
171
|
private glob;
|
|
150
172
|
private git;
|
package/dist/utils/tools.js
CHANGED
|
@@ -995,48 +995,270 @@ class AgenticTools {
|
|
|
995
995
|
}
|
|
996
996
|
grep(args) {
|
|
997
997
|
const searchPath = args.path ? this.resolvePath(args.path) : this.cwd;
|
|
998
|
-
const
|
|
999
|
-
const
|
|
1000
|
-
//
|
|
1001
|
-
|
|
998
|
+
const osModule = require('os');
|
|
999
|
+
const platform = osModule.platform();
|
|
1000
|
+
// If forced fallback, go directly to Node-native
|
|
1001
|
+
if (args._fallback === 'node-native') {
|
|
1002
|
+
return this.grepNodeNative(args, searchPath);
|
|
1003
|
+
}
|
|
1004
|
+
// Try external search tools first (rg > grep/Select-String), fall back to Node-native
|
|
1005
|
+
if (platform === 'win32') {
|
|
1006
|
+
return this.grepWindows(args, searchPath);
|
|
1007
|
+
}
|
|
1008
|
+
return this.grepUnix(args, searchPath, platform);
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Windows grep: rg > Select-String > Node-native
|
|
1012
|
+
*/
|
|
1013
|
+
grepWindows(args, searchPath) {
|
|
1014
|
+
// 1. Try ripgrep (rg) first — best cross-platform search tool
|
|
1015
|
+
try {
|
|
1016
|
+
(0, child_process_1.execSync)('rg --version', { encoding: 'utf-8', timeout: 5000, stdio: 'pipe' });
|
|
1017
|
+
return this.grepWithRg(args, searchPath);
|
|
1018
|
+
}
|
|
1019
|
+
catch { /* rg not available */ }
|
|
1020
|
+
// 2. Try PowerShell Select-String
|
|
1021
|
+
try {
|
|
1022
|
+
return this.grepWithSelectString(args, searchPath);
|
|
1023
|
+
}
|
|
1024
|
+
catch { /* Select-String failed */ }
|
|
1025
|
+
// 3. Fall back to Node-native recursive file scanner
|
|
1026
|
+
return this.grepNodeNative(args, searchPath);
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Unix grep: rg > system grep (BSD/GNU)
|
|
1030
|
+
*/
|
|
1031
|
+
grepUnix(args, searchPath, platform) {
|
|
1032
|
+
// Try ripgrep first if available
|
|
1033
|
+
try {
|
|
1034
|
+
(0, child_process_1.execSync)('rg --version', { encoding: 'utf-8', timeout: 5000, stdio: 'pipe' });
|
|
1035
|
+
return this.grepWithRg(args, searchPath);
|
|
1036
|
+
}
|
|
1037
|
+
catch { /* rg not available, fall through to system grep */ }
|
|
1038
|
+
const isMac = platform === 'darwin';
|
|
1002
1039
|
let cmd;
|
|
1003
1040
|
if (isMac) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
cmd = `grep -rn --include="${args.include}" "${args.pattern}" "${searchPath}"`;
|
|
1008
|
-
}
|
|
1009
|
-
else {
|
|
1010
|
-
cmd = `grep -rn "${args.pattern}" "${searchPath}"`;
|
|
1011
|
-
}
|
|
1041
|
+
cmd = args.include
|
|
1042
|
+
? `grep -rn --include="${args.include}" "${args.pattern}" "${searchPath}"`
|
|
1043
|
+
: `grep -rn "${args.pattern}" "${searchPath}"`;
|
|
1012
1044
|
}
|
|
1013
1045
|
else {
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1046
|
+
cmd = args.include
|
|
1047
|
+
? `grep -rn --color=never --include="${args.include}" "${args.pattern}" "${searchPath}"`
|
|
1048
|
+
: `grep -rn --color=never "${args.pattern}" "${searchPath}"`;
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
const output = (0, child_process_1.execSync)(cmd, {
|
|
1052
|
+
cwd: this.cwd,
|
|
1053
|
+
encoding: 'utf-8',
|
|
1054
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
1055
|
+
timeout: 30000,
|
|
1056
|
+
env: { ...process.env, GREP_OPTIONS: '' },
|
|
1057
|
+
});
|
|
1058
|
+
return {
|
|
1059
|
+
success: true,
|
|
1060
|
+
output: output.trim(),
|
|
1061
|
+
metadata: { searchStatus: 'search_matches_found' },
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
catch (error) {
|
|
1065
|
+
// Distinguish real "no matches" (exit 1 + no stderr) from command failure
|
|
1066
|
+
const stderr = (error.stderr || '').toString();
|
|
1067
|
+
if (error.status === 1 && !stderr.trim()) {
|
|
1068
|
+
return {
|
|
1069
|
+
success: true,
|
|
1070
|
+
output: 'No matches found',
|
|
1071
|
+
metadata: { searchStatus: 'search_no_matches' },
|
|
1072
|
+
};
|
|
1020
1073
|
}
|
|
1074
|
+
// Real failure: command not found, permission denied, etc.
|
|
1075
|
+
return {
|
|
1076
|
+
success: false,
|
|
1077
|
+
error: stderr || error.message,
|
|
1078
|
+
suggestion: 'The grep command failed. Install ripgrep (rg) for best cross-platform search.',
|
|
1079
|
+
canRetry: true,
|
|
1080
|
+
metadata: { searchStatus: 'search_failed' },
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Search using ripgrep (rg) — works on all platforms
|
|
1086
|
+
*/
|
|
1087
|
+
grepWithRg(args, searchPath) {
|
|
1088
|
+
let cmd = `rg -n --no-heading`;
|
|
1089
|
+
if (args.include) {
|
|
1090
|
+
cmd += ` -g "${args.include}"`;
|
|
1021
1091
|
}
|
|
1092
|
+
cmd += ` "${args.pattern}" "${searchPath}"`;
|
|
1022
1093
|
try {
|
|
1023
1094
|
const output = (0, child_process_1.execSync)(cmd, {
|
|
1024
1095
|
cwd: this.cwd,
|
|
1025
1096
|
encoding: 'utf-8',
|
|
1026
1097
|
maxBuffer: 5 * 1024 * 1024,
|
|
1027
1098
|
timeout: 30000,
|
|
1028
|
-
env: { ...process.env, GREP_OPTIONS: '' }, // Prevent user's grep options from interfering
|
|
1029
1099
|
});
|
|
1030
|
-
return {
|
|
1100
|
+
return {
|
|
1101
|
+
success: true,
|
|
1102
|
+
output: output.trim(),
|
|
1103
|
+
metadata: { searchStatus: 'search_matches_found', backend: 'ripgrep' },
|
|
1104
|
+
};
|
|
1031
1105
|
}
|
|
1032
1106
|
catch (error) {
|
|
1033
|
-
if (error.status === 1) {
|
|
1034
|
-
|
|
1035
|
-
|
|
1107
|
+
if (error.status === 1 && !(error.stderr || '').toString().trim()) {
|
|
1108
|
+
return {
|
|
1109
|
+
success: true,
|
|
1110
|
+
output: 'No matches found',
|
|
1111
|
+
metadata: { searchStatus: 'search_no_matches', backend: 'ripgrep' },
|
|
1112
|
+
};
|
|
1036
1113
|
}
|
|
1037
|
-
|
|
1114
|
+
throw error; // Let caller try next backend
|
|
1038
1115
|
}
|
|
1039
1116
|
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Search using PowerShell Select-String (Windows)
|
|
1119
|
+
*/
|
|
1120
|
+
grepWithSelectString(args, searchPath) {
|
|
1121
|
+
// Normalize path for PowerShell
|
|
1122
|
+
const psPath = searchPath.replace(/\//g, '\\');
|
|
1123
|
+
const includeFilter = args.include
|
|
1124
|
+
? `-Include "${args.include}"`
|
|
1125
|
+
: `-Include *`;
|
|
1126
|
+
const escapedPattern = args.pattern.replace(/'/g, "''");
|
|
1127
|
+
const cmd = `powershell -NoProfile -Command "Get-ChildItem -Path '${psPath}' -Recurse -File ${includeFilter} | Select-String -Pattern '${escapedPattern}' | ForEach-Object { $_.Path + ':' + $_.LineNumber + ':' + $_.Line }"`;
|
|
1128
|
+
try {
|
|
1129
|
+
const output = (0, child_process_1.execSync)(cmd, {
|
|
1130
|
+
cwd: this.cwd,
|
|
1131
|
+
encoding: 'utf-8',
|
|
1132
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
1133
|
+
timeout: 60000,
|
|
1134
|
+
});
|
|
1135
|
+
const trimmed = output.trim();
|
|
1136
|
+
if (!trimmed) {
|
|
1137
|
+
return {
|
|
1138
|
+
success: true,
|
|
1139
|
+
output: 'No matches found',
|
|
1140
|
+
metadata: { searchStatus: 'search_no_matches', backend: 'select-string' },
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
return {
|
|
1144
|
+
success: true,
|
|
1145
|
+
output: trimmed,
|
|
1146
|
+
metadata: { searchStatus: 'search_matches_found', backend: 'select-string' },
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
catch (error) {
|
|
1150
|
+
const stderr = (error.stderr || '').toString();
|
|
1151
|
+
// Select-String returns exit code 1 for no matches when used in pipeline
|
|
1152
|
+
if (error.status === 1 && !stderr.trim()) {
|
|
1153
|
+
return {
|
|
1154
|
+
success: true,
|
|
1155
|
+
output: 'No matches found',
|
|
1156
|
+
metadata: { searchStatus: 'search_no_matches', backend: 'select-string' },
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
throw error; // Let caller try next backend
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Pure Node.js recursive file search — reliable on all platforms, no external deps
|
|
1164
|
+
*/
|
|
1165
|
+
grepNodeNative(args, searchPath) {
|
|
1166
|
+
const { minimatch } = (() => {
|
|
1167
|
+
try {
|
|
1168
|
+
return require('minimatch');
|
|
1169
|
+
}
|
|
1170
|
+
catch {
|
|
1171
|
+
return { minimatch: null };
|
|
1172
|
+
}
|
|
1173
|
+
})();
|
|
1174
|
+
const results = [];
|
|
1175
|
+
let regex;
|
|
1176
|
+
try {
|
|
1177
|
+
regex = new RegExp(args.pattern, 'i');
|
|
1178
|
+
}
|
|
1179
|
+
catch {
|
|
1180
|
+
// If pattern is not valid regex, treat as literal
|
|
1181
|
+
regex = new RegExp(args.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
|
1182
|
+
}
|
|
1183
|
+
const includeGlob = args.include || null;
|
|
1184
|
+
const maxResults = 500;
|
|
1185
|
+
const skipDirs = new Set(['node_modules', '.git', 'dist', 'build', '__pycache__', '.next', 'vendor', '.venv']);
|
|
1186
|
+
const walk = (dir) => {
|
|
1187
|
+
if (results.length >= maxResults)
|
|
1188
|
+
return;
|
|
1189
|
+
let entries;
|
|
1190
|
+
try {
|
|
1191
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1192
|
+
}
|
|
1193
|
+
catch {
|
|
1194
|
+
return; // Permission denied or inaccessible
|
|
1195
|
+
}
|
|
1196
|
+
for (const entry of entries) {
|
|
1197
|
+
if (results.length >= maxResults)
|
|
1198
|
+
break;
|
|
1199
|
+
const fullPath = path.join(dir, entry.name);
|
|
1200
|
+
if (entry.isDirectory()) {
|
|
1201
|
+
if (!skipDirs.has(entry.name)) {
|
|
1202
|
+
walk(fullPath);
|
|
1203
|
+
}
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
if (!entry.isFile())
|
|
1207
|
+
continue;
|
|
1208
|
+
// Check include pattern
|
|
1209
|
+
if (includeGlob) {
|
|
1210
|
+
const matches = minimatch
|
|
1211
|
+
? minimatch(entry.name, includeGlob)
|
|
1212
|
+
: entry.name.endsWith(includeGlob.replace('*', ''));
|
|
1213
|
+
if (!matches)
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
// Skip binary files by extension
|
|
1217
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
1218
|
+
const binaryExts = new Set(['.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.eot', '.zip', '.tar', '.gz', '.exe', '.dll', '.so', '.dylib', '.pdf', '.mp3', '.mp4', '.wav', '.avi', '.mov']);
|
|
1219
|
+
if (binaryExts.has(ext))
|
|
1220
|
+
continue;
|
|
1221
|
+
try {
|
|
1222
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
1223
|
+
const lines = content.split('\n');
|
|
1224
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1225
|
+
if (regex.test(lines[i])) {
|
|
1226
|
+
const relativePath = path.relative(this.cwd, fullPath).replace(/\\/g, '/');
|
|
1227
|
+
results.push(`${relativePath}:${i + 1}:${lines[i]}`);
|
|
1228
|
+
if (results.length >= maxResults)
|
|
1229
|
+
break;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
catch {
|
|
1234
|
+
// Skip files that can't be read (binary, encoding issues, etc.)
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
try {
|
|
1239
|
+
walk(searchPath);
|
|
1240
|
+
}
|
|
1241
|
+
catch (error) {
|
|
1242
|
+
return {
|
|
1243
|
+
success: false,
|
|
1244
|
+
error: `File search failed: ${error.message}`,
|
|
1245
|
+
metadata: { searchStatus: 'search_failed', backend: 'node-native' },
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
if (results.length === 0) {
|
|
1249
|
+
return {
|
|
1250
|
+
success: true,
|
|
1251
|
+
output: 'No matches found',
|
|
1252
|
+
metadata: { searchStatus: 'search_no_matches', backend: 'node-native' },
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
const truncationNote = results.length >= maxResults ? `\n...[truncated at ${maxResults} matches]` : '';
|
|
1256
|
+
return {
|
|
1257
|
+
success: true,
|
|
1258
|
+
output: results.join('\n') + truncationNote,
|
|
1259
|
+
metadata: { searchStatus: 'search_matches_found', backend: 'node-native' },
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1040
1262
|
listDir(args) {
|
|
1041
1263
|
const dirPath = this.resolvePath(args.path);
|
|
1042
1264
|
if (!fs.existsSync(dirPath)) {
|