vigthoria-cli 1.12.3 → 1.13.7

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 CHANGED
@@ -30,7 +30,7 @@ AI-powered terminal coding assistant for Vigthoria Coder subscribers, integrated
30
30
 
31
31
  ## Installation
32
32
 
33
- Version 1.9.3 is published as `vigthoria-cli` and installs the `vigthoria`, `vig`, and `vigthoria-chat` commands. Use the platform installer for automatic setup and updates, or install the npm package globally on any Node.js 18+ platform.
33
+ The `vigthoria-cli` package installs the `vigthoria`, `vig`, and `vigthoria-chat` commands. Use the platform installer for automatic setup and updates, or install the npm package globally on Node.js 20.19 or later.
34
34
 
35
35
  ### Quick Install (Linux/macOS)
36
36
 
@@ -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;
@@ -1469,9 +1475,10 @@ export class ChatCommand {
1469
1475
  return;
1470
1476
  }
1471
1477
  }
1472
- constructor(config, logger) {
1478
+ constructor(config, logger, commandProgram) {
1473
1479
  this.config = config;
1474
1480
  this.logger = logger;
1481
+ this.commandProgram = commandProgram;
1475
1482
  this.api = new APIClient(config, logger);
1476
1483
  this.sessionManager = new SessionManager();
1477
1484
  }
@@ -2332,38 +2339,16 @@ export class ChatCommand {
2332
2339
  : '';
2333
2340
  this.logger.debug(`Agent route: ${r.path} / ${r.taskKind} [${r.source}]${routerNote} — ${r.reason}`);
2334
2341
  }
2335
- const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(resolvedPrompt);
2336
- const handledByTemplateInstant = await this.tryTemplateInstantPath(resolvedPrompt);
2337
- if (handledByTemplateInstant) {
2338
- this.saveSession();
2339
- return;
2340
- }
2341
- const handledByDirectFileFlow = await this.tryDirectSingleFileFlow(resolvedPrompt);
2342
- if (handledByDirectFileFlow) {
2343
- this.saveSession();
2344
- return;
2345
- }
2346
- // Prime the message context with the target file when the direct-file flow was
2347
- // bypassed (e.g. HTML files routed to V3) so the local agent loop has file
2348
- // awareness and the ⚙ Executing: read_file banner is always emitted.
2349
- await this.primeBypassedTargetFileContext(resolvedPrompt);
2350
- if (this.shouldPreferLocalAgentLoop(resolvedPrompt)) {
2351
- await this.runLocalAgentLoop(resolvedPrompt);
2352
- return;
2353
- }
2354
2342
  const handledByV3Workflow = await this.tryV3AgentWorkflow(resolvedPrompt);
2355
2343
  if (handledByV3Workflow) {
2356
2344
  this.saveSession();
2357
2345
  return;
2358
2346
  }
2359
- if (requiresV3Workflow) {
2360
- const errorMessage = 'This task requires the V3 agent workflow and the CLI will not fall back to the legacy local agent loop.';
2361
- this.logger.error(errorMessage);
2362
- this.messages.push({ role: 'assistant', content: errorMessage });
2363
- this.saveSession();
2364
- return;
2365
- }
2366
- 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();
2367
2352
  }
2368
2353
  resolveAgentTurnPrompt(prompt) {
2369
2354
  if (isBuiltContinuePrompt(prompt) || isBuiltRetryPrompt(prompt) || isBuiltWriteConfirmationPrompt(prompt)) {
@@ -3962,20 +3947,29 @@ export class ChatCommand {
3962
3947
  }
3963
3948
  showHelp() {
3964
3949
  console.log('');
3965
- console.log('Commands:');
3966
- console.log(' /help Show this help');
3967
- console.log(' /exit Exit chat');
3968
- console.log(' /agent Toggle agent mode');
3969
- console.log(' /operator Toggle BMAD operator mode');
3970
- console.log(' /context Show current session and project memory');
3971
- console.log(' /memory Show Vigthoria project brain status');
3972
- console.log(' /compact Compact current session into memory summary');
3973
- console.log(' /clear Clear conversation');
3974
- console.log(' /save Save session');
3975
- console.log(' /model <name> Change model');
3976
- console.log(' /status Show the last agent run outcome');
3977
- console.log(' /retry Re-run the last failed agent task');
3978
- 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
+ }
3979
3973
  console.log('');
3980
3974
  }
3981
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
  */