vigthoria-cli 1.8.19 → 1.9.2
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/README.md +2 -6
- package/dist/commands/auth.d.ts +49 -21
- package/dist/commands/auth.js +385 -343
- package/dist/commands/chat.d.ts +9 -0
- package/dist/commands/chat.js +221 -33
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +40 -20
- package/dist/commands/index.d.ts +12 -0
- package/dist/commands/index.js +182 -0
- package/dist/commands/legion.d.ts +39 -0
- package/dist/commands/legion.js +999 -71
- package/dist/index.d.ts +3 -1
- package/dist/index.js +374 -34
- package/dist/utils/api.d.ts +61 -1
- package/dist/utils/api.js +558 -86
- package/dist/utils/config.js +9 -10
- package/dist/utils/context-ranker.d.ts +24 -0
- package/dist/utils/context-ranker.js +147 -0
- package/dist/utils/post-write-validator.d.ts +25 -0
- package/dist/utils/post-write-validator.js +138 -0
- package/dist/utils/session.d.ts +19 -0
- package/dist/utils/session.js +91 -6
- package/dist/utils/task-display.d.ts +31 -0
- package/dist/utils/task-display.js +115 -0
- package/dist/utils/tools.d.ts +15 -0
- package/dist/utils/tools.js +341 -58
- package/dist/utils/workspace-cache.d.ts +31 -0
- package/dist/utils/workspace-cache.js +96 -0
- package/package.json +7 -3
package/dist/utils/config.js
CHANGED
|
@@ -155,28 +155,27 @@ class Config {
|
|
|
155
155
|
expiresAt: data.expiresAt || null,
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
|
-
// Model selection - Vigthoria Models (internal
|
|
158
|
+
// Model selection - Vigthoria Models (internal routing IDs only)
|
|
159
159
|
getAvailableModels() {
|
|
160
160
|
const sub = this.store.get('subscription');
|
|
161
161
|
const plan = (sub.plan || '').toLowerCase();
|
|
162
162
|
// ═══════════════════════════════════════════════════════════════
|
|
163
|
-
// VIGTHORIA LOCAL - Self-hosted models
|
|
163
|
+
// VIGTHORIA LOCAL - Self-hosted operational models
|
|
164
164
|
// ═══════════════════════════════════════════════════════════════
|
|
165
165
|
const models = [
|
|
166
|
-
{ id: 'agent', name: 'Vigthoria Agent GPU', description: 'Blackwell autonomous agent workflow', tier: 'local', backendModel: 'vigthoria-v3-code-
|
|
167
|
-
{ id: 'code', name: 'Vigthoria v3 Code
|
|
168
|
-
{ id: 'code-
|
|
169
|
-
{ id: 'code-
|
|
170
|
-
{ id: 'balanced', name: 'Vigthoria
|
|
171
|
-
{ id: '
|
|
172
|
-
{ id: 'creative', name: 'Vigthoria Creative 9B V4', description: 'Creative and lyric generation model', tier: 'local', backendModel: 'vigthoria-creative-9b-v4' },
|
|
166
|
+
{ id: 'agent', name: 'Vigthoria Agent GPU', description: 'Blackwell autonomous agent workflow', tier: 'local', backendModel: 'vigthoria-v3-code-35b' },
|
|
167
|
+
{ id: 'code', name: 'Vigthoria v3 Code 35B', description: 'Native 35B coding model on Blackwell', tier: 'local', backendModel: 'vigthoria-v3-code-35b' },
|
|
168
|
+
{ id: 'code-35b', name: 'Vigthoria v3 Code 35B', description: 'Same flagship model as code', tier: 'local', backendModel: 'vigthoria-v3-code-35b' },
|
|
169
|
+
{ id: 'code-9b', name: 'Vigthoria C1 Code 9B', description: 'Efficient coding specialist for quick tasks', tier: 'local', backendModel: 'vigthoria_c1_m' },
|
|
170
|
+
{ id: 'balanced', name: 'Vigthoria Master 7.6B', description: 'Balanced general-purpose local model', tier: 'local', backendModel: 'vigthoria_master' },
|
|
171
|
+
{ id: 'balanced-4b', name: 'Vigthoria v3 Balanced 4B', description: 'Efficient 4B general-purpose model (Qwen3.5-4B based)', tier: 'local', backendModel: 'vigthoria-balanced-4b' },
|
|
173
172
|
];
|
|
174
173
|
// ═══════════════════════════════════════════════════════════════
|
|
175
174
|
// VIGTHORIA CLOUD - Premium cloud models (Pro subscription)
|
|
176
175
|
// For complex multi-file tasks, large refactoring, architecture
|
|
177
176
|
// ═══════════════════════════════════════════════════════════════
|
|
178
177
|
if (this.hasCloudAccess()) {
|
|
179
|
-
models.push({ id: 'cloud', name: 'Vigthoria Cloud Pro', description: '
|
|
178
|
+
models.push({ id: 'cloud', name: 'Vigthoria Cloud Pro', description: 'High-capability cloud model for complex tasks', tier: 'cloud', backendModel: 'vigthoria-cloud-pro' }, { id: 'cloud-reason', name: 'Vigthoria Cloud K2', description: 'Reasoning-focused cloud model', tier: 'cloud', backendModel: 'vigthoria-cloud-k2' }, { id: 'ultra', name: 'Vigthoria Cloud Ultra', description: 'Maximum capability cloud routing', tier: 'cloud', backendModel: 'vigthoria-cloud-ultra' });
|
|
180
179
|
}
|
|
181
180
|
return models;
|
|
182
181
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vigthoria CLI — Semantic Context Ranker
|
|
3
|
+
*
|
|
4
|
+
* Lightweight local-first file relevance scoring. Extracts keywords from the
|
|
5
|
+
* user prompt and scores workspace files by occurrence in filename + content.
|
|
6
|
+
* Used to prioritise which files are sent to V3 as workspace context.
|
|
7
|
+
*
|
|
8
|
+
* No external dependencies — uses only Node.js built-ins.
|
|
9
|
+
*/
|
|
10
|
+
export interface RankedFile {
|
|
11
|
+
path: string;
|
|
12
|
+
score: number;
|
|
13
|
+
snippet: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RankedContext {
|
|
16
|
+
topFiles: RankedFile[];
|
|
17
|
+
totalFilesScanned: number;
|
|
18
|
+
keywords: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Score and rank workspace files by relevance to the given prompt.
|
|
22
|
+
* Returns the top N files with path, score, and content snippet.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildSemanticContext(workspacePath: string, prompt: string, topN?: number): RankedContext;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vigthoria CLI — Semantic Context Ranker
|
|
4
|
+
*
|
|
5
|
+
* Lightweight local-first file relevance scoring. Extracts keywords from the
|
|
6
|
+
* user prompt and scores workspace files by occurrence in filename + content.
|
|
7
|
+
* Used to prioritise which files are sent to V3 as workspace context.
|
|
8
|
+
*
|
|
9
|
+
* No external dependencies — uses only Node.js built-ins.
|
|
10
|
+
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.buildSemanticContext = buildSemanticContext;
|
|
16
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
17
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
const IGNORED_DIRS = new Set([
|
|
19
|
+
'.git', 'node_modules', 'dist', '.next', '__pycache__',
|
|
20
|
+
'.vigthoria', 'coverage', '.cache', '.turbo', 'build', 'out',
|
|
21
|
+
]);
|
|
22
|
+
const TEXT_EXTENSIONS = new Set([
|
|
23
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
24
|
+
'.py', '.go', '.rs', '.java', '.c', '.cpp', '.h', '.cs', '.php', '.rb',
|
|
25
|
+
'.json', '.yaml', '.yml', '.toml', '.env', '.sh', '.bash',
|
|
26
|
+
'.html', '.css', '.scss', '.sass', '.less',
|
|
27
|
+
'.md', '.txt', '.xml', '.svg',
|
|
28
|
+
]);
|
|
29
|
+
/** Extract scoring keywords from the user prompt. */
|
|
30
|
+
function extractKeywords(prompt) {
|
|
31
|
+
// General words (min length 4)
|
|
32
|
+
const words = prompt
|
|
33
|
+
.replace(/[^\w\s./\\-]/g, ' ')
|
|
34
|
+
.split(/\s+/)
|
|
35
|
+
.filter((w) => w.length >= 4)
|
|
36
|
+
.map((w) => w.toLowerCase());
|
|
37
|
+
// CamelCase splits
|
|
38
|
+
const camel = (prompt.match(/[a-z][A-Z][a-zA-Z]+|[A-Z]{2,}[a-z]+[a-zA-Z]*/g) || [])
|
|
39
|
+
.map((w) => w.toLowerCase());
|
|
40
|
+
// Acronyms: uppercase sequences 2-4 chars (SSE, API, HTTP, etc.)
|
|
41
|
+
const acronyms = (prompt.match(/\b[A-Z]{2,4}\b/g) || [])
|
|
42
|
+
.map((w) => w.toLowerCase());
|
|
43
|
+
return [...new Set([...words, ...camel, ...acronyms])].filter((kw) => kw.length >= 2);
|
|
44
|
+
}
|
|
45
|
+
function scoreFile(filePath, keywords) {
|
|
46
|
+
if (keywords.length === 0)
|
|
47
|
+
return 0;
|
|
48
|
+
const filename = node_path_1.default.basename(filePath).toLowerCase();
|
|
49
|
+
const parentDir = node_path_1.default.basename(node_path_1.default.dirname(filePath)).toLowerCase();
|
|
50
|
+
let score = 0;
|
|
51
|
+
for (const kw of keywords) {
|
|
52
|
+
// Filename match (high weight)
|
|
53
|
+
if (filename.includes(kw))
|
|
54
|
+
score += 15;
|
|
55
|
+
// Parent directory name match (medium weight)
|
|
56
|
+
if (parentDir.includes(kw))
|
|
57
|
+
score += 6;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const content = node_fs_1.default.readFileSync(filePath, 'utf-8').slice(0, 300000);
|
|
61
|
+
const lower = content.toLowerCase();
|
|
62
|
+
for (const kw of keywords) {
|
|
63
|
+
const escaped = kw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
64
|
+
// Word boundaries for short keywords prevent false-positives (e.g. 'sse' inside 'assert')
|
|
65
|
+
const pattern = kw.length <= 4 ? '\\b' + escaped + '\\b' : escaped;
|
|
66
|
+
const hits = (lower.match(new RegExp(pattern, 'g')) || []).length;
|
|
67
|
+
score += hits;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch { /* binary or unreadable */ }
|
|
71
|
+
return score;
|
|
72
|
+
}
|
|
73
|
+
function walkWorkspace(dir, maxFiles = 800) {
|
|
74
|
+
const results = [];
|
|
75
|
+
const stack = [dir];
|
|
76
|
+
const workspaceRoot = node_path_1.default.resolve(dir);
|
|
77
|
+
while (stack.length > 0 && results.length < maxFiles) {
|
|
78
|
+
const current = stack.pop();
|
|
79
|
+
if (!current)
|
|
80
|
+
continue;
|
|
81
|
+
let entries;
|
|
82
|
+
try {
|
|
83
|
+
entries = node_fs_1.default.readdirSync(current, { withFileTypes: true });
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
// Hard cap enforced inside inner loop (fixes > 800 overshoot)
|
|
90
|
+
if (results.length >= maxFiles)
|
|
91
|
+
break;
|
|
92
|
+
if (IGNORED_DIRS.has(entry.name))
|
|
93
|
+
continue;
|
|
94
|
+
const fullPath = node_path_1.default.join(current, entry.name);
|
|
95
|
+
// Security: skip symlinks that escape workspace root (fixes 8.2)
|
|
96
|
+
if (entry.isSymbolicLink()) {
|
|
97
|
+
try {
|
|
98
|
+
const real = node_fs_1.default.realpathSync(fullPath);
|
|
99
|
+
if (!real.startsWith(workspaceRoot + node_path_1.default.sep) && real !== workspaceRoot)
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (entry.isDirectory()) {
|
|
107
|
+
stack.push(fullPath);
|
|
108
|
+
}
|
|
109
|
+
else if (entry.isFile() && TEXT_EXTENSIONS.has(node_path_1.default.extname(entry.name).toLowerCase())) {
|
|
110
|
+
results.push(fullPath);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Score and rank workspace files by relevance to the given prompt.
|
|
118
|
+
* Returns the top N files with path, score, and content snippet.
|
|
119
|
+
*/
|
|
120
|
+
function buildSemanticContext(workspacePath, prompt, topN = 12) {
|
|
121
|
+
if (!workspacePath || !node_fs_1.default.existsSync(workspacePath)) {
|
|
122
|
+
return { topFiles: [], totalFilesScanned: 0, keywords: [] };
|
|
123
|
+
}
|
|
124
|
+
const keywords = extractKeywords(prompt);
|
|
125
|
+
if (keywords.length === 0) {
|
|
126
|
+
return { topFiles: [], totalFilesScanned: 0, keywords: [] };
|
|
127
|
+
}
|
|
128
|
+
const files = walkWorkspace(workspacePath);
|
|
129
|
+
const scored = files
|
|
130
|
+
.map((f) => ({ filePath: f, score: scoreFile(f, keywords) }))
|
|
131
|
+
.filter((f) => f.score > 0)
|
|
132
|
+
.sort((a, b) => b.score - a.score)
|
|
133
|
+
.slice(0, topN);
|
|
134
|
+
const topFiles = scored.map(({ filePath, score }) => {
|
|
135
|
+
let snippet = '';
|
|
136
|
+
try {
|
|
137
|
+
snippet = node_fs_1.default.readFileSync(filePath, 'utf-8').slice(0, 600);
|
|
138
|
+
}
|
|
139
|
+
catch { /* ignore */ }
|
|
140
|
+
return {
|
|
141
|
+
path: node_path_1.default.relative(workspacePath, filePath).replace(/\\/g, '/'),
|
|
142
|
+
score,
|
|
143
|
+
snippet,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
return { topFiles, totalFilesScanned: files.length, keywords };
|
|
147
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vigthoria CLI — Post-Write Validator / Self-Healing Layer
|
|
3
|
+
*
|
|
4
|
+
* After the V3 agent writes files to the workspace, this module:
|
|
5
|
+
* 1. Detects available validators (tsc, npm test, py_compile).
|
|
6
|
+
* 2. Runs them in the project directory.
|
|
7
|
+
* 3. Returns structured pass/fail results for each tool.
|
|
8
|
+
*
|
|
9
|
+
* Used by runSelfHealingCycle in api.ts to send targeted correction prompts.
|
|
10
|
+
*/
|
|
11
|
+
export interface ValidationResult {
|
|
12
|
+
ran: boolean;
|
|
13
|
+
tool: string;
|
|
14
|
+
passed: boolean;
|
|
15
|
+
output: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Run post-write validation in the project directory.
|
|
19
|
+
* Each check is time-bounded to avoid blocking the CLI.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runPostWriteValidation(workspacePath: string): Promise<ValidationResult[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Build a clear error summary string from failed validation results.
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatValidationErrors(results: ValidationResult[]): string;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vigthoria CLI — Post-Write Validator / Self-Healing Layer
|
|
4
|
+
*
|
|
5
|
+
* After the V3 agent writes files to the workspace, this module:
|
|
6
|
+
* 1. Detects available validators (tsc, npm test, py_compile).
|
|
7
|
+
* 2. Runs them in the project directory.
|
|
8
|
+
* 3. Returns structured pass/fail results for each tool.
|
|
9
|
+
*
|
|
10
|
+
* Used by runSelfHealingCycle in api.ts to send targeted correction prompts.
|
|
11
|
+
*/
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.runPostWriteValidation = runPostWriteValidation;
|
|
17
|
+
exports.formatValidationErrors = formatValidationErrors;
|
|
18
|
+
const node_child_process_1 = require("node:child_process");
|
|
19
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
20
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
21
|
+
function commandExists(cmd, args = ['--version']) {
|
|
22
|
+
try {
|
|
23
|
+
(0, node_child_process_1.execFileSync)(cmd, args, { stdio: 'pipe', timeout: 4000, windowsHide: true });
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function hasTsConfig(dir) {
|
|
31
|
+
return node_fs_1.default.existsSync(node_path_1.default.join(dir, 'tsconfig.json'));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the best available tsc binary for the project.
|
|
35
|
+
* Prefers local node_modules/.bin/tsc over npx to avoid network fetches.
|
|
36
|
+
*/
|
|
37
|
+
function resolveTscBin(workspacePath) {
|
|
38
|
+
const localTsc = node_path_1.default.join(workspacePath, 'node_modules', '.bin', 'tsc');
|
|
39
|
+
if (node_fs_1.default.existsSync(localTsc))
|
|
40
|
+
return { cmd: localTsc, args: ["--noEmit", "--skipLibCheck"] };
|
|
41
|
+
try {
|
|
42
|
+
(0, node_child_process_1.execFileSync)("npx", ["--version"], { stdio: "pipe", timeout: 4000, windowsHide: true });
|
|
43
|
+
return { cmd: "npx", args: ["tsc", "--noEmit", "--skipLibCheck"] };
|
|
44
|
+
}
|
|
45
|
+
catch { /* npx not available */ }
|
|
46
|
+
try {
|
|
47
|
+
(0, node_child_process_1.execFileSync)("tsc", ["--version"], { stdio: "pipe", timeout: 4000, windowsHide: true });
|
|
48
|
+
return { cmd: "tsc", args: ["--noEmit", "--skipLibCheck"] };
|
|
49
|
+
}
|
|
50
|
+
catch { /* tsc not available */ }
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function hasRealTestScript(dir) {
|
|
54
|
+
try {
|
|
55
|
+
const pkgPath = node_path_1.default.join(dir, 'package.json');
|
|
56
|
+
if (!node_fs_1.default.existsSync(pkgPath))
|
|
57
|
+
return false;
|
|
58
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
59
|
+
const script = pkg.scripts?.test ?? '';
|
|
60
|
+
return typeof script === 'string' && script.length > 0
|
|
61
|
+
&& !script.startsWith('echo "Error: no test specified"');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Run post-write validation in the project directory.
|
|
69
|
+
* Each check is time-bounded to avoid blocking the CLI.
|
|
70
|
+
*/
|
|
71
|
+
async function runPostWriteValidation(workspacePath) {
|
|
72
|
+
if (!workspacePath || !node_fs_1.default.existsSync(workspacePath))
|
|
73
|
+
return [];
|
|
74
|
+
const results = [];
|
|
75
|
+
const opts = { cwd: workspacePath, stdio: 'pipe', windowsHide: true };
|
|
76
|
+
// 1. TypeScript type check
|
|
77
|
+
const tscBin = hasTsConfig(workspacePath) ? resolveTscBin(workspacePath) : null;
|
|
78
|
+
if (tscBin) {
|
|
79
|
+
try {
|
|
80
|
+
(0, node_child_process_1.execFileSync)(tscBin.cmd, tscBin.args, { ...opts, timeout: 45_000 });
|
|
81
|
+
results.push({ ran: true, tool: 'tsc', passed: true, output: '' });
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
const out = [err.stderr?.toString(), err.stdout?.toString()].filter(Boolean).join('\n').slice(0, 4000);
|
|
85
|
+
results.push({ ran: true, tool: 'tsc', passed: false, output: out });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// 2. npm test
|
|
89
|
+
if (hasRealTestScript(workspacePath) && commandExists('npm')) {
|
|
90
|
+
try {
|
|
91
|
+
(0, node_child_process_1.execFileSync)('npm', ['test', '--', '--passWithNoTests'], { ...opts, timeout: 90_000 });
|
|
92
|
+
results.push({ ran: true, tool: 'npm test', passed: true, output: '' });
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
const out = [err.stderr?.toString(), err.stdout?.toString()].filter(Boolean).join('\n').slice(0, 4000);
|
|
96
|
+
results.push({ ran: true, tool: 'npm test', passed: false, output: out });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// 3. Python syntax check
|
|
100
|
+
const hasPy = node_fs_1.default.existsSync(node_path_1.default.join(workspacePath, 'requirements.txt'))
|
|
101
|
+
|| node_fs_1.default.existsSync(node_path_1.default.join(workspacePath, 'setup.py'))
|
|
102
|
+
|| node_fs_1.default.existsSync(node_path_1.default.join(workspacePath, 'pyproject.toml'));
|
|
103
|
+
if (hasPy && commandExists('python3', ['-c', 'import ast'])) {
|
|
104
|
+
const pyFiles = [];
|
|
105
|
+
try {
|
|
106
|
+
const entries = node_fs_1.default.readdirSync(workspacePath, { withFileTypes: true });
|
|
107
|
+
for (const e of entries) {
|
|
108
|
+
if (e.isFile() && e.name.endsWith('.py'))
|
|
109
|
+
pyFiles.push(e.name);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch { /* ignore */ }
|
|
113
|
+
let pyPassed = true;
|
|
114
|
+
let pyOut = '';
|
|
115
|
+
for (const pyFile of pyFiles.slice(0, 20)) {
|
|
116
|
+
try {
|
|
117
|
+
(0, node_child_process_1.execFileSync)('python3', ['-m', 'py_compile', pyFile], { ...opts, timeout: 10_000 });
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
pyPassed = false;
|
|
121
|
+
pyOut += `${pyFile}: ${err.stderr?.toString()?.trim() || 'syntax error'}\n`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (pyFiles.length > 0) {
|
|
125
|
+
results.push({ ran: true, tool: 'py_compile', passed: pyPassed, output: pyOut.slice(0, 2000) });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Build a clear error summary string from failed validation results.
|
|
132
|
+
*/
|
|
133
|
+
function formatValidationErrors(results) {
|
|
134
|
+
return results
|
|
135
|
+
.filter((r) => r.ran && !r.passed)
|
|
136
|
+
.map((r) => `[${r.tool}]\n${r.output.trim()}`)
|
|
137
|
+
.join('\n\n─────────────────────────\n\n');
|
|
138
|
+
}
|
package/dist/utils/session.d.ts
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
* Similar to Vigthoria's session persistence
|
|
4
4
|
*/
|
|
5
5
|
import { ChatMessage } from './api.js';
|
|
6
|
+
export type AuthState = {
|
|
7
|
+
token: string | null;
|
|
8
|
+
expiresAt: number | null;
|
|
9
|
+
isValid: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type CliError = {
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
details?: any;
|
|
15
|
+
isCritical: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Validate persisted authentication state without assuming any field is present.
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateSession(session: AuthState): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Load persisted authentication state and normalize nullable token fields safely.
|
|
23
|
+
*/
|
|
24
|
+
export declare function loadSession(): Promise<AuthState>;
|
|
6
25
|
export interface Session {
|
|
7
26
|
id: string;
|
|
8
27
|
name: string;
|
package/dist/utils/session.js
CHANGED
|
@@ -38,9 +38,96 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
})();
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.SessionManager = void 0;
|
|
41
|
+
exports.validateSession = validateSession;
|
|
42
|
+
exports.loadSession = loadSession;
|
|
41
43
|
const fs = __importStar(require("fs"));
|
|
42
44
|
const path = __importStar(require("path"));
|
|
43
45
|
const os = __importStar(require("os"));
|
|
46
|
+
function createSessionLoadError(message, details) {
|
|
47
|
+
return {
|
|
48
|
+
code: 'SESSION_LOAD_FAILED',
|
|
49
|
+
message,
|
|
50
|
+
details,
|
|
51
|
+
isCritical: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function normalizeAuthState(raw) {
|
|
55
|
+
if (!raw || typeof raw !== 'object') {
|
|
56
|
+
const session = { token: null, expiresAt: null, isValid: false };
|
|
57
|
+
session.isValid = validateSession(session);
|
|
58
|
+
return session;
|
|
59
|
+
}
|
|
60
|
+
const source = raw.auth && typeof raw.auth === 'object' ? raw.auth : raw;
|
|
61
|
+
const rawToken = source.token ?? source.accessToken ?? source.jwt ?? null;
|
|
62
|
+
const rawExpiresAt = source.expiresAt ?? null;
|
|
63
|
+
const token = typeof rawToken === 'string' && rawToken.trim().length > 0 ? rawToken.trim() : null;
|
|
64
|
+
const expiresAt = typeof rawExpiresAt === 'number' && Number.isFinite(rawExpiresAt) ? rawExpiresAt : null;
|
|
65
|
+
const session = { token, expiresAt, isValid: false };
|
|
66
|
+
session.isValid = validateSession(session);
|
|
67
|
+
return session;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Validate persisted authentication state without assuming any field is present.
|
|
71
|
+
*/
|
|
72
|
+
function validateSession(session) {
|
|
73
|
+
if (!session || typeof session !== 'object') {
|
|
74
|
+
console.warn('Invalid session: session state is missing.');
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (typeof session.token !== 'string' || session.token.trim().length === 0) {
|
|
78
|
+
console.warn('Invalid session: authentication token is missing.');
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (typeof session.expiresAt !== 'number' || !Number.isFinite(session.expiresAt)) {
|
|
82
|
+
console.warn('Invalid session: expiration timestamp is missing.');
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (session.expiresAt <= Date.now()) {
|
|
86
|
+
console.warn('Invalid session: authentication token has expired.');
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Load persisted authentication state and normalize nullable token fields safely.
|
|
93
|
+
*/
|
|
94
|
+
async function loadSession() {
|
|
95
|
+
const configDir = path.join(os.homedir(), '.vigthoria');
|
|
96
|
+
const candidateFiles = [
|
|
97
|
+
path.join(configDir, 'auth.json'),
|
|
98
|
+
path.join(configDir, 'session.json'),
|
|
99
|
+
path.join(configDir, 'config.json'),
|
|
100
|
+
];
|
|
101
|
+
try {
|
|
102
|
+
const sessionFile = candidateFiles.find((filePath) => fs.existsSync(filePath));
|
|
103
|
+
if (!sessionFile) {
|
|
104
|
+
const session = { token: null, expiresAt: null, isValid: false };
|
|
105
|
+
validateSession(session);
|
|
106
|
+
throw createSessionLoadError('Failed to load persisted authentication session: no session file found.');
|
|
107
|
+
}
|
|
108
|
+
const content = fs.readFileSync(sessionFile, 'utf-8');
|
|
109
|
+
const parsed = JSON.parse(content);
|
|
110
|
+
const session = normalizeAuthState(parsed);
|
|
111
|
+
if (session.token === null || session.expiresAt === null || !session.isValid) {
|
|
112
|
+
throw createSessionLoadError('Failed to load persisted authentication session: token is missing or expired.', {
|
|
113
|
+
sessionFile,
|
|
114
|
+
hasToken: session.token !== null,
|
|
115
|
+
expiresAt: session.expiresAt,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return session;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error?.code === 'SESSION_LOAD_FAILED') {
|
|
122
|
+
console.error(error.message, error.details ?? '');
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
console.error('Failed to load persisted authentication session.', error?.message ?? error);
|
|
126
|
+
throw createSessionLoadError('Failed to load persisted authentication session.', {
|
|
127
|
+
message: error?.message ?? String(error),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
44
131
|
class SessionManager {
|
|
45
132
|
sessionsDir;
|
|
46
133
|
compactThreshold = 40;
|
|
@@ -56,15 +143,12 @@ class SessionManager {
|
|
|
56
143
|
}
|
|
57
144
|
}
|
|
58
145
|
catch (error) {
|
|
59
|
-
// On permission errors, try alternative location
|
|
60
146
|
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
61
|
-
// Fallback to temp directory
|
|
62
147
|
this.sessionsDir = path.join(os.tmpdir(), 'vigthoria-sessions');
|
|
63
148
|
if (!fs.existsSync(this.sessionsDir)) {
|
|
64
149
|
fs.mkdirSync(this.sessionsDir, { recursive: true });
|
|
65
150
|
}
|
|
66
151
|
}
|
|
67
|
-
// Silently continue if directory creation fails
|
|
68
152
|
}
|
|
69
153
|
}
|
|
70
154
|
/**
|
|
@@ -117,7 +201,8 @@ class SessionManager {
|
|
|
117
201
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
118
202
|
return JSON.parse(content);
|
|
119
203
|
}
|
|
120
|
-
catch {
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.warn(`Failed to load session ${id}:`, error);
|
|
121
206
|
return null;
|
|
122
207
|
}
|
|
123
208
|
}
|
|
@@ -141,11 +226,11 @@ class SessionManager {
|
|
|
141
226
|
try {
|
|
142
227
|
const content = fs.readFileSync(path.join(this.sessionsDir, f), 'utf-8');
|
|
143
228
|
const session = JSON.parse(content);
|
|
144
|
-
// Return without messages for efficiency
|
|
145
229
|
const { messages, ...metadata } = session;
|
|
146
230
|
return { ...metadata, messages: [] };
|
|
147
231
|
}
|
|
148
|
-
catch {
|
|
232
|
+
catch (error) {
|
|
233
|
+
console.warn(`Failed to read session metadata from ${f}:`, error);
|
|
149
234
|
return null;
|
|
150
235
|
}
|
|
151
236
|
}).filter(Boolean);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vigthoria CLI — Multi-Step Terminal Task Display
|
|
3
|
+
*
|
|
4
|
+
* Renders a live, updating task progress list in the terminal using ANSI
|
|
5
|
+
* escape codes and chalk — no external UI framework required.
|
|
6
|
+
*
|
|
7
|
+
* Only activates when stderr is a real TTY; JSON mode and piped output stay clean.
|
|
8
|
+
*/
|
|
9
|
+
export type TaskStatus = 'pending' | 'running' | 'done' | 'error' | 'skipped';
|
|
10
|
+
export interface Task {
|
|
11
|
+
label: string;
|
|
12
|
+
status: TaskStatus;
|
|
13
|
+
detail?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class TaskDisplay {
|
|
16
|
+
private tasks;
|
|
17
|
+
private linesRendered;
|
|
18
|
+
private readonly enabled;
|
|
19
|
+
constructor(labels: string[], enabled?: boolean);
|
|
20
|
+
private clearLines;
|
|
21
|
+
private renderLines;
|
|
22
|
+
render(): void;
|
|
23
|
+
start(index: number, detail?: string): void;
|
|
24
|
+
complete(index: number, detail?: string): void;
|
|
25
|
+
fail(index: number, detail?: string): void;
|
|
26
|
+
skip(index: number, detail?: string): void;
|
|
27
|
+
setDetail(index: number, detail: string): void;
|
|
28
|
+
clear(): void;
|
|
29
|
+
finalize(): void;
|
|
30
|
+
get isEnabled(): boolean;
|
|
31
|
+
}
|