vigthoria-cli 1.12.0 → 1.12.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.
@@ -3,12 +3,14 @@ export interface AgentSessionConfig {
3
3
  debugMode: boolean;
4
4
  autoApprove: boolean;
5
5
  autoSave: boolean;
6
+ engine: 'v3' | 'v4';
6
7
  }
7
8
  export interface AgentSessionMenuDefaults {
8
9
  workspacePath?: string;
9
10
  debugMode?: boolean;
10
11
  autoApprove?: boolean;
11
12
  autoSave?: boolean;
13
+ engine?: 'v3' | 'v4';
12
14
  }
13
15
  export interface AgentSessionMenuOptions {
14
16
  prompt?: string;
@@ -90,12 +90,34 @@ export async function runAgentSessionMenu(defaults = {}) {
90
90
  debugMode: defaults.debugMode === true,
91
91
  autoApprove: defaults.autoApprove === true,
92
92
  autoSave: defaults.autoSave !== false,
93
+ engine: defaults.engine === 'v4' ? 'v4' : 'v3',
93
94
  };
94
95
  console.log();
95
96
  console.log(chalk.cyan('═══ Agent Session Setup ═══'));
96
97
  console.log(chalk.gray(fitLine(`Workspace: ${session.workspacePath}`)));
97
98
  console.log(chalk.gray(fitLine(`Debug: ${session.debugMode ? 'on' : 'off'} | Auto-approve: ${session.autoApprove ? 'on' : 'off'} | Auto-save: ${session.autoSave ? 'on' : 'off'}`)));
98
99
  console.log();
100
+ const { engine } = await inquirer.prompt([
101
+ {
102
+ type: 'list',
103
+ name: 'engine',
104
+ message: 'Agent engine',
105
+ default: session.engine,
106
+ choices: [
107
+ { name: 'V3 Code Agent (default, stable)', value: 'v3' },
108
+ { name: 'V4 Operating Agent (experimental, requires local V4-Operating-Agent checkout)', value: 'v4' },
109
+ ],
110
+ },
111
+ ]);
112
+ session.engine = engine;
113
+ if (session.engine === 'v4') {
114
+ console.log();
115
+ console.log(chalk.cyan('── Session ready ──'));
116
+ console.log(chalk.gray(fitLine(`Workspace: ${session.workspacePath}`)));
117
+ console.log(chalk.gray('Engine: V4 Operating Agent'));
118
+ console.log();
119
+ return session;
120
+ }
99
121
  const { action } = await inquirer.prompt([
100
122
  {
101
123
  type: 'list',
@@ -14,6 +14,7 @@ import { ProjectMemoryService } from '../utils/project-memory.js';
14
14
  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
+ import { V4Command } from './v4.js';
17
18
  import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
18
19
  import { looksLikeMarkdownReport, renderMarkdownToTerminal, summarizeMarkdownReport, } from '../utils/terminalMarkdown.js';
19
20
  import { emitDeckEvent, isDeckModeEnabled } from '../utils/deckEvents.js';
@@ -1503,6 +1504,11 @@ export class ChatCommand {
1503
1504
  if (sessionConfig.debugMode) {
1504
1505
  process.env.DEBUG = process.env.DEBUG || 'vigthoria:*';
1505
1506
  }
1507
+ if (sessionConfig.engine === 'v4') {
1508
+ const v4 = new V4Command(this.config, this.logger);
1509
+ await v4.run({ v4Path: process.env.VIGTHORIA_V4_AGENT_DIR });
1510
+ return;
1511
+ }
1506
1512
  console.log(chalk.green(this.fitTerminalText(`Using workspace: ${this.currentProjectPath}`)));
1507
1513
  }
1508
1514
  if (this.jsonOutput && !options.prompt && !options.retry && !options.continue) {
@@ -0,0 +1,9 @@
1
+ export interface V4MenuOptions {
2
+ defaultAgent?: string;
3
+ defaultProvider?: string;
4
+ byokApiKey?: string;
5
+ byokBaseUrl?: string;
6
+ byokModel?: string;
7
+ outFile: string;
8
+ }
9
+ export declare function runV4Menu(options: V4MenuOptions): Promise<void>;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Interactive V3/V4 agent + V4 provider picker, styled to match the rest of
3
+ * the Vigthoria CLI's menus (see agent-session-menu.ts). This is invoked as a
4
+ * subprocess (with inherited stdio, so it keeps full TTY/inquirer rendering)
5
+ * by the V4 Operating Agent's Python launcher (cli_menu.py) instead of that
6
+ * project rendering its own boxed prompt_toolkit dialog, which looked
7
+ * inconsistent with the CLI's visual design.
8
+ *
9
+ * The result is written as JSON to --out-file rather than printed to stdout,
10
+ * so stdout can stay fully interactive (a piped stdout would make inquirer
11
+ * fall back to non-interactive/plain rendering).
12
+ */
13
+ import * as fs from 'fs';
14
+ import chalk from 'chalk';
15
+ import inquirer from 'inquirer';
16
+ function isCancelError(error) {
17
+ const name = error instanceof Error ? error.name : '';
18
+ return name === 'ExitPromptError' || name === 'ERR_CLOSED';
19
+ }
20
+ function writeResult(outFile, result) {
21
+ fs.writeFileSync(outFile, JSON.stringify(result), 'utf8');
22
+ }
23
+ export async function runV4Menu(options) {
24
+ const defaultAgent = options.defaultAgent === 'v4' ? 'v4' : 'v3';
25
+ const defaultProvider = options.defaultProvider === 'v3_local' || options.defaultProvider === 'byok'
26
+ ? options.defaultProvider
27
+ : 'cloud';
28
+ console.log();
29
+ console.log(chalk.cyan('═══ Vigthoria Agent Launcher ═══'));
30
+ console.log(chalk.gray('Choose which agent runtime to start.'));
31
+ console.log();
32
+ try {
33
+ const { agent } = await inquirer.prompt([
34
+ {
35
+ type: 'list',
36
+ name: 'agent',
37
+ message: 'Select runtime',
38
+ default: defaultAgent,
39
+ choices: [
40
+ { name: 'V3 Code Agent', value: 'v3' },
41
+ { name: 'V4 Operating Agent', value: 'v4' },
42
+ ],
43
+ },
44
+ ]);
45
+ if (agent === 'v3') {
46
+ console.log();
47
+ console.log(chalk.cyan('── Session ready ──'));
48
+ console.log(chalk.gray('Runtime: V3 Code Agent'));
49
+ console.log();
50
+ writeResult(options.outFile, { agent: 'v3' });
51
+ return;
52
+ }
53
+ const { provider } = await inquirer.prompt([
54
+ {
55
+ type: 'list',
56
+ name: 'provider',
57
+ message: 'Select V4 backend',
58
+ default: defaultProvider,
59
+ choices: [
60
+ { name: 'Vigthoria Cloud (Gateway + Session Wallet Token)', value: 'cloud' },
61
+ { name: 'Vigthoria V3 Local (Blackwell 35B Direct)', value: 'v3_local' },
62
+ { name: 'Bring Your Own Key (API Key + Base URL + Model)', value: 'byok' },
63
+ ],
64
+ },
65
+ ]);
66
+ if (provider !== 'byok') {
67
+ console.log();
68
+ console.log(chalk.cyan('── Session ready ──'));
69
+ console.log(chalk.gray(`Runtime: V4 Operating Agent (${provider === 'cloud' ? 'Vigthoria Cloud' : 'Vigthoria V3 Local'})`));
70
+ console.log();
71
+ writeResult(options.outFile, { agent: 'v4', provider });
72
+ return;
73
+ }
74
+ const existingApiKey = String(options.byokApiKey || '').trim();
75
+ const existingBaseUrl = String(options.byokBaseUrl || '').trim();
76
+ const existingModel = String(options.byokModel || '').trim();
77
+ const missing = !existingApiKey || !existingBaseUrl || !existingModel;
78
+ const questions = [];
79
+ if (!existingApiKey) {
80
+ questions.push({
81
+ type: 'password',
82
+ name: 'apiKey',
83
+ message: 'BYOK API key:',
84
+ mask: '*',
85
+ validate: (value) => (String(value || '').trim() ? true : 'API key is required.'),
86
+ });
87
+ }
88
+ if (!existingBaseUrl) {
89
+ questions.push({
90
+ type: 'input',
91
+ name: 'baseUrl',
92
+ message: 'BYOK base URL:',
93
+ validate: (value) => (String(value || '').trim() ? true : 'Base URL is required.'),
94
+ });
95
+ }
96
+ if (!existingModel) {
97
+ questions.push({
98
+ type: 'input',
99
+ name: 'model',
100
+ message: 'BYOK default model:',
101
+ validate: (value) => (String(value || '').trim() ? true : 'Model is required.'),
102
+ });
103
+ }
104
+ const answers = questions.length > 0 ? await inquirer.prompt(questions) : {};
105
+ const apiKey = String(answers.apiKey || existingApiKey).trim();
106
+ const baseUrl = String(answers.baseUrl || existingBaseUrl).trim();
107
+ const model = String(answers.model || existingModel).trim();
108
+ let save = false;
109
+ if (missing) {
110
+ const { saveLocally } = await inquirer.prompt([
111
+ {
112
+ type: 'confirm',
113
+ name: 'saveLocally',
114
+ message: 'Store BYOK values locally in ~/.config/vigthoria/config.yaml?',
115
+ default: true,
116
+ },
117
+ ]);
118
+ save = saveLocally;
119
+ }
120
+ console.log();
121
+ console.log(chalk.cyan('── Session ready ──'));
122
+ console.log(chalk.gray('Runtime: V4 Operating Agent (Bring Your Own Key)'));
123
+ console.log();
124
+ writeResult(options.outFile, { agent: 'v4', provider: 'byok', apiKey, baseUrl, model, save });
125
+ }
126
+ catch (error) {
127
+ if (isCancelError(error)) {
128
+ console.log();
129
+ console.log(chalk.yellow('Cancelled.'));
130
+ writeResult(options.outFile, { cancelled: true });
131
+ process.exitCode = 130;
132
+ return;
133
+ }
134
+ throw error;
135
+ }
136
+ }
@@ -1,6 +1,8 @@
1
1
  import { Command } from 'commander';
2
2
  import { Config } from '../utils/config.js';
3
3
  import { Logger } from '../utils/logger.js';
4
+ export declare function defaultV4PythonBin(): string;
5
+ export declare function defaultV4AgentDir(): string | undefined;
4
6
  type V4Options = {
5
7
  provider?: 'cloud' | 'v3_local' | 'byok';
6
8
  apiKey?: string;
@@ -2,6 +2,23 @@ import { spawn } from 'child_process';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import chalk from 'chalk';
5
+ const DEFAULT_V4_AGENT_DIR_UNIX = '/var/www/V4-Operating-Agent';
6
+ export function defaultV4PythonBin() {
7
+ return process.env.VIGTHORIA_V4_PYTHON || (process.platform === 'win32' ? 'python' : 'python3');
8
+ }
9
+ export function defaultV4AgentDir() {
10
+ if (process.env.VIGTHORIA_V4_AGENT_DIR) {
11
+ return process.env.VIGTHORIA_V4_AGENT_DIR;
12
+ }
13
+ // The historical hardcoded default only ever existed on this project's Linux
14
+ // dev/test boxes. Never assume it on Windows/macOS — only use it as a
15
+ // fallback when it actually exists on disk, so real users get a clear
16
+ // "not found" error instead of a silently-wrong path.
17
+ if (process.platform !== 'win32' && fs.existsSync(DEFAULT_V4_AGENT_DIR_UNIX)) {
18
+ return DEFAULT_V4_AGENT_DIR_UNIX;
19
+ }
20
+ return undefined;
21
+ }
5
22
  export class V4Command {
6
23
  _config;
7
24
  logger;
@@ -23,18 +40,22 @@ export class V4Command {
23
40
  .option('--harness-control-url <url>', 'Override harness control URL')
24
41
  .option('--session-id <id>', 'Explicit V4 session id')
25
42
  .option('--non-interactive', 'Disable interactive menu and use CLI options', false)
26
- .option('--python <bin>', 'Python binary to use', process.env.VIGTHORIA_V4_PYTHON || 'python3')
27
- .option('--v4-path <path>', 'V4 agent root path', process.env.VIGTHORIA_V4_AGENT_DIR || '/var/www/V4-Operating-Agent')
43
+ .option('--python <bin>', 'Python binary to use', defaultV4PythonBin())
44
+ .option('--v4-path <path>', 'V4 agent root path (or set VIGTHORIA_V4_AGENT_DIR)', defaultV4AgentDir())
28
45
  .action(async (options) => {
29
46
  await this.run(options);
30
47
  });
31
48
  }
32
49
  async run(options) {
33
- const v4Path = path.resolve(String(options.v4Path || '/var/www/V4-Operating-Agent'));
34
- const pythonBin = String(options.python || 'python3');
50
+ const resolvedV4Path = options.v4Path || defaultV4AgentDir();
51
+ if (!resolvedV4Path) {
52
+ throw Object.assign(new Error('V4 agent path not set. Pass --v4-path <dir> or set the VIGTHORIA_V4_AGENT_DIR environment variable.'), { skipPathScrub: true });
53
+ }
54
+ const v4Path = path.resolve(String(resolvedV4Path));
55
+ const pythonBin = String(options.python || defaultV4PythonBin());
35
56
  const mainScript = path.join(v4Path, 'main_agent.py');
36
57
  if (!fs.existsSync(mainScript)) {
37
- throw new Error(`V4 launcher not found: ${mainScript}`);
58
+ throw Object.assign(new Error(`V4 launcher not found: ${mainScript}. Check --v4-path/VIGTHORIA_V4_AGENT_DIR points at a valid V4-Operating-Agent checkout.`), { skipPathScrub: true });
38
59
  }
39
60
  const args = [mainScript];
40
61
  if (options.nonInteractive) {
package/dist/index.js CHANGED
@@ -38,7 +38,8 @@ import { ForkCommand } from './commands/fork.js';
38
38
  import { CancelCommand } from './commands/cancel.js';
39
39
  import { BackgroundCommand } from './commands/background.js';
40
40
  import { SecurityCommand } from './commands/security.js';
41
- import { V4Command } from './commands/v4.js';
41
+ import { V4Command, defaultV4PythonBin, defaultV4AgentDir } from './commands/v4.js';
42
+ import { runV4Menu } from './commands/v4-menu.js';
42
43
  import { Config } from './utils/config.js';
43
44
  import { Logger, CH } from './utils/logger.js';
44
45
  import chalk from 'chalk';
@@ -473,8 +474,14 @@ function normalizeCliError(error) {
473
474
  ? extended.response.data.message
474
475
  : undefined;
475
476
  const rawMessage = responseMessage || extended.message || 'An unexpected CLI error occurred.';
477
+ // Purely local, client-side errors (e.g. "this config path doesn't exist
478
+ // on YOUR machine") can legitimately mention local paths that happen to
479
+ // look like internal server paths (e.g. `/var/www/...`-style directories
480
+ // the user copied/typed themselves). Scrubbing those makes an otherwise
481
+ // actionable message unreadable, so commands that construct their own
482
+ // local filesystem errors can opt out via `skipPathScrub`.
476
483
  return {
477
- message: scrubMessageForUser(rawMessage),
484
+ message: extended.skipPathScrub ? rawMessage : scrubMessageForUser(rawMessage),
478
485
  code: extended.code,
479
486
  status: extended.status || extended.statusCode || extended.response?.status,
480
487
  details: extended.details || extended.response?.data,
@@ -753,8 +760,8 @@ export async function main(args) {
753
760
  .option('--harness-control-url <url>', 'Override harness control URL')
754
761
  .option('--session-id <id>', 'Explicit V4 session id')
755
762
  .option('--non-interactive', 'Disable interactive menu and use CLI options', false)
756
- .option('--python <bin>', 'Python binary to use', process.env.VIGTHORIA_V4_PYTHON || 'python3')
757
- .option('--v4-path <path>', 'V4 agent root path', process.env.VIGTHORIA_V4_AGENT_DIR || '/var/www/V4-Operating-Agent')
763
+ .option('--python <bin>', 'Python binary to use', defaultV4PythonBin())
764
+ .option('--v4-path <path>', 'V4 agent root path (or set VIGTHORIA_V4_AGENT_DIR)', defaultV4AgentDir())
758
765
  .action(async (options) => {
759
766
  const v4 = new V4Command(config, logger);
760
767
  await v4.run({
@@ -771,6 +778,25 @@ export async function main(args) {
771
778
  v4Path: options.v4Path,
772
779
  });
773
780
  });
781
+ program
782
+ .command('v4-menu')
783
+ .description('Internal: interactive V3/V4 agent + provider picker used by the V4 Operating Agent launcher')
784
+ .option('--default-agent <agent>', 'v3 | v4', 'v3')
785
+ .option('--default-provider <provider>', 'cloud | v3_local | byok', 'cloud')
786
+ .option('--byok-api-key <key>', 'Existing BYOK api key', '')
787
+ .option('--byok-base-url <url>', 'Existing BYOK base url', '')
788
+ .option('--byok-model <name>', 'Existing BYOK model', '')
789
+ .requiredOption('--out-file <path>', 'Write JSON selection result to this file')
790
+ .action(async (options) => {
791
+ await runV4Menu({
792
+ defaultAgent: options.defaultAgent,
793
+ defaultProvider: options.defaultProvider,
794
+ byokApiKey: options.byokApiKey,
795
+ byokBaseUrl: options.byokBaseUrl,
796
+ byokModel: options.byokModel,
797
+ outFile: options.outFile,
798
+ });
799
+ });
774
800
  program
775
801
  .command('bridge')
776
802
  .description('Inspect local DevTools Bridge availability for browser debugging tasks')
@@ -124,9 +124,17 @@ function minimalHelloWorldHtml(popupMessage = 'Hello World') {
124
124
  </html>
125
125
  `;
126
126
  }
127
+ // The Template Service (port 4011) hard-rejects any `vision` payload over
128
+ // 2000 characters with an HTTP 400. Real prompts (especially resumed-session
129
+ // `/retry` context on an existing project) routinely exceed that, which
130
+ // otherwise made this fast-path fail every single time for non-trivial
131
+ // requests instead of ever getting a chance to match. Cap to the server's
132
+ // own limit so template matching still gets a fair shot on long prompts.
133
+ const TEMPLATE_SERVICE_MAX_VISION_CHARS = 2000;
127
134
  async function fetchTemplateMatch(api, vision, timeoutMs = 15000) {
128
135
  const bases = api.getTemplateServiceBaseUrls();
129
136
  let lastError = 'Template Service unreachable';
137
+ const boundedVision = String(vision || '').slice(0, TEMPLATE_SERVICE_MAX_VISION_CHARS);
130
138
  for (const base of bases) {
131
139
  const controller = new AbortController();
132
140
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -134,7 +142,7 @@ async function fetchTemplateMatch(api, vision, timeoutMs = 15000) {
134
142
  const response = await fetch(`${base}/match`, {
135
143
  method: 'POST',
136
144
  headers: { 'Content-Type': 'application/json' },
137
- body: JSON.stringify({ vision }),
145
+ body: JSON.stringify({ vision: boundedVision }),
138
146
  signal: controller.signal,
139
147
  });
140
148
  clearTimeout(timer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.12.0",
3
+ "version": "1.12.2",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",