vigthoria-cli 1.6.54 → 1.6.56
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.js +14 -0
- package/dist/commands/fork.d.ts +16 -0
- package/dist/commands/fork.js +148 -0
- package/dist/commands/history.d.ts +16 -0
- package/dist/commands/history.js +92 -0
- package/dist/commands/legion.d.ts +29 -0
- package/dist/commands/legion.js +223 -0
- package/dist/commands/preview.d.ts +55 -0
- package/dist/commands/preview.js +478 -0
- package/dist/commands/replay.d.ts +17 -0
- package/dist/commands/replay.js +140 -0
- package/dist/index.js +77 -0
- package/dist/utils/api.d.ts +1 -0
- package/dist/utils/api.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vigthoria CLI - Preview Command
|
|
4
|
+
*
|
|
5
|
+
* Local preview server + consolidated visual diffs + Template Service proof gate
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* vigthoria preview - Preview project in browser
|
|
9
|
+
* vigthoria preview --diff - Show consolidated diff of recent agent changes
|
|
10
|
+
* vigthoria preview --proof - Run Template Service preview gate
|
|
11
|
+
* vigthoria preview -p /path/to/project - Preview specific project
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
47
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
48
|
+
};
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.PreviewCommand = void 0;
|
|
51
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const http = __importStar(require("http"));
|
|
55
|
+
const diff_1 = require("diff");
|
|
56
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
57
|
+
const api_js_1 = require("../utils/api.js");
|
|
58
|
+
// Common MIME types for static file serving
|
|
59
|
+
const MIME_TYPES = {
|
|
60
|
+
'.html': 'text/html',
|
|
61
|
+
'.htm': 'text/html',
|
|
62
|
+
'.css': 'text/css',
|
|
63
|
+
'.js': 'application/javascript',
|
|
64
|
+
'.mjs': 'application/javascript',
|
|
65
|
+
'.json': 'application/json',
|
|
66
|
+
'.png': 'image/png',
|
|
67
|
+
'.jpg': 'image/jpeg',
|
|
68
|
+
'.jpeg': 'image/jpeg',
|
|
69
|
+
'.gif': 'image/gif',
|
|
70
|
+
'.svg': 'image/svg+xml',
|
|
71
|
+
'.ico': 'image/x-icon',
|
|
72
|
+
'.webp': 'image/webp',
|
|
73
|
+
'.woff': 'font/woff',
|
|
74
|
+
'.woff2': 'font/woff2',
|
|
75
|
+
'.ttf': 'font/ttf',
|
|
76
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
77
|
+
'.mp4': 'video/mp4',
|
|
78
|
+
'.webm': 'video/webm',
|
|
79
|
+
'.mp3': 'audio/mpeg',
|
|
80
|
+
'.wav': 'audio/wav',
|
|
81
|
+
'.pdf': 'application/pdf',
|
|
82
|
+
'.xml': 'application/xml',
|
|
83
|
+
'.txt': 'text/plain',
|
|
84
|
+
'.map': 'application/json',
|
|
85
|
+
};
|
|
86
|
+
class PreviewCommand {
|
|
87
|
+
config;
|
|
88
|
+
logger;
|
|
89
|
+
api;
|
|
90
|
+
server = null;
|
|
91
|
+
constructor(config, logger) {
|
|
92
|
+
this.config = config;
|
|
93
|
+
this.logger = logger;
|
|
94
|
+
this.api = new api_js_1.APIClient(config, logger);
|
|
95
|
+
}
|
|
96
|
+
async run(options) {
|
|
97
|
+
const projectPath = path.resolve(options.project || process.cwd());
|
|
98
|
+
if (!fs.existsSync(projectPath) || !fs.statSync(projectPath).isDirectory()) {
|
|
99
|
+
this.logger.error(`Project directory not found: ${projectPath}`);
|
|
100
|
+
this.api.destroy();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
console.log();
|
|
104
|
+
console.log(chalk_1.default.bold.white(` ${logger_js_1.CH.hLine.repeat(3)} Vigthoria Preview ${logger_js_1.CH.hLine.repeat(40)}`));
|
|
105
|
+
console.log(chalk_1.default.gray(` Project: ${projectPath}`));
|
|
106
|
+
console.log();
|
|
107
|
+
// Show consolidated diff of recent agent changes
|
|
108
|
+
if (options.diff) {
|
|
109
|
+
await this.showConsolidatedDiff(projectPath);
|
|
110
|
+
}
|
|
111
|
+
// Run Template Service preview proof gate
|
|
112
|
+
if (options.proof) {
|
|
113
|
+
await this.runProofGate(projectPath, options.screenshot);
|
|
114
|
+
}
|
|
115
|
+
// If only --diff or --proof without a preview server, exit
|
|
116
|
+
if ((options.diff || options.proof) && options.open === false) {
|
|
117
|
+
this.api.destroy();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Detect entry file
|
|
121
|
+
const entryFile = this.detectEntryFile(projectPath, options.entry);
|
|
122
|
+
if (!entryFile) {
|
|
123
|
+
this.logger.warn('No HTML entry file found. Use --entry <file> to specify one.');
|
|
124
|
+
if (!options.diff && !options.proof) {
|
|
125
|
+
this.api.destroy();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.api.destroy();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Start local preview server
|
|
132
|
+
const port = options.port || (await this.findAvailablePort(3500));
|
|
133
|
+
await this.startServer(projectPath, entryFile, port, options.open !== false);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Detect the HTML entry file in the project
|
|
137
|
+
*/
|
|
138
|
+
detectEntryFile(projectPath, specified) {
|
|
139
|
+
if (specified) {
|
|
140
|
+
const fullPath = path.join(projectPath, specified);
|
|
141
|
+
if (fs.existsSync(fullPath))
|
|
142
|
+
return specified;
|
|
143
|
+
this.logger.warn(`Specified entry file not found: ${specified}`);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
// Common entry point candidates
|
|
147
|
+
const candidates = [
|
|
148
|
+
'index.html',
|
|
149
|
+
'public/index.html',
|
|
150
|
+
'dist/index.html',
|
|
151
|
+
'build/index.html',
|
|
152
|
+
'src/index.html',
|
|
153
|
+
'out/index.html',
|
|
154
|
+
];
|
|
155
|
+
for (const candidate of candidates) {
|
|
156
|
+
if (fs.existsSync(path.join(projectPath, candidate))) {
|
|
157
|
+
return candidate;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Fallback: find any .html file at root
|
|
161
|
+
try {
|
|
162
|
+
const rootFiles = fs.readdirSync(projectPath);
|
|
163
|
+
const htmlFile = rootFiles.find(f => f.endsWith('.html'));
|
|
164
|
+
if (htmlFile)
|
|
165
|
+
return htmlFile;
|
|
166
|
+
}
|
|
167
|
+
catch { /* ignore */ }
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Find an available port starting from the given number
|
|
172
|
+
*/
|
|
173
|
+
findAvailablePort(startPort) {
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
const testServer = http.createServer();
|
|
176
|
+
testServer.on('error', () => {
|
|
177
|
+
resolve(this.findAvailablePort(startPort + 1));
|
|
178
|
+
});
|
|
179
|
+
testServer.listen(startPort, () => {
|
|
180
|
+
testServer.close(() => resolve(startPort));
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Start a local HTTP server for preview
|
|
186
|
+
*/
|
|
187
|
+
async startServer(projectPath, entryFile, port, autoOpen) {
|
|
188
|
+
return new Promise((resolve) => {
|
|
189
|
+
this.server = http.createServer((req, res) => {
|
|
190
|
+
// Sanitize URL to prevent path traversal
|
|
191
|
+
const urlPath = decodeURIComponent(new URL(req.url || '/', `http://localhost:${port}`).pathname);
|
|
192
|
+
const safePath = path.normalize(urlPath).replace(/^(\.\.[/\\])+/, '');
|
|
193
|
+
let filePath = path.join(projectPath, safePath);
|
|
194
|
+
// Ensure the resolved path is within the project directory
|
|
195
|
+
const resolvedPath = path.resolve(filePath);
|
|
196
|
+
if (!resolvedPath.startsWith(path.resolve(projectPath))) {
|
|
197
|
+
res.writeHead(403);
|
|
198
|
+
res.end('Forbidden');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// Directory → serve index.html
|
|
202
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
|
203
|
+
filePath = path.join(filePath, 'index.html');
|
|
204
|
+
}
|
|
205
|
+
// SPA fallback
|
|
206
|
+
if (!fs.existsSync(filePath)) {
|
|
207
|
+
const spaFallback = path.join(projectPath, entryFile);
|
|
208
|
+
if (fs.existsSync(spaFallback)) {
|
|
209
|
+
filePath = spaFallback;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
res.writeHead(404);
|
|
213
|
+
res.end('Not found');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
218
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
219
|
+
try {
|
|
220
|
+
const content = fs.readFileSync(filePath);
|
|
221
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
222
|
+
res.end(content);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
res.writeHead(500);
|
|
226
|
+
res.end('Internal server error');
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
this.server.listen(port, () => {
|
|
230
|
+
const url = `http://localhost:${port}/${entryFile}`;
|
|
231
|
+
console.log(chalk_1.default.green(` ${logger_js_1.CH.success} Preview server running`));
|
|
232
|
+
console.log(chalk_1.default.gray(` URL: `) + chalk_1.default.cyan.underline(url));
|
|
233
|
+
console.log(chalk_1.default.gray(` Entry: ${entryFile}`));
|
|
234
|
+
console.log(chalk_1.default.gray(` Press Ctrl+C to stop`));
|
|
235
|
+
console.log();
|
|
236
|
+
if (autoOpen) {
|
|
237
|
+
this.openBrowser(url);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
// Handle graceful shutdown
|
|
241
|
+
const shutdown = () => {
|
|
242
|
+
console.log(chalk_1.default.gray('\n Stopping preview server...'));
|
|
243
|
+
this.server?.close();
|
|
244
|
+
this.api.destroy();
|
|
245
|
+
resolve();
|
|
246
|
+
};
|
|
247
|
+
process.on('SIGINT', shutdown);
|
|
248
|
+
process.on('SIGTERM', shutdown);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Open URL in default browser
|
|
253
|
+
*/
|
|
254
|
+
openBrowser(url) {
|
|
255
|
+
const { exec } = require('child_process');
|
|
256
|
+
const platform = process.platform;
|
|
257
|
+
const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
|
|
258
|
+
exec(`${cmd} ${url}`, (err) => {
|
|
259
|
+
if (err) {
|
|
260
|
+
this.logger.debug(`Could not auto-open browser: ${err.message}`);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Show consolidated diff of recent agent changes using git
|
|
266
|
+
*/
|
|
267
|
+
async showConsolidatedDiff(projectPath) {
|
|
268
|
+
console.log(chalk_1.default.bold.white(` ${logger_js_1.CH.hLine.repeat(3)} Change Summary ${logger_js_1.CH.hLine.repeat(43)}`));
|
|
269
|
+
console.log();
|
|
270
|
+
const proofDir = path.join(projectPath, '.vigthoria', 'proof', 'preview');
|
|
271
|
+
const hasProof = fs.existsSync(proofDir);
|
|
272
|
+
// Try git diff first (most reliable)
|
|
273
|
+
try {
|
|
274
|
+
const { execSync } = require('child_process');
|
|
275
|
+
const isGit = fs.existsSync(path.join(projectPath, '.git'));
|
|
276
|
+
if (isGit) {
|
|
277
|
+
// Get list of changed files (unstaged + staged)
|
|
278
|
+
const statusOutput = execSync('git status --porcelain', { cwd: projectPath, encoding: 'utf-8' }).trim();
|
|
279
|
+
if (!statusOutput) {
|
|
280
|
+
console.log(chalk_1.default.gray(' No changes detected (working tree clean).'));
|
|
281
|
+
console.log();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const changedLines = statusOutput.split('\n').filter(Boolean);
|
|
285
|
+
const created = [];
|
|
286
|
+
const modified = [];
|
|
287
|
+
const deleted = [];
|
|
288
|
+
for (const line of changedLines) {
|
|
289
|
+
const status = line.substring(0, 2).trim();
|
|
290
|
+
const filePath = line.substring(3);
|
|
291
|
+
if (status === '??' || status === 'A')
|
|
292
|
+
created.push(filePath);
|
|
293
|
+
else if (status === 'D')
|
|
294
|
+
deleted.push(filePath);
|
|
295
|
+
else
|
|
296
|
+
modified.push(filePath);
|
|
297
|
+
}
|
|
298
|
+
// Summary header
|
|
299
|
+
const total = created.length + modified.length + deleted.length;
|
|
300
|
+
console.log(chalk_1.default.white(` ${total} file${total !== 1 ? 's' : ''} changed:`));
|
|
301
|
+
for (const f of created)
|
|
302
|
+
console.log(chalk_1.default.green(` + ${f}`));
|
|
303
|
+
for (const f of modified)
|
|
304
|
+
console.log(chalk_1.default.yellow(` ~ ${f}`));
|
|
305
|
+
for (const f of deleted)
|
|
306
|
+
console.log(chalk_1.default.red(` - ${f}`));
|
|
307
|
+
console.log();
|
|
308
|
+
// Show unified diffs for modified and created files (limit to keep terminal manageable)
|
|
309
|
+
const diffTargets = [...modified, ...created].slice(0, 20);
|
|
310
|
+
for (const relPath of diffTargets) {
|
|
311
|
+
const absPath = path.join(projectPath, relPath);
|
|
312
|
+
if (!fs.existsSync(absPath))
|
|
313
|
+
continue;
|
|
314
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
315
|
+
const textExts = ['.html', '.css', '.js', '.ts', '.jsx', '.tsx', '.json', '.md', '.yml', '.yaml', '.txt', '.xml', '.svg', '.py', '.sh', '.env'];
|
|
316
|
+
if (!textExts.includes(ext))
|
|
317
|
+
continue;
|
|
318
|
+
try {
|
|
319
|
+
const newContent = fs.readFileSync(absPath, 'utf-8');
|
|
320
|
+
let oldContent = '';
|
|
321
|
+
if (modified.includes(relPath)) {
|
|
322
|
+
try {
|
|
323
|
+
oldContent = execSync(`git show HEAD:${relPath}`, { cwd: projectPath, encoding: 'utf-8' });
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
// File is new or not tracked
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const patch = (0, diff_1.structuredPatch)(relPath, relPath, oldContent, newContent, 'before', 'after', { context: 3 });
|
|
330
|
+
if (patch.hunks.length === 0)
|
|
331
|
+
continue;
|
|
332
|
+
console.log(chalk_1.default.bold.white(` ${logger_js_1.CH.hLine.repeat(3)} ${relPath} ${logger_js_1.CH.hLine.repeat(Math.max(1, 50 - relPath.length))}`));
|
|
333
|
+
console.log(chalk_1.default.gray(` --- a/${relPath}`));
|
|
334
|
+
console.log(chalk_1.default.gray(` +++ b/${relPath}`));
|
|
335
|
+
for (const hunk of patch.hunks) {
|
|
336
|
+
console.log(chalk_1.default.cyan(` @@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`));
|
|
337
|
+
for (const line of hunk.lines) {
|
|
338
|
+
if (line.startsWith('+')) {
|
|
339
|
+
console.log(chalk_1.default.green(` ${line}`));
|
|
340
|
+
}
|
|
341
|
+
else if (line.startsWith('-')) {
|
|
342
|
+
console.log(chalk_1.default.red(` ${line}`));
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
console.log(chalk_1.default.gray(` ${line}`));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
console.log();
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// Skip files that can't be diffed
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (diffTargets.length < modified.length + created.length) {
|
|
356
|
+
console.log(chalk_1.default.gray(` ... and ${modified.length + created.length - diffTargets.length} more files`));
|
|
357
|
+
console.log();
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// git not available, fall through
|
|
364
|
+
}
|
|
365
|
+
// Fallback: list .vigthoria/proof manifests
|
|
366
|
+
if (hasProof) {
|
|
367
|
+
const manifests = fs.readdirSync(proofDir)
|
|
368
|
+
.filter(f => f.endsWith('.json'))
|
|
369
|
+
.sort()
|
|
370
|
+
.reverse()
|
|
371
|
+
.slice(0, 5);
|
|
372
|
+
if (manifests.length > 0) {
|
|
373
|
+
console.log(chalk_1.default.white(' Recent proof bundles:'));
|
|
374
|
+
for (const m of manifests) {
|
|
375
|
+
try {
|
|
376
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(proofDir, m), 'utf-8'));
|
|
377
|
+
const passed = manifest.previewGate?.passed ? chalk_1.default.green('passed') : chalk_1.default.red('failed');
|
|
378
|
+
console.log(chalk_1.default.gray(` ${manifest.createdAt || m} `) + passed + chalk_1.default.gray(` ${manifest.entryPath || '-'}`));
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
console.log(chalk_1.default.gray(` ${m}`));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
console.log();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
console.log(chalk_1.default.gray(' No git history or proof bundles found.'));
|
|
389
|
+
console.log(chalk_1.default.gray(' Run an agent task first to generate changes.'));
|
|
390
|
+
console.log();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Run Template Service preview gate and persist proof bundle
|
|
395
|
+
*/
|
|
396
|
+
async runProofGate(projectPath, captureScreenshot) {
|
|
397
|
+
const spinner = (0, logger_js_1.createSpinner)('Running preview proof gate...').start();
|
|
398
|
+
try {
|
|
399
|
+
const result = await this.api.runTemplateServicePreviewGate('', {
|
|
400
|
+
workspacePath: projectPath,
|
|
401
|
+
projectPath: projectPath,
|
|
402
|
+
targetPath: projectPath,
|
|
403
|
+
});
|
|
404
|
+
spinner.stop();
|
|
405
|
+
console.log(chalk_1.default.bold.white(` ${logger_js_1.CH.hLine.repeat(3)} Preview Proof Gate ${logger_js_1.CH.hLine.repeat(39)}`));
|
|
406
|
+
console.log();
|
|
407
|
+
if (!result.required) {
|
|
408
|
+
console.log(chalk_1.default.gray(' Preview gate: not required (no frontend artifacts detected)'));
|
|
409
|
+
console.log();
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const statusIcon = result.passed ? chalk_1.default.green(logger_js_1.CH.success) : chalk_1.default.red(logger_js_1.CH.error);
|
|
413
|
+
console.log(` ${statusIcon} Preview gate: ${result.passed ? chalk_1.default.green('PASSED') : chalk_1.default.red('FAILED')}`);
|
|
414
|
+
if (result.error) {
|
|
415
|
+
console.log(chalk_1.default.yellow(` Error: ${result.error}`));
|
|
416
|
+
}
|
|
417
|
+
// Show modes
|
|
418
|
+
if (result.modes) {
|
|
419
|
+
const modes = result.modes;
|
|
420
|
+
console.log();
|
|
421
|
+
if (modes.design) {
|
|
422
|
+
const designStatus = modes.design.ready ? chalk_1.default.green('ready') : chalk_1.default.gray('not ready');
|
|
423
|
+
console.log(chalk_1.default.gray(` Design mode: `) + designStatus);
|
|
424
|
+
if (modes.design.devices) {
|
|
425
|
+
console.log(chalk_1.default.gray(` Devices: ${modes.design.devices.join(', ')}`));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (modes.live) {
|
|
429
|
+
const liveStatus = modes.live.ready ? chalk_1.default.green('ready') : chalk_1.default.gray('not ready');
|
|
430
|
+
console.log(chalk_1.default.gray(` Live mode: `) + liveStatus);
|
|
431
|
+
if (modes.live.entryPoint) {
|
|
432
|
+
console.log(chalk_1.default.gray(` Entry: ${modes.live.entryPoint}`));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (modes.production) {
|
|
436
|
+
const prodStatus = modes.production.ready ? chalk_1.default.green('ready') : chalk_1.default.gray('not ready');
|
|
437
|
+
console.log(chalk_1.default.gray(` Production mode: `) + prodStatus);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Show summary
|
|
441
|
+
if (result.summary) {
|
|
442
|
+
console.log();
|
|
443
|
+
const summary = result.summary;
|
|
444
|
+
if (summary.hasViewportMeta !== undefined) {
|
|
445
|
+
console.log(chalk_1.default.gray(` Viewport meta: `) + (summary.hasViewportMeta ? chalk_1.default.green('yes') : chalk_1.default.yellow('missing')));
|
|
446
|
+
}
|
|
447
|
+
if (summary.hasResponsiveSignals !== undefined) {
|
|
448
|
+
console.log(chalk_1.default.gray(` Responsive CSS: `) + (summary.hasResponsiveSignals ? chalk_1.default.green('yes') : chalk_1.default.gray('no')));
|
|
449
|
+
}
|
|
450
|
+
if (summary.hasInteractiveSignals !== undefined) {
|
|
451
|
+
console.log(chalk_1.default.gray(` Interactive JS: `) + (summary.hasInteractiveSignals ? chalk_1.default.green('yes') : chalk_1.default.gray('no')));
|
|
452
|
+
}
|
|
453
|
+
if (typeof summary.sectionCount === 'number') {
|
|
454
|
+
console.log(chalk_1.default.gray(` Section count: ${summary.sectionCount}`));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// Show artifacts
|
|
458
|
+
if (result.artifacts) {
|
|
459
|
+
console.log();
|
|
460
|
+
if (result.artifacts.manifestPath) {
|
|
461
|
+
console.log(chalk_1.default.gray(` Manifest: ${result.artifacts.manifestPath}`));
|
|
462
|
+
}
|
|
463
|
+
if (result.artifacts.screenshotCaptured && result.artifacts.screenshotPath) {
|
|
464
|
+
console.log(chalk_1.default.gray(` Screenshot: ${result.artifacts.screenshotPath}`));
|
|
465
|
+
}
|
|
466
|
+
if (result.artifacts.previewFileUrl) {
|
|
467
|
+
console.log(chalk_1.default.gray(` File URL: `) + chalk_1.default.cyan.underline(result.artifacts.previewFileUrl));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
console.log();
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
spinner.stop();
|
|
474
|
+
this.logger.error(`Preview proof gate failed: ${error?.message || String(error)}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
exports.PreviewCommand = PreviewCommand;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Config } from '../utils/config.js';
|
|
2
|
+
import { Logger } from '../utils/logger.js';
|
|
3
|
+
interface ReplayOptions {
|
|
4
|
+
speed?: number;
|
|
5
|
+
json?: boolean;
|
|
6
|
+
project?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class ReplayCommand {
|
|
9
|
+
private config;
|
|
10
|
+
private logger;
|
|
11
|
+
constructor(config: Config, logger: Logger);
|
|
12
|
+
private getHeaders;
|
|
13
|
+
private getBaseUrl;
|
|
14
|
+
private sleep;
|
|
15
|
+
run(runId: string, options: ReplayOptions): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ReplayCommand = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* replay.ts — Replay events from a V3 agent run step-by-step.
|
|
9
|
+
*/
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
12
|
+
class ReplayCommand {
|
|
13
|
+
config;
|
|
14
|
+
logger;
|
|
15
|
+
constructor(config, logger) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
}
|
|
19
|
+
getHeaders() {
|
|
20
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
21
|
+
const token = process.env.VIGTHORIA_TOKEN ||
|
|
22
|
+
process.env.VIGTHORIA_AUTH_TOKEN ||
|
|
23
|
+
this.config.get('authToken');
|
|
24
|
+
if (token) {
|
|
25
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
26
|
+
headers['Cookie'] = `vigthoria-auth-token=${token}`;
|
|
27
|
+
}
|
|
28
|
+
const serviceKey = process.env.VIGTHORIA_V3_SERVICE_KEY;
|
|
29
|
+
if (serviceKey)
|
|
30
|
+
headers['X-Service-Key'] = serviceKey;
|
|
31
|
+
return headers;
|
|
32
|
+
}
|
|
33
|
+
getBaseUrl() {
|
|
34
|
+
return (process.env.VIGTHORIA_V3_AGENT_URL ||
|
|
35
|
+
process.env.V3_AGENT_URL ||
|
|
36
|
+
'http://127.0.0.1:8030');
|
|
37
|
+
}
|
|
38
|
+
sleep(ms) {
|
|
39
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
40
|
+
}
|
|
41
|
+
async run(runId, options) {
|
|
42
|
+
const speed = options.speed || 200;
|
|
43
|
+
const project = options.project || process.cwd();
|
|
44
|
+
const spinner = (0, logger_js_1.createSpinner)(`Loading events for run ${runId}...`).start();
|
|
45
|
+
try {
|
|
46
|
+
const baseUrl = this.getBaseUrl();
|
|
47
|
+
const params = new URLSearchParams({ workspace_root: project });
|
|
48
|
+
const resp = await fetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/events?${params}`, {
|
|
49
|
+
headers: this.getHeaders(),
|
|
50
|
+
});
|
|
51
|
+
if (!resp.ok) {
|
|
52
|
+
spinner.stop();
|
|
53
|
+
if (resp.status === 404) {
|
|
54
|
+
this.logger.error(`No event log found for run ${runId}. Events are only recorded for runs made after the replay system was deployed.`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.logger.error(`Failed to fetch events: ${resp.status} ${resp.statusText}`);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const data = (await resp.json());
|
|
62
|
+
spinner.stop();
|
|
63
|
+
if (options.json) {
|
|
64
|
+
console.log(JSON.stringify(data, null, 2));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk_1.default.bold(`\n${logger_js_1.CH.success} Replaying run ${chalk_1.default.cyan(runId)} (${data.count} events)\n`));
|
|
68
|
+
let toolCallNum = 0;
|
|
69
|
+
for (let i = 0; i < data.events.length; i++) {
|
|
70
|
+
const evt = data.events[i];
|
|
71
|
+
const type = evt.type || 'unknown';
|
|
72
|
+
const idx = chalk_1.default.dim(`[${String(i + 1).padStart(3)}/${data.count}]`);
|
|
73
|
+
switch (type) {
|
|
74
|
+
case 'context':
|
|
75
|
+
console.log(`${idx} ${chalk_1.default.blue('context')} workspace=${evt.workspace_root || '?'}`);
|
|
76
|
+
break;
|
|
77
|
+
case 'start':
|
|
78
|
+
console.log(`${idx} ${chalk_1.default.green('▶ START')} task=${evt.task_id || '?'} ${evt.continuation ? chalk_1.default.dim(`(continuation #${evt.continuation})`) : ''}`);
|
|
79
|
+
break;
|
|
80
|
+
case 'thinking':
|
|
81
|
+
console.log(`${idx} ${chalk_1.default.dim('💭 thinking')} ${(evt.content || '').substring(0, 100)}`);
|
|
82
|
+
break;
|
|
83
|
+
case 'plan':
|
|
84
|
+
const taskCount = evt.tasks?.length || 0;
|
|
85
|
+
console.log(`${idx} ${chalk_1.default.magenta('📋 PLAN')} ${taskCount} tasks`);
|
|
86
|
+
if (evt.tasks) {
|
|
87
|
+
for (const t of evt.tasks.slice(0, 5)) {
|
|
88
|
+
console.log(` ${chalk_1.default.dim('├')} ${t.id || '?'}: ${(t.title || '').substring(0, 60)}`);
|
|
89
|
+
}
|
|
90
|
+
if (taskCount > 5)
|
|
91
|
+
console.log(` ${chalk_1.default.dim('└')} ...and ${taskCount - 5} more`);
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
case 'tool_call':
|
|
95
|
+
toolCallNum++;
|
|
96
|
+
console.log(`${idx} ${chalk_1.default.yellow(`🔧 tool_call #${toolCallNum}`)} ${chalk_1.default.bold(evt.name || '?')}(${JSON.stringify(evt.arguments || {}).substring(0, 80)})`);
|
|
97
|
+
break;
|
|
98
|
+
case 'tool_result': {
|
|
99
|
+
const success = evt.success !== false;
|
|
100
|
+
const icon = success ? chalk_1.default.green('✓') : chalk_1.default.red('✗');
|
|
101
|
+
const output = (evt.output || '').substring(0, 120);
|
|
102
|
+
console.log(`${idx} ${icon} ${evt.name || '?'}: ${chalk_1.default.dim(output)}`);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case 'message':
|
|
106
|
+
console.log(`${idx} ${chalk_1.default.cyan('💬 message')} ${(evt.content || '').substring(0, 120)}`);
|
|
107
|
+
break;
|
|
108
|
+
case 'complete': {
|
|
109
|
+
const summary = (evt.summary || '').substring(0, 200);
|
|
110
|
+
const seal = evt.seal_score ? ` [${evt.seal_score.tier} ${evt.seal_score.overall}]` : '';
|
|
111
|
+
console.log(`${idx} ${chalk_1.default.green(`✅ COMPLETE`)}${chalk_1.default.yellow(seal)} ${evt.iterations || '?'} iterations, ${evt.tool_calls || '?'} tool calls`);
|
|
112
|
+
if (summary)
|
|
113
|
+
console.log(` ${chalk_1.default.dim(summary)}`);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case 'error':
|
|
117
|
+
console.log(`${idx} ${chalk_1.default.red('❌ ERROR')} ${(evt.message || '').substring(0, 200)}`);
|
|
118
|
+
if (evt.checkpointed)
|
|
119
|
+
console.log(` ${chalk_1.default.yellow('Checkpointed — can be continued or forked')}`);
|
|
120
|
+
break;
|
|
121
|
+
case 'file_mutation':
|
|
122
|
+
console.log(`${idx} ${chalk_1.default.blue('📁 file_mutation')} ${evt.kind || '?'} ${evt.path || '?'}`);
|
|
123
|
+
break;
|
|
124
|
+
default:
|
|
125
|
+
console.log(`${idx} ${chalk_1.default.dim(type)} ${JSON.stringify(evt).substring(0, 100)}`);
|
|
126
|
+
}
|
|
127
|
+
// Delay between events for replay effect
|
|
128
|
+
if (i < data.events.length - 1) {
|
|
129
|
+
await this.sleep(speed);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
console.log(chalk_1.default.bold(`\n${logger_js_1.CH.success} Replay complete (${data.count} events)\n`));
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
spinner.stop();
|
|
136
|
+
this.logger.error(`Replay error: ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.ReplayCommand = ReplayCommand;
|