vigthoria-cli 1.12.2 → 1.13.6

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.
@@ -1,3 +1,4 @@
1
+ import type { Command } from 'commander';
1
2
  import { Config } from '../utils/config.js';
2
3
  import { Logger } from '../utils/logger.js';
3
4
  import { ChatMessage } from '../utils/api.js';
@@ -21,6 +22,7 @@ interface ChatOptions {
21
22
  continue?: boolean;
22
23
  }
23
24
  export declare class ChatCommand {
25
+ private commandProgram?;
24
26
  private config;
25
27
  private logger;
26
28
  private api;
@@ -144,7 +146,7 @@ export declare class ChatCommand {
144
146
  private writeAgentActivityLine;
145
147
  private updateV3AgentSpinner;
146
148
  private updateOperatorSpinner;
147
- constructor(config: Config, logger: Logger);
149
+ constructor(config: Config, logger: Logger, commandProgram?: Command);
148
150
  run(options: ChatOptions): Promise<void>;
149
151
  /** Handle an inbound admin command from the Commando Bridge. */
150
152
  private handleAdminCommand;
@@ -15,6 +15,7 @@ import { WorkspaceBrainService } from '../utils/workspace-brain-service.js';
15
15
  import { buildPersonaOverlay, normalizePersonaMode } from '../utils/persona.js';
16
16
  import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session-menu.js';
17
17
  import { V4Command } from './v4.js';
18
+ import { renderDynamicHelp } from '../utils/command-menu.js';
18
19
  import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
19
20
  import { looksLikeMarkdownReport, renderMarkdownToTerminal, summarizeMarkdownReport, } from '../utils/terminalMarkdown.js';
20
21
  import { emitDeckEvent, isDeckModeEnabled } from '../utils/deckEvents.js';
@@ -79,6 +80,7 @@ function emitAgentJsonOutput(payload) {
79
80
  }
80
81
  }
81
82
  export class ChatCommand {
83
+ commandProgram;
82
84
  config;
83
85
  logger;
84
86
  api;
@@ -464,6 +466,8 @@ export class ChatCommand {
464
466
  return buildPersonaOverlay(this.getActivePersonaMode(), this.getLastUserPrompt());
465
467
  }
466
468
  isDiagnosticPrompt(prompt) {
469
+ if (promptRequiresWorkspaceChanges(prompt))
470
+ return false;
467
471
  return /(startup|start up|won'?t start|doesn'?t start|crash|crashes|error|errors|failing|fails|issue|issues|bug|bugs|diagnos|debug|runtime|log|logs|exception|traceback|stack trace|yaml|blocking|blocker)/i.test(prompt);
468
472
  }
469
473
  /**
@@ -471,6 +475,8 @@ export class ChatCommand {
471
475
  * question — these should use analysis_only workflow, not full_autonomy.
472
476
  */
473
477
  isAnalysisLookupPrompt(prompt) {
478
+ if (promptRequiresWorkspaceChanges(prompt))
479
+ return false;
474
480
  const trimmed = prompt.trim();
475
481
  if (/^(what|which|where|how many|who|find|list|show|check|inspect|analyze|analyse|audit|explain|describe|summarize|summarise|review|overview|count|read|look at|tell me|locate|search for|does .* exist)/i.test(trimmed)) {
476
482
  return true;
@@ -921,7 +927,13 @@ export class ChatCommand {
921
927
  }
922
928
  terminalColumns() {
923
929
  const raw = Number(process.stdout.columns || process.stderr.columns || 80);
924
- return Number.isFinite(raw) && raw > 0 ? raw : 80;
930
+ const safe = Number.isFinite(raw) && raw > 0 ? raw : 80;
931
+ // process.stdout.columns is not always a reliable indicator of how many
932
+ // columns actually render visually (seen mismatching badly in some
933
+ // embedded/split-pane terminal hosts on Windows) -- cap at a
934
+ // conservative width so wrapped/fitted text never overflows even when
935
+ // the reported column count is larger than the real usable width.
936
+ return Math.min(safe, 60);
925
937
  }
926
938
  fitTerminalText(text, reserve = 0) {
927
939
  const width = Math.max(24, this.terminalColumns() - reserve);
@@ -1463,9 +1475,10 @@ export class ChatCommand {
1463
1475
  return;
1464
1476
  }
1465
1477
  }
1466
- constructor(config, logger) {
1478
+ constructor(config, logger, commandProgram) {
1467
1479
  this.config = config;
1468
1480
  this.logger = logger;
1481
+ this.commandProgram = commandProgram;
1469
1482
  this.api = new APIClient(config, logger);
1470
1483
  this.sessionManager = new SessionManager();
1471
1484
  }
@@ -2326,38 +2339,16 @@ export class ChatCommand {
2326
2339
  : '';
2327
2340
  this.logger.debug(`Agent route: ${r.path} / ${r.taskKind} [${r.source}]${routerNote} — ${r.reason}`);
2328
2341
  }
2329
- const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(resolvedPrompt);
2330
- const handledByTemplateInstant = await this.tryTemplateInstantPath(resolvedPrompt);
2331
- if (handledByTemplateInstant) {
2332
- this.saveSession();
2333
- return;
2334
- }
2335
- const handledByDirectFileFlow = await this.tryDirectSingleFileFlow(resolvedPrompt);
2336
- if (handledByDirectFileFlow) {
2337
- this.saveSession();
2338
- return;
2339
- }
2340
- // Prime the message context with the target file when the direct-file flow was
2341
- // bypassed (e.g. HTML files routed to V3) so the local agent loop has file
2342
- // awareness and the ⚙ Executing: read_file banner is always emitted.
2343
- await this.primeBypassedTargetFileContext(resolvedPrompt);
2344
- if (this.shouldPreferLocalAgentLoop(resolvedPrompt)) {
2345
- await this.runLocalAgentLoop(resolvedPrompt);
2346
- return;
2347
- }
2348
2342
  const handledByV3Workflow = await this.tryV3AgentWorkflow(resolvedPrompt);
2349
2343
  if (handledByV3Workflow) {
2350
2344
  this.saveSession();
2351
2345
  return;
2352
2346
  }
2353
- if (requiresV3Workflow) {
2354
- const errorMessage = 'This task requires the V3 agent workflow and the CLI will not fall back to the legacy local agent loop.';
2355
- this.logger.error(errorMessage);
2356
- this.messages.push({ role: 'assistant', content: errorMessage });
2357
- this.saveSession();
2358
- return;
2359
- }
2360
- await this.runLocalAgentLoop(resolvedPrompt);
2347
+ const errorMessage = 'The V3 planner/executor workflow is unavailable. Refusing to fall back to legacy template, direct-file, or local write paths.';
2348
+ this.logger.error(errorMessage);
2349
+ this.messages.push({ role: 'assistant', content: errorMessage });
2350
+ process.exitCode = 1;
2351
+ this.saveSession();
2361
2352
  }
2362
2353
  resolveAgentTurnPrompt(prompt) {
2363
2354
  if (isBuiltContinuePrompt(prompt) || isBuiltRetryPrompt(prompt) || isBuiltWriteConfirmationPrompt(prompt)) {
@@ -3956,20 +3947,29 @@ export class ChatCommand {
3956
3947
  }
3957
3948
  showHelp() {
3958
3949
  console.log('');
3959
- console.log('Commands:');
3960
- console.log(' /help Show this help');
3961
- console.log(' /exit Exit chat');
3962
- console.log(' /agent Toggle agent mode');
3963
- console.log(' /operator Toggle BMAD operator mode');
3964
- console.log(' /context Show current session and project memory');
3965
- console.log(' /memory Show Vigthoria project brain status');
3966
- console.log(' /compact Compact current session into memory summary');
3967
- console.log(' /clear Clear conversation');
3968
- console.log(' /save Save session');
3969
- console.log(' /model <name> Change model');
3970
- console.log(' /status Show the last agent run outcome');
3971
- console.log(' /retry Re-run the last failed agent task');
3972
- console.log(' /continue Ask the agent to keep working on unfinished tasks');
3950
+ console.log('Interactive chat commands:');
3951
+ const chatCommands = [
3952
+ ['/help', 'Show this dynamically generated help'],
3953
+ ['/exit, /quit', 'Exit chat'],
3954
+ ['/agent', 'Toggle Agent Mode'],
3955
+ ['/operator', 'Toggle BMAD operator mode'],
3956
+ ['/context', 'Show current session and project memory'],
3957
+ ['/memory', 'Show Vigthoria project brain status'],
3958
+ ['/compact', 'Compact current session into memory summary'],
3959
+ ['/clear', 'Clear conversation'],
3960
+ ['/save', 'Save session'],
3961
+ ['/model <name>', 'Change model'],
3962
+ ['/status', 'Show the last agent run outcome'],
3963
+ ['/retry', 'Re-run the last failed agent task'],
3964
+ ['/continue', 'Ask the agent to keep working on unfinished tasks'],
3965
+ ];
3966
+ for (const [command, description] of chatCommands) {
3967
+ console.log(` ${command.padEnd(18)} ${description}`);
3968
+ }
3969
+ if (this.commandProgram) {
3970
+ console.log('');
3971
+ console.log(renderDynamicHelp(this.commandProgram));
3972
+ }
3973
3973
  console.log('');
3974
3974
  }
3975
3975
  /**
@@ -0,0 +1,26 @@
1
+ import type { Logger } from '../utils/logger.js';
2
+ type Manager = 'npm' | 'pnpm' | 'yarn' | 'bun';
3
+ type CommonOptions = {
4
+ project?: string;
5
+ packageManager?: Manager;
6
+ };
7
+ export declare class GameCommand {
8
+ private logger;
9
+ constructor(logger: Logger);
10
+ init(input: string | undefined, o: {
11
+ engine?: string;
12
+ install?: boolean;
13
+ packageManager?: Manager;
14
+ }): Promise<void>;
15
+ run(o: CommonOptions & {
16
+ port?: number;
17
+ open?: boolean;
18
+ }): Promise<void>;
19
+ validate(o: CommonOptions & {
20
+ json?: boolean;
21
+ }): Promise<boolean>;
22
+ private project;
23
+ private sources;
24
+ private finish;
25
+ }
26
+ export {};
@@ -0,0 +1,113 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import inquirer from 'inquirer';
5
+ import { runPostWriteValidation } from '../utils/post-write-validator.js';
6
+ const NAME = /^[a-zA-Z0-9][a-zA-Z0-9 _-]{0,63}$/;
7
+ const manager = (root, requested) => {
8
+ if (requested)
9
+ return requested;
10
+ try {
11
+ const value = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).packageManager?.split('@')[0];
12
+ if (['npm', 'pnpm', 'yarn', 'bun'].includes(value))
13
+ return value;
14
+ }
15
+ catch { }
16
+ if (fs.existsSync(path.join(root, 'pnpm-lock.yaml')))
17
+ return 'pnpm';
18
+ if (fs.existsSync(path.join(root, 'yarn.lock')))
19
+ return 'yarn';
20
+ if (fs.existsSync(path.join(root, 'bun.lock')) || fs.existsSync(path.join(root, 'bun.lockb')))
21
+ return 'bun';
22
+ return 'npm';
23
+ };
24
+ const run = (pm, args, cwd) => new Promise((resolve, reject) => { const child = spawn(process.platform === 'win32' ? `${pm}.cmd` : pm, args, { cwd, stdio: 'inherit', windowsHide: true }); child.once('error', reject); child.once('exit', (code, signal) => code === 0 ? resolve() : reject(new Error(`${pm} exited with ${signal || code}`))); });
25
+ const html = `<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>VGE Game</title></head><body><canvas id="game"></canvas><div id="hud"><strong>Vigthoria Gaming Engine</strong><span>Ready</span></div><script type="module" src="/src/main.ts"></script></body></html>\n`;
26
+ const style = `*{box-sizing:border-box}html,body,#game{width:100%;height:100%;margin:0;overflow:hidden}body{background:#030510;color:#e8f7ff;font-family:Inter,system-ui,sans-serif}#game{display:block;touch-action:none}#hud{position:fixed;left:24px;top:24px;display:flex;gap:16px;padding:12px 16px;border:1px solid #00d4ff66;border-radius:12px;background:#070a18cc;backdrop-filter:blur(12px);box-shadow:0 0 32px #7c3aed33}#hud span{color:#5eead4}\n`;
27
+ const main = `import { ArcRotateCamera, Color3, Color4, DirectionalLight, Engine, HemisphericLight, MeshBuilder, PBRMaterial, Scene, ShadowGenerator, Vector3 } from '@babylonjs/core';
28
+ import '@babylonjs/loaders'; import './style.css';
29
+ const canvas=document.querySelector<HTMLCanvasElement>('#game')!; const engine=new Engine(canvas,true,{stencil:true}); const scene=new Scene(engine); scene.clearColor=new Color4(.012,.018,.05,1);
30
+ const camera=new ArcRotateCamera('camera',-Math.PI/2,Math.PI/3,12,new Vector3(0,1,0),scene); camera.attachControl(canvas,true); new HemisphericLight('sky',new Vector3(0,1,0),scene).intensity=.55;
31
+ const sun=new DirectionalLight('sun',new Vector3(-.5,-1,-.4),scene); sun.position=new Vector3(8,14,8); sun.intensity=2; const shadows=new ShadowGenerator(2048,sun); shadows.useBlurExponentialShadowMap=true;
32
+ const ground=MeshBuilder.CreateGround('ground',{width:30,height:30},scene); const gm=new PBRMaterial('ground-pbr',scene); gm.albedoColor=new Color3(.025,.06,.12); gm.metallic=.35; gm.roughness=.72; ground.material=gm; ground.receiveShadows=true;
33
+ const hero=MeshBuilder.CreateCapsule('hero',{height:2.4,radius:.55,tessellation:32},scene); hero.position.y=1.2; const hm=new PBRMaterial('hero-pbr',scene); hm.albedoColor=Color3.FromHexString('#7c3aed'); hm.metallic=.55; hm.roughness=.22; hero.material=hm; shadows.addShadowCaster(hero);
34
+ declare global { interface Window { __vgeDiagnostics: Record<string,unknown> } }
35
+ engine.runRenderLoop(()=>{hero.rotation.y+=engine.getDeltaTime()*.00035; scene.render(); window.__vgeDiagnostics={engineReady:true,sceneReady:true,activeCamera:camera.name,fps:Math.round(engine.getFps()),frameCount:Number(window.__vgeDiagnostics?.frameCount||0)+1,meshCount:scene.meshes.length,materialCount:scene.materials.length,assetErrors:[],consoleErrors:[]};}); addEventListener('resize',()=>engine.resize());\n`;
36
+ export class GameCommand {
37
+ logger;
38
+ constructor(logger) {
39
+ this.logger = logger;
40
+ }
41
+ async init(input, o) {
42
+ if (String(o.engine || 'babylon').toLowerCase() !== 'babylon')
43
+ throw new Error('Unsupported engine. Use babylon.');
44
+ let name = input?.trim();
45
+ if (!name)
46
+ name = (await inquirer.prompt([{ type: 'input', name: 'name', message: 'Game name:', validate: v => NAME.test(String(v).trim()) || 'Use 1-64 letters, numbers, spaces, dashes, or underscores.' }])).name.trim();
47
+ if (!NAME.test(name))
48
+ throw new Error('Invalid game name.');
49
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
50
+ const root = path.resolve(process.cwd(), slug);
51
+ if (fs.existsSync(root) && fs.readdirSync(root).length)
52
+ throw new Error(`Target directory is not empty: ${root}`);
53
+ fs.mkdirSync(path.join(root, 'src'), { recursive: true });
54
+ const pkg = { name: slug, version: '0.1.0', private: true, type: 'module', vigthoria: { kind: 'game', engine: 'babylon', renderTransport: 'client-state', schemaVersion: 1 }, scripts: { dev: 'vite', build: 'tsc --noEmit && vite build', preview: 'vite preview' }, dependencies: { '@babylonjs/core': '7.54.3', '@babylonjs/loaders': '7.54.3' }, devDependencies: { typescript: '5.8.3', vite: '6.1.0' } };
55
+ fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
56
+ fs.writeFileSync(path.join(root, 'index.html'), html);
57
+ fs.writeFileSync(path.join(root, 'src/main.ts'), main);
58
+ fs.writeFileSync(path.join(root, 'src/style.css'), style);
59
+ fs.writeFileSync(path.join(root, '.gitignore'), 'node_modules\ndist\n.vigthoria\n');
60
+ fs.writeFileSync(path.join(root, 'tsconfig.json'), JSON.stringify({ compilerOptions: { target: 'ES2022', module: 'ESNext', lib: ['ES2022', 'DOM'], skipLibCheck: true, moduleResolution: 'Bundler', isolatedModules: true, noEmit: true, strict: true }, include: ['src'] }, null, 2) + '\n');
61
+ if (o.install !== false)
62
+ await run(manager(root, o.packageManager), ['install'], root);
63
+ this.logger.success(`Babylon/VGE game created: ${root}`);
64
+ this.logger.info(`Next: cd ${slug} && vigthoria game run`);
65
+ }
66
+ async run(o) { const root = path.resolve(o.project || process.cwd()); this.project(root); const args = ['run', 'dev', '--', '--host', '127.0.0.1']; if (o.port)
67
+ args.push('--port', String(o.port)); if (o.open !== false)
68
+ args.push('--open'); await run(manager(root, o.packageManager), args, root); }
69
+ async validate(o) {
70
+ const root = path.resolve(o.project || process.cwd());
71
+ const checks = [];
72
+ let pkg;
73
+ try {
74
+ pkg = this.project(root);
75
+ checks.push({ check: 'package.json', passed: true });
76
+ }
77
+ catch (e) {
78
+ checks.push({ check: 'package.json', passed: false, detail: e.message });
79
+ return this.finish(checks, o.json);
80
+ }
81
+ const source = this.sources(root);
82
+ checks.push({ check: 'Babylon client-state metadata', passed: pkg.vigthoria?.engine === 'babylon' && pkg.vigthoria?.renderTransport === 'client-state' }, { check: 'Pinned local Babylon dependencies', passed: /^\d/.test(pkg.dependencies?.['@babylonjs/core'] || '') && /^\d/.test(pkg.dependencies?.['@babylonjs/loaders'] || '') }, { check: 'No CDN engine imports', passed: !/https?:\/\/(cdn|unpkg|jsdelivr)[^\s"']*(babylon|three)/i.test(source) }, { check: 'Runtime diagnostics', passed: source.includes('__vgeDiagnostics') }, { check: 'Client source entry', passed: fs.existsSync(path.join(root, 'src/main.ts')) || fs.existsSync(path.join(root, 'src/main.js')) });
83
+ try {
84
+ await run(manager(root, o.packageManager), ['run', 'build'], root);
85
+ checks.push({ check: 'Production build', passed: true });
86
+ }
87
+ catch (e) {
88
+ checks.push({ check: 'Production build', passed: false, detail: e.message });
89
+ }
90
+ for (const result of await runPostWriteValidation(root))
91
+ checks.push({ check: result.tool, passed: result.passed, detail: result.output || undefined });
92
+ return this.finish(checks, o.json);
93
+ }
94
+ project(root) { if (!fs.existsSync(root) || !fs.statSync(root).isDirectory())
95
+ throw new Error(`Project directory not found: ${root}`); const file = path.join(root, 'package.json'); if (!fs.existsSync(file))
96
+ throw new Error('package.json not found'); const pkg = JSON.parse(fs.readFileSync(file, 'utf8')); if (!pkg.scripts?.dev || !pkg.scripts?.build)
97
+ throw new Error('Game requires dev and build scripts'); return pkg; }
98
+ sources(root) { const files = []; const walk = (d) => { for (const e of fs.readdirSync(d, { withFileTypes: true })) {
99
+ if (['node_modules', 'dist', '.git'].includes(e.name))
100
+ continue;
101
+ const f = path.join(d, e.name);
102
+ if (e.isDirectory())
103
+ walk(f);
104
+ else if (/\.(html|[cm]?[jt]sx?)$/.test(e.name))
105
+ files.push(f);
106
+ } }; walk(root); return files.map(f => fs.readFileSync(f, 'utf8')).join('\n'); }
107
+ finish(checks, json = false) { const passed = checks.every(x => x.passed); if (json)
108
+ console.log(JSON.stringify({ passed, checks }, null, 2));
109
+ else
110
+ for (const c of checks)
111
+ (c.passed ? this.logger.success.bind(this.logger) : this.logger.error.bind(this.logger))(`${c.check}${c.detail ? `: ${c.detail.trim().slice(0, 500)}` : ''}`); if (!passed)
112
+ process.exitCode = 1; return passed; }
113
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Vigthoria CLI - Repo Commands
3
3
  *
4
- * Push and pull projects to/from Vigthoria Repository
4
+ * Push and pull projects to/from Vigthoria Community Repository
5
5
  * Enables version control and project sharing through the Vigthoria platform
6
6
  *
7
7
  * Usage:
@@ -21,6 +21,7 @@ interface PushOptions {
21
21
  description?: string;
22
22
  force?: boolean;
23
23
  yes?: boolean;
24
+ retry?: string;
24
25
  }
25
26
  interface PullOptions {
26
27
  output?: string;
@@ -47,6 +48,7 @@ export declare class RepoCommand {
47
48
  private getMyRepos;
48
49
  private resolveRepoByName;
49
50
  private formatRepoError;
51
+ private printSecurityReview;
50
52
  private isAuthenticated;
51
53
  private requireAuth;
52
54
  /**
@@ -61,6 +63,12 @@ export declare class RepoCommand {
61
63
  * Push a project to Vigthoria Repository
62
64
  */
63
65
  push(options?: PushOptions): Promise<void>;
66
+ /**
67
+ * Show the current user's repository security review queue or one review.
68
+ */
69
+ review(reviewId?: string, options?: {
70
+ json?: boolean;
71
+ }): Promise<void>;
64
72
  /**
65
73
  * Pull a project from Vigthoria Repository
66
74
  */