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 +1 -1
- package/dist/commands/chat.d.ts +3 -1
- package/dist/commands/chat.js +36 -42
- package/dist/commands/game.d.ts +26 -0
- package/dist/commands/game.js +113 -0
- package/dist/commands/repo.d.ts +9 -1
- package/dist/commands/repo.js +182 -31
- package/dist/commands/v4.d.ts +3 -0
- package/dist/commands/v4.js +12 -0
- package/dist/index.js +117 -58
- package/dist/utils/api.d.ts +1 -0
- package/dist/utils/api.js +99 -62
- package/dist/utils/brain-hub-client.js +42 -14
- package/dist/utils/codebase-indexer.js +29 -6
- package/dist/utils/command-menu.d.ts +11 -0
- package/dist/utils/command-menu.js +97 -0
- package/dist/utils/deckEvents.js +7 -1
- package/dist/utils/requestIntent.d.ts +4 -0
- package/dist/utils/requestIntent.js +16 -1
- package/dist/utils/tools.js +18 -0
- package/install.ps1 +5 -4
- package/install.sh +7 -5
- package/package.json +11 -6
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
- package/scripts/release/validate-no-go-gates.sh +1 -1
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ import { BridgeCommand } from './commands/bridge.js';
|
|
|
31
31
|
import { DeviceCommand } from './commands/device.js';
|
|
32
32
|
import { WorkflowCommand } from './commands/workflow.js';
|
|
33
33
|
import { PreviewCommand } from './commands/preview.js';
|
|
34
|
+
import { GameCommand } from './commands/game.js';
|
|
34
35
|
import { LegionCommand } from './commands/legion.js';
|
|
35
36
|
import { HistoryCommand } from './commands/history.js';
|
|
36
37
|
import { ReplayCommand } from './commands/replay.js';
|
|
@@ -38,7 +39,7 @@ import { ForkCommand } from './commands/fork.js';
|
|
|
38
39
|
import { CancelCommand } from './commands/cancel.js';
|
|
39
40
|
import { BackgroundCommand } from './commands/background.js';
|
|
40
41
|
import { SecurityCommand } from './commands/security.js';
|
|
41
|
-
import { V4Command
|
|
42
|
+
import { V4Command } from './commands/v4.js';
|
|
42
43
|
import { runV4Menu } from './commands/v4-menu.js';
|
|
43
44
|
import { Config } from './utils/config.js';
|
|
44
45
|
import { Logger, CH } from './utils/logger.js';
|
|
@@ -49,6 +50,7 @@ import * as os from 'os';
|
|
|
49
50
|
import { fileURLToPath } from 'url';
|
|
50
51
|
import { createHash } from 'crypto';
|
|
51
52
|
import axios from 'axios';
|
|
53
|
+
import { renderDynamicHelp, selectInteractiveCommand } from './utils/command-menu.js';
|
|
52
54
|
// ESM shim — TypeScript emits ESM under "type": "module", so __dirname
|
|
53
55
|
// is not available natively. Restore the CommonJS-style helper so the
|
|
54
56
|
// pre-existing package-discovery logic continues to work unchanged.
|
|
@@ -58,6 +60,44 @@ import { APIClient, sanitizeUserFacingErrorText } from './utils/api.js';
|
|
|
58
60
|
import { getCliStateFile, isOfflineMode, isSafeNpmPackageSpec, isUpdateCheckSuppressed, readCachedLatestVersion, readGatewayPreflightCache, writeCachedLatestVersion, writeGatewayPreflightCache, } from './utils/cli-state.js';
|
|
59
61
|
import { applyLocalTestfarmDefaults, isLocalTestfarmMode } from './utils/localTestMode.js';
|
|
60
62
|
applyLocalTestfarmDefaults();
|
|
63
|
+
// --- Terminal column-width safety clamp -----------------------------------
|
|
64
|
+
// Node's `readline` module (used for the interactive chat `> ` prompt and a
|
|
65
|
+
// few other prompts) redraws the current input line on every keystroke using
|
|
66
|
+
// cursor-position math derived from `process.stdout.columns`. When this CLI
|
|
67
|
+
// runs inside a freshly-spawned ConPTY child (e.g. the Vigthoria Workbench's
|
|
68
|
+
// embedded terminal pane on Windows), there is a brief window right after
|
|
69
|
+
// process start where the console width hasn't been reported yet (or is
|
|
70
|
+
// reported as an implausibly small value like 0/1) before the real terminal
|
|
71
|
+
// size propagates down to the child process. Because `readline` reads
|
|
72
|
+
// `columns` fresh on every redraw (not just once at startup), an early or
|
|
73
|
+
// transient bad reading makes it believe the terminal is only 1 column wide,
|
|
74
|
+
// so it moves the cursor to a new row (and re-indents) after every single
|
|
75
|
+
// typed character -- producing a "staircase" of one-character lines that
|
|
76
|
+
// looks exactly like what's reported. This has nothing to do with the host
|
|
77
|
+
// terminal's real (correct) width; it's purely readline believing a bad
|
|
78
|
+
// number. Clamp `columns`/`rows` to sane floors up front and keep them
|
|
79
|
+
// clamped on every real resize event so readline never sees an implausible
|
|
80
|
+
// value, regardless of what the host terminal actually reports.
|
|
81
|
+
const MIN_TTY_COLUMNS = 30;
|
|
82
|
+
const MIN_TTY_ROWS = 4;
|
|
83
|
+
function clampStreamSize(stream) {
|
|
84
|
+
if (!stream || !stream.isTTY)
|
|
85
|
+
return;
|
|
86
|
+
if (!Number.isFinite(stream.columns) || (stream.columns ?? 0) < MIN_TTY_COLUMNS) {
|
|
87
|
+
stream.columns = 80;
|
|
88
|
+
}
|
|
89
|
+
if (!Number.isFinite(stream.rows) || (stream.rows ?? 0) < MIN_TTY_ROWS) {
|
|
90
|
+
stream.rows = 24;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
clampStreamSize(process.stdout);
|
|
94
|
+
clampStreamSize(process.stderr);
|
|
95
|
+
if (process.stdout.isTTY) {
|
|
96
|
+
process.stdout.on('resize', () => clampStreamSize(process.stdout));
|
|
97
|
+
}
|
|
98
|
+
if (process.stderr.isTTY) {
|
|
99
|
+
process.stderr.on('resize', () => clampStreamSize(process.stderr));
|
|
100
|
+
}
|
|
61
101
|
function isApiError(error) {
|
|
62
102
|
return Boolean(error &&
|
|
63
103
|
typeof error === 'object' &&
|
|
@@ -200,10 +240,14 @@ function maxVersion(...versions) {
|
|
|
200
240
|
async function fetchNpmLatestVersion() {
|
|
201
241
|
const { execSync } = await import('child_process');
|
|
202
242
|
try {
|
|
243
|
+
// Hard 5s timeout: a slow/blocked path to the public npm registry must
|
|
244
|
+
// never hang the caller (e.g. the Workbench's blocking `vigthoria update`
|
|
245
|
+
// invocation) indefinitely.
|
|
203
246
|
const latest = execSync('npm view vigthoria-cli version', {
|
|
204
247
|
encoding: 'utf8',
|
|
205
248
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
206
249
|
windowsHide: true,
|
|
250
|
+
timeout: 5000,
|
|
207
251
|
}).trim();
|
|
208
252
|
return latest || null;
|
|
209
253
|
}
|
|
@@ -212,7 +256,7 @@ async function fetchNpmLatestVersion() {
|
|
|
212
256
|
}
|
|
213
257
|
}
|
|
214
258
|
const VERSION = getVersion();
|
|
215
|
-
const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://
|
|
259
|
+
const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://coder.vigthoria.io/releases/manifest.json";
|
|
216
260
|
function resolveManifestEntry(manifest, channel) {
|
|
217
261
|
const normalized = String(channel || 'stable').trim() || 'stable';
|
|
218
262
|
if (manifest.channels && manifest.channels[normalized]) {
|
|
@@ -321,6 +365,10 @@ async function installGlobalPackageWithNpm(packageSpec) {
|
|
|
321
365
|
stdio: 'inherit',
|
|
322
366
|
windowsHide: true,
|
|
323
367
|
shell: attempt.shell === true,
|
|
368
|
+
// Hard cap so a stalled/hung npm install (registry outage, network
|
|
369
|
+
// issue, etc.) can never block the caller indefinitely - especially
|
|
370
|
+
// important since the Workbench invokes this synchronously.
|
|
371
|
+
timeout: 180000,
|
|
324
372
|
});
|
|
325
373
|
return;
|
|
326
374
|
}
|
|
@@ -646,7 +694,7 @@ export async function main(args) {
|
|
|
646
694
|
.option('--auto-approve', 'Auto-approve agent actions (dangerous!)', false)
|
|
647
695
|
.option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
|
|
648
696
|
.action(async (options) => {
|
|
649
|
-
const chat = new ChatCommand(config, logger);
|
|
697
|
+
const chat = new ChatCommand(config, logger, program);
|
|
650
698
|
await chat.run({
|
|
651
699
|
model: options.model,
|
|
652
700
|
project: options.project,
|
|
@@ -675,7 +723,7 @@ export async function main(args) {
|
|
|
675
723
|
.option('--auto-approve', 'Auto-approve agent actions (dangerous!)', false)
|
|
676
724
|
.option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
|
|
677
725
|
.action(async (options) => {
|
|
678
|
-
const chat = new ChatCommand(config, logger);
|
|
726
|
+
const chat = new ChatCommand(config, logger, program);
|
|
679
727
|
await chat.run({
|
|
680
728
|
model: options.model,
|
|
681
729
|
project: options.project,
|
|
@@ -707,7 +755,7 @@ export async function main(args) {
|
|
|
707
755
|
.option('--auto-approve', 'Auto-approve all actions (dangerous!)', false)
|
|
708
756
|
.option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
|
|
709
757
|
.action(async (options) => {
|
|
710
|
-
const chat = new ChatCommand(config, logger);
|
|
758
|
+
const chat = new ChatCommand(config, logger, program);
|
|
711
759
|
await chat.run({
|
|
712
760
|
model: options.model,
|
|
713
761
|
project: options.project,
|
|
@@ -737,7 +785,7 @@ export async function main(args) {
|
|
|
737
785
|
.option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
|
|
738
786
|
.option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
|
|
739
787
|
.action(async (options) => {
|
|
740
|
-
const chat = new ChatCommand(config, logger);
|
|
788
|
+
const chat = new ChatCommand(config, logger, program);
|
|
741
789
|
await chat.run({
|
|
742
790
|
model: options.model,
|
|
743
791
|
project: options.project,
|
|
@@ -752,37 +800,7 @@ export async function main(args) {
|
|
|
752
800
|
bridge: options.bridge,
|
|
753
801
|
});
|
|
754
802
|
});
|
|
755
|
-
program
|
|
756
|
-
.command('v4')
|
|
757
|
-
.alias('v4-agent')
|
|
758
|
-
.description('Launch Vigthoria V4 Operating Agent (DeerFlow 2.0 harness)')
|
|
759
|
-
.option('--provider <provider>', 'Provider: cloud | v3_local | byok')
|
|
760
|
-
.option('--api-key <key>', 'BYOK API key for non-interactive mode')
|
|
761
|
-
.option('--base-url <url>', 'Provider base URL override')
|
|
762
|
-
.option('--model <name>', 'Provider model override')
|
|
763
|
-
.option('--transport <mode>', 'Bridge transport: auto | ws | sse', 'auto')
|
|
764
|
-
.option('--harness-stream-url <url>', 'Override harness stream URL')
|
|
765
|
-
.option('--harness-control-url <url>', 'Override harness control URL')
|
|
766
|
-
.option('--session-id <id>', 'Explicit V4 session id')
|
|
767
|
-
.option('--non-interactive', 'Disable interactive menu and use CLI options', false)
|
|
768
|
-
.option('--python <bin>', 'Python binary to use', defaultV4PythonBin())
|
|
769
|
-
.option('--v4-path <path>', 'V4 agent root path (or set VIGTHORIA_V4_AGENT_DIR)', defaultV4AgentDir())
|
|
770
|
-
.action(async (options) => {
|
|
771
|
-
const v4 = new V4Command(config, logger);
|
|
772
|
-
await v4.run({
|
|
773
|
-
provider: options.provider,
|
|
774
|
-
apiKey: options.apiKey,
|
|
775
|
-
baseUrl: options.baseUrl,
|
|
776
|
-
model: options.model,
|
|
777
|
-
transport: options.transport,
|
|
778
|
-
harnessStreamUrl: options.harnessStreamUrl,
|
|
779
|
-
harnessControlUrl: options.harnessControlUrl,
|
|
780
|
-
sessionId: options.sessionId,
|
|
781
|
-
nonInteractive: options.nonInteractive,
|
|
782
|
-
python: options.python,
|
|
783
|
-
v4Path: options.v4Path,
|
|
784
|
-
});
|
|
785
|
-
});
|
|
803
|
+
new V4Command(config, logger).register(program);
|
|
786
804
|
program
|
|
787
805
|
.command('v4-menu')
|
|
788
806
|
.description('Internal: interactive V3/V4 agent + provider picker used by the V4 Operating Agent launcher')
|
|
@@ -1143,20 +1161,21 @@ export async function main(args) {
|
|
|
1143
1161
|
await music.status(taskId, options);
|
|
1144
1162
|
});
|
|
1145
1163
|
// ==================== REPO COMMANDS ====================
|
|
1146
|
-
// Repo command - Push/Pull projects to/from Vigthoria Repository
|
|
1164
|
+
// Repo command - Push/Pull projects to/from Vigthoria Community Repository
|
|
1147
1165
|
const repoCommand = program
|
|
1148
1166
|
.command('repo')
|
|
1149
1167
|
.alias('repository')
|
|
1150
|
-
.description('Push and pull projects to/from your Vigthoria Repository');
|
|
1168
|
+
.description('Push and pull projects to/from your Vigthoria Community Repository');
|
|
1151
1169
|
repoCommand
|
|
1152
1170
|
.command('push [path]')
|
|
1153
1171
|
.alias('upload')
|
|
1154
|
-
.description('Push current or specified project to Vigthoria
|
|
1172
|
+
.description('Push current or specified project to Vigthoria Community Repository')
|
|
1155
1173
|
.option('-n, --name <projectName>', 'Project name override for the remote repo')
|
|
1156
1174
|
.option('-v, --visibility <type>', 'Set visibility (private, restricted, public)', 'private')
|
|
1157
1175
|
.option('-d, --description <text>', 'Project description')
|
|
1158
1176
|
.option('-f, --force', 'Overwrite existing project', false)
|
|
1159
1177
|
.option('-y, --yes', 'Run non-interactively with provided/default values', false)
|
|
1178
|
+
.option('--retry <reviewId>', 'Link this corrected push to a repository security review')
|
|
1160
1179
|
.option('--open-in <engine>', 'After push, open project in engine: shop, visual, game')
|
|
1161
1180
|
.option('--browser', 'Open result URL in default browser', false)
|
|
1162
1181
|
.action(async (pathArg, options) => {
|
|
@@ -1167,7 +1186,8 @@ export async function main(args) {
|
|
|
1167
1186
|
visibility: options.visibility,
|
|
1168
1187
|
description: options.description,
|
|
1169
1188
|
force: options.force,
|
|
1170
|
-
yes: options.yes
|
|
1189
|
+
yes: options.yes,
|
|
1190
|
+
retry: options.retry
|
|
1171
1191
|
});
|
|
1172
1192
|
if (options.openIn) {
|
|
1173
1193
|
const engine = options.openIn;
|
|
@@ -1179,10 +1199,18 @@ export async function main(args) {
|
|
|
1179
1199
|
}
|
|
1180
1200
|
}
|
|
1181
1201
|
});
|
|
1202
|
+
repoCommand
|
|
1203
|
+
.command('review [reviewId]')
|
|
1204
|
+
.description('Show repository security review status, findings, and retry guidance')
|
|
1205
|
+
.option('--json', 'Emit machine-readable JSON output', false)
|
|
1206
|
+
.action(async (reviewId, options) => {
|
|
1207
|
+
const repo = new RepoCommand(config, logger);
|
|
1208
|
+
await repo.review(reviewId, { json: options.json });
|
|
1209
|
+
});
|
|
1182
1210
|
repoCommand
|
|
1183
1211
|
.command('pull <name>')
|
|
1184
1212
|
.alias('download')
|
|
1185
|
-
.description('Pull a project from your Vigthoria
|
|
1213
|
+
.description('Pull a project from your Vigthoria Community Repository')
|
|
1186
1214
|
.option('-o, --output <path>', 'Output directory path')
|
|
1187
1215
|
.option('-f, --force', 'Overwrite existing directory', false)
|
|
1188
1216
|
.action(async (name, options) => {
|
|
@@ -1195,7 +1223,7 @@ export async function main(args) {
|
|
|
1195
1223
|
repoCommand
|
|
1196
1224
|
.command('list')
|
|
1197
1225
|
.alias('ls')
|
|
1198
|
-
.description('List all your projects in Vigthoria
|
|
1226
|
+
.description('List all your projects in Vigthoria Community Repository')
|
|
1199
1227
|
.option('-v, --visibility <type>', 'Filter by visibility (private, restricted, public)')
|
|
1200
1228
|
.action(async (options) => {
|
|
1201
1229
|
const repo = new RepoCommand(config, logger);
|
|
@@ -1219,14 +1247,14 @@ export async function main(args) {
|
|
|
1219
1247
|
repoCommand
|
|
1220
1248
|
.command('delete <name>')
|
|
1221
1249
|
.alias('rm')
|
|
1222
|
-
.description('Remove a project from your Vigthoria
|
|
1250
|
+
.description('Remove a project from your Vigthoria Community Repository')
|
|
1223
1251
|
.action(async (name) => {
|
|
1224
1252
|
const repo = new RepoCommand(config, logger);
|
|
1225
1253
|
await repo.delete(name);
|
|
1226
1254
|
});
|
|
1227
1255
|
repoCommand
|
|
1228
1256
|
.command('clone <url>')
|
|
1229
|
-
.description('Clone a public project from Vigthoria
|
|
1257
|
+
.description('Clone a public project from Vigthoria Community')
|
|
1230
1258
|
.option('-o, --output <path>', 'Output directory path')
|
|
1231
1259
|
.option('-f, --force', 'Overwrite existing directory', false)
|
|
1232
1260
|
.action(async (url, options) => {
|
|
@@ -1238,7 +1266,7 @@ export async function main(args) {
|
|
|
1238
1266
|
});
|
|
1239
1267
|
repoCommand
|
|
1240
1268
|
.command('open-in <engine> [projectName]')
|
|
1241
|
-
.description('Open a Vigthoria
|
|
1269
|
+
.description('Open a Vigthoria Community Repository project in the Shop Engine, Visual Editor, or Gaming Engine')
|
|
1242
1270
|
.option('--browser', 'Open result URL in default browser', false)
|
|
1243
1271
|
.option('--shop-id <id>', 'Target shop ID (for shop engine)', 'default')
|
|
1244
1272
|
.addHelpText('after', `
|
|
@@ -1416,6 +1444,12 @@ Examples:
|
|
|
1416
1444
|
screenshot: options.screenshot,
|
|
1417
1445
|
});
|
|
1418
1446
|
});
|
|
1447
|
+
// ==================== GAME COMMAND ====================
|
|
1448
|
+
const gameCommand = program.command('game').description('Create, run, and validate Babylon/VGE game projects');
|
|
1449
|
+
gameCommand.command('init [name]').description('Create a production-ready client-rendered Babylon/VGE game').option('--engine <engine>', 'Game engine', 'babylon').option('--no-install', 'Create files without installing dependencies').option('--package-manager <manager>', 'npm, pnpm, yarn, or bun').action(async (name, options) => { await new GameCommand(logger).init(name, options); });
|
|
1450
|
+
gameCommand.command('run').description('Run the local game development server').option('-p, --project <path>', 'Game project directory', process.cwd()).option('--port <number>', 'Development server port', parseInt).option('--no-open', 'Do not open a browser').option('--package-manager <manager>', 'npm, pnpm, yarn, or bun').action(async (options) => { await new GameCommand(logger).run(options); });
|
|
1451
|
+
gameCommand.command('validate').description('Build and validate a Babylon/VGE game project').option('-p, --project <path>', 'Game project directory', process.cwd()).option('--json', 'Print machine-readable validation results').option('--package-manager <manager>', 'npm, pnpm, yarn, or bun').action(async (options) => { await new GameCommand(logger).validate(options); });
|
|
1452
|
+
gameCommand.action(() => gameCommand.outputHelp());
|
|
1419
1453
|
// ==================== LEGION COMMAND ====================
|
|
1420
1454
|
program
|
|
1421
1455
|
.command('legion [request]')
|
|
@@ -1676,10 +1710,23 @@ Examples:
|
|
|
1676
1710
|
console.log(chalk.gray('Continuing with npm registry check...'));
|
|
1677
1711
|
}
|
|
1678
1712
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1713
|
+
// The release manifest (server-curated, sha256-verified) is the
|
|
1714
|
+
// authoritative source of truth for this private CLI. Only fall back
|
|
1715
|
+
// to probing the public npm registry when the manifest itself has no
|
|
1716
|
+
// usable entry - never let an npm version number override a valid,
|
|
1717
|
+
// verified manifest entry (npm install -g against the public registry
|
|
1718
|
+
// is slower, unverified, and has previously caused the Workbench's
|
|
1719
|
+
// update button to hang for minutes).
|
|
1720
|
+
let npmVersion = null;
|
|
1721
|
+
if (manifestEntry?.version && manifestEntry?.url) {
|
|
1722
|
+
console.log(chalk.gray('Release manifest has a valid entry; skipping public npm registry lookup.'));
|
|
1723
|
+
}
|
|
1724
|
+
else {
|
|
1725
|
+
console.log(chalk.cyan('Checking npm registry...'));
|
|
1726
|
+
npmVersion = await fetchNpmLatestVersion();
|
|
1727
|
+
if (!npmVersion) {
|
|
1728
|
+
console.log(chalk.yellow('Could not read latest version from npm registry.'));
|
|
1729
|
+
}
|
|
1683
1730
|
}
|
|
1684
1731
|
const effectiveLatest = maxVersion(manifestEntry?.version, npmVersion);
|
|
1685
1732
|
if (!effectiveLatest) {
|
|
@@ -1687,11 +1734,6 @@ Examples:
|
|
|
1687
1734
|
process.exitCode = 1;
|
|
1688
1735
|
return;
|
|
1689
1736
|
}
|
|
1690
|
-
if (manifestEntry?.version
|
|
1691
|
-
&& npmVersion
|
|
1692
|
-
&& compareVersions(npmVersion, manifestEntry.version) > 0) {
|
|
1693
|
-
console.log(chalk.yellow(`Release manifest (${manifestEntry.version}) is behind npm (${npmVersion}); npm will be used when needed.`));
|
|
1694
|
-
}
|
|
1695
1737
|
if (!allowDowngrade && compareVersions(effectiveLatest, currentVersion) <= 0) {
|
|
1696
1738
|
console.log(chalk.green(`You are running the latest version (${currentVersion})`));
|
|
1697
1739
|
return;
|
|
@@ -1807,6 +1849,21 @@ Examples:
|
|
|
1807
1849
|
const configCmd = new ConfigCommand(config, logger);
|
|
1808
1850
|
await configCmd.init();
|
|
1809
1851
|
});
|
|
1852
|
+
program
|
|
1853
|
+
.command('menu')
|
|
1854
|
+
.description('Open the interactive command-family menu')
|
|
1855
|
+
.action(async () => {
|
|
1856
|
+
const selectedArgs = await selectInteractiveCommand(program);
|
|
1857
|
+
if (selectedArgs) {
|
|
1858
|
+
await program.parseAsync([args[0], args[1], ...selectedArgs]);
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
program
|
|
1862
|
+
.command('commands')
|
|
1863
|
+
.description('List every CLI command grouped by command family')
|
|
1864
|
+
.action(() => {
|
|
1865
|
+
console.log(renderDynamicHelp(program));
|
|
1866
|
+
});
|
|
1810
1867
|
const codingCommandDefinitions = [
|
|
1811
1868
|
{ name: 'edit', description: 'Edit code by describing the desired change', instruction: 'Edit the project according to this request' },
|
|
1812
1869
|
{ name: 'generate', description: 'Generate code from a prompt', instruction: 'Generate code for this request' },
|
|
@@ -1823,7 +1880,7 @@ Examples:
|
|
|
1823
1880
|
.option('-p, --project <path>', 'Project directory', process.cwd())
|
|
1824
1881
|
.action(async (requestParts = [], options) => {
|
|
1825
1882
|
const requestText = requestParts.join(' ').trim();
|
|
1826
|
-
const chat = new ChatCommand(config, logger);
|
|
1883
|
+
const chat = new ChatCommand(config, logger, program);
|
|
1827
1884
|
await chat.run({
|
|
1828
1885
|
model: options.model || 'code',
|
|
1829
1886
|
project: options.project || process.cwd(),
|
|
@@ -1834,8 +1891,10 @@ Examples:
|
|
|
1834
1891
|
try {
|
|
1835
1892
|
// Default to chat if no command
|
|
1836
1893
|
if (args.length === 2) {
|
|
1837
|
-
const
|
|
1838
|
-
|
|
1894
|
+
const selectedArgs = await selectInteractiveCommand(program);
|
|
1895
|
+
if (selectedArgs) {
|
|
1896
|
+
await program.parseAsync([args[0], args[1], ...selectedArgs]);
|
|
1897
|
+
}
|
|
1839
1898
|
process.exitCode = 0;
|
|
1840
1899
|
return;
|
|
1841
1900
|
}
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -277,6 +277,7 @@ export declare class APIClient {
|
|
|
277
277
|
private listFrontendWorkspaceFiles;
|
|
278
278
|
private chooseFrontendPreviewEntry;
|
|
279
279
|
private extractLinkedFrontendAssets;
|
|
280
|
+
private buildFrontendForPreview;
|
|
280
281
|
private gatherFrontendPreviewArtifacts;
|
|
281
282
|
private captureFrontendPreviewScreenshot;
|
|
282
283
|
private evaluateFrontendVisualProof;
|
package/dist/utils/api.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* Connects to coder.vigthoria.io API endpoints
|
|
4
4
|
*/
|
|
5
5
|
import axios from 'axios';
|
|
6
|
+
import { execFile } from 'child_process';
|
|
6
7
|
import { randomUUID } from 'crypto';
|
|
7
8
|
import fs from 'fs';
|
|
8
9
|
import https from 'https';
|
|
9
10
|
import net from 'net';
|
|
10
11
|
import path from 'path';
|
|
11
12
|
import WebSocket from 'ws';
|
|
13
|
+
import { globSync } from 'glob';
|
|
12
14
|
import { buildSemanticContext } from './context-ranker.js';
|
|
13
15
|
import { getChangedFiles } from './workspace-cache.js';
|
|
14
16
|
import { isSubstantiveAgentAnswer, isToolEvidenceStubAnswer } from './agentRunOutcome.js';
|
|
@@ -743,8 +745,8 @@ export class APIClient {
|
|
|
743
745
|
const urls = [
|
|
744
746
|
process.env.VIGTHORIA_TEMPLATE_SERVICE_URL,
|
|
745
747
|
process.env.TEMPLATE_SERVICE_URL,
|
|
746
|
-
'http://127.0.0.1:4011',
|
|
747
748
|
`${configuredApiUrl}/api/template-service`,
|
|
749
|
+
'http://127.0.0.1:4011',
|
|
748
750
|
].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
|
|
749
751
|
return [...new Set(urls)];
|
|
750
752
|
}
|
|
@@ -842,7 +844,15 @@ export class APIClient {
|
|
|
842
844
|
const baseName = path.posix.basename(relativePath).toLowerCase();
|
|
843
845
|
const depth = relativePath.split('/').length - 1;
|
|
844
846
|
let score = 0;
|
|
845
|
-
|
|
847
|
+
const normalizedPath = relativePath.toLowerCase();
|
|
848
|
+
const topLevelDir = normalizedPath.split('/')[0];
|
|
849
|
+
if (['dist', 'build', 'out'].includes(topLevelDir)) {
|
|
850
|
+
score += 320;
|
|
851
|
+
}
|
|
852
|
+
else if (topLevelDir === 'public') {
|
|
853
|
+
score += 180;
|
|
854
|
+
}
|
|
855
|
+
if (normalizedPath === 'index.html') {
|
|
846
856
|
score += 200;
|
|
847
857
|
}
|
|
848
858
|
if (baseName === 'index.html') {
|
|
@@ -896,6 +906,34 @@ export class APIClient {
|
|
|
896
906
|
js: Array.from(js),
|
|
897
907
|
};
|
|
898
908
|
}
|
|
909
|
+
async buildFrontendForPreview(rootPath) {
|
|
910
|
+
const packagePath = path.join(rootPath, 'package.json');
|
|
911
|
+
if (!fs.existsSync(packagePath)) {
|
|
912
|
+
return { attempted: false, succeeded: false };
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
916
|
+
if (!packageJson.scripts?.build) {
|
|
917
|
+
return { attempted: false, succeeded: false };
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
catch (error) {
|
|
921
|
+
return { attempted: false, succeeded: false, error: `Invalid package.json: ${error?.message || String(error)}` };
|
|
922
|
+
}
|
|
923
|
+
const npmCommand = process.platform === 'win32' ? (process.env.ComSpec || 'cmd.exe') : 'npm';
|
|
924
|
+
const npmArgs = process.platform === 'win32'
|
|
925
|
+
? ['/d', '/s', '/c', 'npm run build']
|
|
926
|
+
: ['run', 'build'];
|
|
927
|
+
return await new Promise((resolve) => {
|
|
928
|
+
execFile(npmCommand, npmArgs, { cwd: rootPath, timeout: 150_000, windowsHide: true, maxBuffer: 2 * 1024 * 1024 }, (error) => {
|
|
929
|
+
if (error) {
|
|
930
|
+
resolve({ attempted: true, succeeded: false, error: sanitizeUserFacingErrorText(error.message).slice(0, 300) });
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
resolve({ attempted: true, succeeded: true });
|
|
934
|
+
});
|
|
935
|
+
});
|
|
936
|
+
}
|
|
899
937
|
gatherFrontendPreviewArtifacts(message = '', context = {}) {
|
|
900
938
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
901
939
|
if (!rootPath || !fs.existsSync(rootPath)) {
|
|
@@ -1051,12 +1089,22 @@ export class APIClient {
|
|
|
1051
1089
|
return { required: false, passed: true };
|
|
1052
1090
|
}
|
|
1053
1091
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
1054
|
-
|
|
1092
|
+
let artifacts = this.gatherFrontendPreviewArtifacts(message, context);
|
|
1093
|
+
let buildResult = null;
|
|
1094
|
+
if (rootPath && artifacts.error?.includes('at least one HTML entry file')) {
|
|
1095
|
+
buildResult = await this.buildFrontendForPreview(rootPath);
|
|
1096
|
+
if (buildResult.attempted) {
|
|
1097
|
+
artifacts = this.gatherFrontendPreviewArtifacts(message, context);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1055
1100
|
if (artifacts.error || !artifacts.htmlPath || typeof artifacts.html !== 'string') {
|
|
1101
|
+
const buildDetail = buildResult?.attempted
|
|
1102
|
+
? ` Build retry ${buildResult.succeeded ? 'completed but produced no HTML entry' : `failed: ${buildResult.error || 'unknown build error'}`}.`
|
|
1103
|
+
: '';
|
|
1056
1104
|
return {
|
|
1057
1105
|
required: true,
|
|
1058
1106
|
passed: false,
|
|
1059
|
-
error: artifacts.error || 'Frontend preview gate could not collect frontend artifacts.'
|
|
1107
|
+
error: `${artifacts.error || 'Frontend preview gate could not collect frontend artifacts.'}${buildDetail}`,
|
|
1060
1108
|
};
|
|
1061
1109
|
}
|
|
1062
1110
|
// Cap artifact sizes to prevent 413 Payload Too Large
|
|
@@ -1083,11 +1131,19 @@ export class APIClient {
|
|
|
1083
1131
|
error: 'Preview proof skipped: payload exceeds size limit.',
|
|
1084
1132
|
};
|
|
1085
1133
|
}
|
|
1134
|
+
const internalServiceKey = process.env.VIGTHORIA_TEMPLATE_SERVICE_KEY
|
|
1135
|
+
|| process.env.V3_SERVICE_KEY;
|
|
1136
|
+
const authToken = this.getAccessToken();
|
|
1086
1137
|
const response = await fetch(`${baseUrl}/preview-proof`, {
|
|
1087
1138
|
method: 'POST',
|
|
1088
1139
|
headers: {
|
|
1089
1140
|
'Content-Type': 'application/json',
|
|
1090
1141
|
Accept: 'application/json',
|
|
1142
|
+
...(authToken ? {
|
|
1143
|
+
Authorization: `Bearer ${authToken}`,
|
|
1144
|
+
Cookie: `vigthoria-auth-token=${authToken}`,
|
|
1145
|
+
} : {}),
|
|
1146
|
+
...(internalServiceKey ? { 'x-vigthoria-service-key': internalServiceKey } : {}),
|
|
1091
1147
|
},
|
|
1092
1148
|
body: proofPayload,
|
|
1093
1149
|
});
|
|
@@ -1670,12 +1726,7 @@ export class APIClient {
|
|
|
1670
1726
|
}
|
|
1671
1727
|
compactionMeta.applied = true;
|
|
1672
1728
|
phases.push('over_limit');
|
|
1673
|
-
|
|
1674
|
-
process.stderr.write(`Workspace context is large — sending a focused summary (~${Math.round(LIMIT / 1000)}k char budget, token-aligned).\n`);
|
|
1675
|
-
}
|
|
1676
|
-
if (process.env.DEBUG || process.env.VIGTHORIA_DEBUG) {
|
|
1677
|
-
process.stderr.write(`[context] Payload ${json.length} chars exceeds ${LIMIT} limit, compacting...\n`);
|
|
1678
|
-
}
|
|
1729
|
+
this.logger.debug(`Workspace context ${json.length} chars exceeds ${LIMIT}; applying token-aligned compaction.`);
|
|
1679
1730
|
const summary = payload.localWorkspaceSummary;
|
|
1680
1731
|
const hydrationRequired = payload.workspaceHydrationRequired === true;
|
|
1681
1732
|
const finish = () => {
|
|
@@ -1772,10 +1823,14 @@ export class APIClient {
|
|
|
1772
1823
|
return finish();
|
|
1773
1824
|
}
|
|
1774
1825
|
/** Normalize common Unix-only commands before Windows cmd/powershell execution. */
|
|
1775
|
-
normalizeWindowsRunCommand(command) {
|
|
1826
|
+
normalizeWindowsRunCommand(command, serverRoot = '') {
|
|
1776
1827
|
let cmd = String(command || '').trim();
|
|
1777
1828
|
if (!cmd)
|
|
1778
1829
|
return cmd;
|
|
1830
|
+
if (serverRoot) {
|
|
1831
|
+
const escapedRoot = serverRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1832
|
+
cmd = cmd.replace(new RegExp(`^\\s*cd\\s+(?:[\"']?${escapedRoot}[\"']?)\\s*(?:&&|;)\\s*`, 'i'), '');
|
|
1833
|
+
}
|
|
1779
1834
|
if (/^\/[^\s/]/.test(cmd) && !/^\/[a-z]:/i.test(cmd)) {
|
|
1780
1835
|
throw new Error('Command starts with "/" which Windows interprets as an invalid switch. Use workspace-relative paths without a leading slash.');
|
|
1781
1836
|
}
|
|
@@ -3003,8 +3058,8 @@ menu {
|
|
|
3003
3058
|
return { success: true, output: entries.join('\n') || '(empty)' };
|
|
3004
3059
|
}
|
|
3005
3060
|
if (name === 'glob' || name === 'search_files' || name === 'grep') {
|
|
3006
|
-
const
|
|
3007
|
-
const
|
|
3061
|
+
const rawPattern = String(args.pattern || args.query || '').trim();
|
|
3062
|
+
const pattern = rawPattern.toLowerCase();
|
|
3008
3063
|
const matches = [];
|
|
3009
3064
|
const walk = (dir) => {
|
|
3010
3065
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -3016,12 +3071,7 @@ menu {
|
|
|
3016
3071
|
continue;
|
|
3017
3072
|
}
|
|
3018
3073
|
const relative = path.relative(rootPath, absolute).replace(/\\/g, '/');
|
|
3019
|
-
if (name
|
|
3020
|
-
if (!pattern || relative.toLowerCase().includes(pattern.replace(/\*/g, ''))) {
|
|
3021
|
-
files.push(relative);
|
|
3022
|
-
}
|
|
3023
|
-
}
|
|
3024
|
-
else if (pattern) {
|
|
3074
|
+
if (name !== 'glob' && pattern) {
|
|
3025
3075
|
try {
|
|
3026
3076
|
const content = fs.readFileSync(absolute, 'utf8');
|
|
3027
3077
|
if (relative.toLowerCase().includes(pattern.replace(/\*/g, '')) || content.toLowerCase().includes(pattern)) {
|
|
@@ -3035,10 +3085,19 @@ menu {
|
|
|
3035
3085
|
}
|
|
3036
3086
|
};
|
|
3037
3087
|
const searchRoot = fs.statSync(target.absolutePath).isDirectory() ? target.absolutePath : rootPath;
|
|
3038
|
-
walk(searchRoot);
|
|
3039
3088
|
if (name === 'glob') {
|
|
3040
|
-
|
|
3089
|
+
const normalizedPattern = rawPattern.replace(/\\/g, '/') || '**/*';
|
|
3090
|
+
const globbed = globSync(normalizedPattern, {
|
|
3091
|
+
cwd: searchRoot,
|
|
3092
|
+
nodir: true,
|
|
3093
|
+
dot: false,
|
|
3094
|
+
ignore: ['**/node_modules/**', '**/.git/**'],
|
|
3095
|
+
})
|
|
3096
|
+
.map((entry) => String(entry).replace(/\\/g, '/'))
|
|
3097
|
+
.sort((left, right) => left.localeCompare(right));
|
|
3098
|
+
return { success: true, output: globbed.slice(0, 200).join('\n') || '(no matches)' };
|
|
3041
3099
|
}
|
|
3100
|
+
walk(searchRoot);
|
|
3042
3101
|
return { success: true, output: matches.slice(0, 100).join('\n') || '(no matches)' };
|
|
3043
3102
|
}
|
|
3044
3103
|
if (name === 'syntax_check') {
|
|
@@ -3082,38 +3141,35 @@ menu {
|
|
|
3082
3141
|
return { success: false, output: '', error: 'run_command requires command.' };
|
|
3083
3142
|
try {
|
|
3084
3143
|
if (process.platform === 'win32') {
|
|
3085
|
-
command = this.normalizeWindowsRunCommand(command);
|
|
3144
|
+
command = this.normalizeWindowsRunCommand(command, serverRoot);
|
|
3086
3145
|
}
|
|
3087
3146
|
}
|
|
3088
3147
|
catch (error) {
|
|
3089
3148
|
return { success: false, output: '', error: error.message };
|
|
3090
3149
|
}
|
|
3091
3150
|
const cwd = this.resolveRunCommandCwd(rootPath, args, serverRoot);
|
|
3092
|
-
const
|
|
3093
|
-
&& /^(npm|npx|node|yarn|pnpm|git|python|pip|powershell)\b/i.test(command);
|
|
3094
|
-
const { exec } = await import('child_process');
|
|
3151
|
+
const { exec, execFile } = await import('child_process');
|
|
3095
3152
|
return await new Promise((resolve) => {
|
|
3096
|
-
const
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
};
|
|
3102
|
-
if (usePowerShell) {
|
|
3103
|
-
execOptions.shell = 'powershell.exe';
|
|
3104
|
-
execOptions.windowsHide = true;
|
|
3105
|
-
command = `-NoProfile -ExecutionPolicy Bypass -Command ${JSON.stringify(command)}`;
|
|
3106
|
-
}
|
|
3107
|
-
else if (process.platform === 'win32') {
|
|
3108
|
-
execOptions.shell = 'cmd.exe';
|
|
3109
|
-
}
|
|
3110
|
-
exec(command, execOptions, (error, stdout, stderr) => {
|
|
3153
|
+
const timeout = Number(args.timeout || 30000);
|
|
3154
|
+
const finish = (error, stdout, stderr) => {
|
|
3155
|
+
const stdoutText = String(stdout || '');
|
|
3156
|
+
const stderrText = String(stderr || '');
|
|
3157
|
+
const combined = [stdoutText, stderrText].filter(Boolean).join('\n').slice(0, 12000);
|
|
3111
3158
|
resolve({
|
|
3112
3159
|
success: !error,
|
|
3113
|
-
output:
|
|
3114
|
-
error: error ? String(
|
|
3160
|
+
output: combined,
|
|
3161
|
+
error: error ? String(error.message || error) : '',
|
|
3115
3162
|
});
|
|
3116
|
-
}
|
|
3163
|
+
};
|
|
3164
|
+
if (process.platform === 'win32') {
|
|
3165
|
+
const commandProcessor = process.env.ComSpec
|
|
3166
|
+
|| path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'cmd.exe');
|
|
3167
|
+
execFile(commandProcessor, ['/d', '/s', '/c', command], {
|
|
3168
|
+
cwd, timeout, maxBuffer: 1024 * 1024, env: process.env, windowsHide: true,
|
|
3169
|
+
}, finish);
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
exec(command, { cwd, timeout, maxBuffer: 1024 * 1024, env: process.env }, finish);
|
|
3117
3173
|
});
|
|
3118
3174
|
}
|
|
3119
3175
|
return { success: false, output: '', error: `Unsupported client tool: ${name}` };
|
|
@@ -3129,27 +3185,8 @@ menu {
|
|
|
3129
3185
|
if (!contextId || !callId)
|
|
3130
3186
|
return;
|
|
3131
3187
|
const backendUrl = responseUrl ? new URL(responseUrl).origin : (context.mcpContextBackendUrl || null);
|
|
3132
|
-
const emitStream = (streamEvent) => {
|
|
3133
|
-
if (typeof context.onStreamEvent === 'function') {
|
|
3134
|
-
try {
|
|
3135
|
-
context.onStreamEvent(streamEvent);
|
|
3136
|
-
}
|
|
3137
|
-
catch {
|
|
3138
|
-
// UI callbacks must never break the client tool bridge.
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
};
|
|
3142
|
-
emitStream({ type: 'tool_call', name: toolName, tool: toolName, arguments: event.arguments || {} });
|
|
3143
3188
|
try {
|
|
3144
3189
|
const result = await this.executeV3ClientToolRequest(event, context);
|
|
3145
|
-
emitStream({
|
|
3146
|
-
type: 'tool_result',
|
|
3147
|
-
name: toolName,
|
|
3148
|
-
tool: toolName,
|
|
3149
|
-
success: result.success,
|
|
3150
|
-
output: result.output,
|
|
3151
|
-
error: result.error || '',
|
|
3152
|
-
});
|
|
3153
3190
|
try {
|
|
3154
3191
|
await this.submitClientToolResult(contextId, callId, result, backendUrl);
|
|
3155
3192
|
}
|