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.
- package/dist/commands/chat.d.ts +3 -1
- package/dist/commands/chat.js +43 -43
- 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 +178 -29
- package/dist/commands/v4.d.ts +3 -0
- package/dist/commands/v4.js +12 -0
- package/dist/index.js +123 -59
- package/dist/utils/api.d.ts +1 -0
- package/dist/utils/api.js +83 -27
- 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/logger.js +6 -1
- package/dist/utils/requestIntent.d.ts +4 -0
- package/dist/utils/requestIntent.js +16 -1
- package/dist/utils/tools.js +18 -0
- package/package.json +6 -3
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
|
}
|
|
@@ -585,8 +633,13 @@ export async function main(args) {
|
|
|
585
633
|
!jsonOutputRequested &&
|
|
586
634
|
!directPromptRequested &&
|
|
587
635
|
!helpOrVersionRequested) {
|
|
636
|
+
// process.stdout.columns is not always a reliable indicator of how
|
|
637
|
+
// many columns actually render visually (seen mismatching badly in some
|
|
638
|
+
// embedded/split-pane terminal hosts on Windows) -- cap the box at a
|
|
639
|
+
// conservative width so it never overflows even when the reported
|
|
640
|
+
// column count is larger than the real usable width.
|
|
588
641
|
const terminalWidth = Math.max(36, Number(process.stdout.columns || 80));
|
|
589
|
-
const boxWidth = Math.max(34, Math.min(
|
|
642
|
+
const boxWidth = Math.max(34, Math.min(44, terminalWidth - 8));
|
|
590
643
|
const innerWidth = boxWidth;
|
|
591
644
|
const fit = (text) => {
|
|
592
645
|
const clean = String(text || '').replace(/\s+/g, ' ').trim();
|
|
@@ -641,7 +694,7 @@ export async function main(args) {
|
|
|
641
694
|
.option('--auto-approve', 'Auto-approve agent actions (dangerous!)', false)
|
|
642
695
|
.option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
|
|
643
696
|
.action(async (options) => {
|
|
644
|
-
const chat = new ChatCommand(config, logger);
|
|
697
|
+
const chat = new ChatCommand(config, logger, program);
|
|
645
698
|
await chat.run({
|
|
646
699
|
model: options.model,
|
|
647
700
|
project: options.project,
|
|
@@ -670,7 +723,7 @@ export async function main(args) {
|
|
|
670
723
|
.option('--auto-approve', 'Auto-approve agent actions (dangerous!)', false)
|
|
671
724
|
.option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
|
|
672
725
|
.action(async (options) => {
|
|
673
|
-
const chat = new ChatCommand(config, logger);
|
|
726
|
+
const chat = new ChatCommand(config, logger, program);
|
|
674
727
|
await chat.run({
|
|
675
728
|
model: options.model,
|
|
676
729
|
project: options.project,
|
|
@@ -702,7 +755,7 @@ export async function main(args) {
|
|
|
702
755
|
.option('--auto-approve', 'Auto-approve all actions (dangerous!)', false)
|
|
703
756
|
.option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
|
|
704
757
|
.action(async (options) => {
|
|
705
|
-
const chat = new ChatCommand(config, logger);
|
|
758
|
+
const chat = new ChatCommand(config, logger, program);
|
|
706
759
|
await chat.run({
|
|
707
760
|
model: options.model,
|
|
708
761
|
project: options.project,
|
|
@@ -732,7 +785,7 @@ export async function main(args) {
|
|
|
732
785
|
.option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
|
|
733
786
|
.option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
|
|
734
787
|
.action(async (options) => {
|
|
735
|
-
const chat = new ChatCommand(config, logger);
|
|
788
|
+
const chat = new ChatCommand(config, logger, program);
|
|
736
789
|
await chat.run({
|
|
737
790
|
model: options.model,
|
|
738
791
|
project: options.project,
|
|
@@ -747,37 +800,7 @@ export async function main(args) {
|
|
|
747
800
|
bridge: options.bridge,
|
|
748
801
|
});
|
|
749
802
|
});
|
|
750
|
-
program
|
|
751
|
-
.command('v4')
|
|
752
|
-
.alias('v4-agent')
|
|
753
|
-
.description('Launch Vigthoria V4 Operating Agent (DeerFlow 2.0 harness)')
|
|
754
|
-
.option('--provider <provider>', 'Provider: cloud | v3_local | byok')
|
|
755
|
-
.option('--api-key <key>', 'BYOK API key for non-interactive mode')
|
|
756
|
-
.option('--base-url <url>', 'Provider base URL override')
|
|
757
|
-
.option('--model <name>', 'Provider model override')
|
|
758
|
-
.option('--transport <mode>', 'Bridge transport: auto | ws | sse', 'auto')
|
|
759
|
-
.option('--harness-stream-url <url>', 'Override harness stream URL')
|
|
760
|
-
.option('--harness-control-url <url>', 'Override harness control URL')
|
|
761
|
-
.option('--session-id <id>', 'Explicit V4 session id')
|
|
762
|
-
.option('--non-interactive', 'Disable interactive menu and use CLI options', false)
|
|
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())
|
|
765
|
-
.action(async (options) => {
|
|
766
|
-
const v4 = new V4Command(config, logger);
|
|
767
|
-
await v4.run({
|
|
768
|
-
provider: options.provider,
|
|
769
|
-
apiKey: options.apiKey,
|
|
770
|
-
baseUrl: options.baseUrl,
|
|
771
|
-
model: options.model,
|
|
772
|
-
transport: options.transport,
|
|
773
|
-
harnessStreamUrl: options.harnessStreamUrl,
|
|
774
|
-
harnessControlUrl: options.harnessControlUrl,
|
|
775
|
-
sessionId: options.sessionId,
|
|
776
|
-
nonInteractive: options.nonInteractive,
|
|
777
|
-
python: options.python,
|
|
778
|
-
v4Path: options.v4Path,
|
|
779
|
-
});
|
|
780
|
-
});
|
|
803
|
+
new V4Command(config, logger).register(program);
|
|
781
804
|
program
|
|
782
805
|
.command('v4-menu')
|
|
783
806
|
.description('Internal: interactive V3/V4 agent + provider picker used by the V4 Operating Agent launcher')
|
|
@@ -1138,20 +1161,21 @@ export async function main(args) {
|
|
|
1138
1161
|
await music.status(taskId, options);
|
|
1139
1162
|
});
|
|
1140
1163
|
// ==================== REPO COMMANDS ====================
|
|
1141
|
-
// Repo command - Push/Pull projects to/from Vigthoria Repository
|
|
1164
|
+
// Repo command - Push/Pull projects to/from Vigthoria Community Repository
|
|
1142
1165
|
const repoCommand = program
|
|
1143
1166
|
.command('repo')
|
|
1144
1167
|
.alias('repository')
|
|
1145
|
-
.description('Push and pull projects to/from your Vigthoria Repository');
|
|
1168
|
+
.description('Push and pull projects to/from your Vigthoria Community Repository');
|
|
1146
1169
|
repoCommand
|
|
1147
1170
|
.command('push [path]')
|
|
1148
1171
|
.alias('upload')
|
|
1149
|
-
.description('Push current or specified project to Vigthoria
|
|
1172
|
+
.description('Push current or specified project to Vigthoria Community Repository')
|
|
1150
1173
|
.option('-n, --name <projectName>', 'Project name override for the remote repo')
|
|
1151
1174
|
.option('-v, --visibility <type>', 'Set visibility (private, restricted, public)', 'private')
|
|
1152
1175
|
.option('-d, --description <text>', 'Project description')
|
|
1153
1176
|
.option('-f, --force', 'Overwrite existing project', false)
|
|
1154
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')
|
|
1155
1179
|
.option('--open-in <engine>', 'After push, open project in engine: shop, visual, game')
|
|
1156
1180
|
.option('--browser', 'Open result URL in default browser', false)
|
|
1157
1181
|
.action(async (pathArg, options) => {
|
|
@@ -1162,7 +1186,8 @@ export async function main(args) {
|
|
|
1162
1186
|
visibility: options.visibility,
|
|
1163
1187
|
description: options.description,
|
|
1164
1188
|
force: options.force,
|
|
1165
|
-
yes: options.yes
|
|
1189
|
+
yes: options.yes,
|
|
1190
|
+
retry: options.retry
|
|
1166
1191
|
});
|
|
1167
1192
|
if (options.openIn) {
|
|
1168
1193
|
const engine = options.openIn;
|
|
@@ -1174,10 +1199,18 @@ export async function main(args) {
|
|
|
1174
1199
|
}
|
|
1175
1200
|
}
|
|
1176
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
|
+
});
|
|
1177
1210
|
repoCommand
|
|
1178
1211
|
.command('pull <name>')
|
|
1179
1212
|
.alias('download')
|
|
1180
|
-
.description('Pull a project from your Vigthoria
|
|
1213
|
+
.description('Pull a project from your Vigthoria Community Repository')
|
|
1181
1214
|
.option('-o, --output <path>', 'Output directory path')
|
|
1182
1215
|
.option('-f, --force', 'Overwrite existing directory', false)
|
|
1183
1216
|
.action(async (name, options) => {
|
|
@@ -1190,7 +1223,7 @@ export async function main(args) {
|
|
|
1190
1223
|
repoCommand
|
|
1191
1224
|
.command('list')
|
|
1192
1225
|
.alias('ls')
|
|
1193
|
-
.description('List all your projects in Vigthoria
|
|
1226
|
+
.description('List all your projects in Vigthoria Community Repository')
|
|
1194
1227
|
.option('-v, --visibility <type>', 'Filter by visibility (private, restricted, public)')
|
|
1195
1228
|
.action(async (options) => {
|
|
1196
1229
|
const repo = new RepoCommand(config, logger);
|
|
@@ -1214,14 +1247,14 @@ export async function main(args) {
|
|
|
1214
1247
|
repoCommand
|
|
1215
1248
|
.command('delete <name>')
|
|
1216
1249
|
.alias('rm')
|
|
1217
|
-
.description('Remove a project from your Vigthoria
|
|
1250
|
+
.description('Remove a project from your Vigthoria Community Repository')
|
|
1218
1251
|
.action(async (name) => {
|
|
1219
1252
|
const repo = new RepoCommand(config, logger);
|
|
1220
1253
|
await repo.delete(name);
|
|
1221
1254
|
});
|
|
1222
1255
|
repoCommand
|
|
1223
1256
|
.command('clone <url>')
|
|
1224
|
-
.description('Clone a public project from Vigthoria
|
|
1257
|
+
.description('Clone a public project from Vigthoria Community')
|
|
1225
1258
|
.option('-o, --output <path>', 'Output directory path')
|
|
1226
1259
|
.option('-f, --force', 'Overwrite existing directory', false)
|
|
1227
1260
|
.action(async (url, options) => {
|
|
@@ -1233,7 +1266,7 @@ export async function main(args) {
|
|
|
1233
1266
|
});
|
|
1234
1267
|
repoCommand
|
|
1235
1268
|
.command('open-in <engine> [projectName]')
|
|
1236
|
-
.description('Open a Vigthoria
|
|
1269
|
+
.description('Open a Vigthoria Community Repository project in the Shop Engine, Visual Editor, or Gaming Engine')
|
|
1237
1270
|
.option('--browser', 'Open result URL in default browser', false)
|
|
1238
1271
|
.option('--shop-id <id>', 'Target shop ID (for shop engine)', 'default')
|
|
1239
1272
|
.addHelpText('after', `
|
|
@@ -1411,6 +1444,12 @@ Examples:
|
|
|
1411
1444
|
screenshot: options.screenshot,
|
|
1412
1445
|
});
|
|
1413
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());
|
|
1414
1453
|
// ==================== LEGION COMMAND ====================
|
|
1415
1454
|
program
|
|
1416
1455
|
.command('legion [request]')
|
|
@@ -1671,10 +1710,23 @@ Examples:
|
|
|
1671
1710
|
console.log(chalk.gray('Continuing with npm registry check...'));
|
|
1672
1711
|
}
|
|
1673
1712
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
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
|
+
}
|
|
1678
1730
|
}
|
|
1679
1731
|
const effectiveLatest = maxVersion(manifestEntry?.version, npmVersion);
|
|
1680
1732
|
if (!effectiveLatest) {
|
|
@@ -1682,11 +1734,6 @@ Examples:
|
|
|
1682
1734
|
process.exitCode = 1;
|
|
1683
1735
|
return;
|
|
1684
1736
|
}
|
|
1685
|
-
if (manifestEntry?.version
|
|
1686
|
-
&& npmVersion
|
|
1687
|
-
&& compareVersions(npmVersion, manifestEntry.version) > 0) {
|
|
1688
|
-
console.log(chalk.yellow(`Release manifest (${manifestEntry.version}) is behind npm (${npmVersion}); npm will be used when needed.`));
|
|
1689
|
-
}
|
|
1690
1737
|
if (!allowDowngrade && compareVersions(effectiveLatest, currentVersion) <= 0) {
|
|
1691
1738
|
console.log(chalk.green(`You are running the latest version (${currentVersion})`));
|
|
1692
1739
|
return;
|
|
@@ -1802,6 +1849,21 @@ Examples:
|
|
|
1802
1849
|
const configCmd = new ConfigCommand(config, logger);
|
|
1803
1850
|
await configCmd.init();
|
|
1804
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
|
+
});
|
|
1805
1867
|
const codingCommandDefinitions = [
|
|
1806
1868
|
{ name: 'edit', description: 'Edit code by describing the desired change', instruction: 'Edit the project according to this request' },
|
|
1807
1869
|
{ name: 'generate', description: 'Generate code from a prompt', instruction: 'Generate code for this request' },
|
|
@@ -1818,7 +1880,7 @@ Examples:
|
|
|
1818
1880
|
.option('-p, --project <path>', 'Project directory', process.cwd())
|
|
1819
1881
|
.action(async (requestParts = [], options) => {
|
|
1820
1882
|
const requestText = requestParts.join(' ').trim();
|
|
1821
|
-
const chat = new ChatCommand(config, logger);
|
|
1883
|
+
const chat = new ChatCommand(config, logger, program);
|
|
1822
1884
|
await chat.run({
|
|
1823
1885
|
model: options.model || 'code',
|
|
1824
1886
|
project: options.project || process.cwd(),
|
|
@@ -1829,8 +1891,10 @@ Examples:
|
|
|
1829
1891
|
try {
|
|
1830
1892
|
// Default to chat if no command
|
|
1831
1893
|
if (args.length === 2) {
|
|
1832
|
-
const
|
|
1833
|
-
|
|
1894
|
+
const selectedArgs = await selectInteractiveCommand(program);
|
|
1895
|
+
if (selectedArgs) {
|
|
1896
|
+
await program.parseAsync([args[0], args[1], ...selectedArgs]);
|
|
1897
|
+
}
|
|
1834
1898
|
process.exitCode = 0;
|
|
1835
1899
|
return;
|
|
1836
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,6 +3,7 @@
|
|
|
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';
|
|
@@ -743,8 +744,8 @@ export class APIClient {
|
|
|
743
744
|
const urls = [
|
|
744
745
|
process.env.VIGTHORIA_TEMPLATE_SERVICE_URL,
|
|
745
746
|
process.env.TEMPLATE_SERVICE_URL,
|
|
746
|
-
'http://127.0.0.1:4011',
|
|
747
747
|
`${configuredApiUrl}/api/template-service`,
|
|
748
|
+
'http://127.0.0.1:4011',
|
|
748
749
|
].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
|
|
749
750
|
return [...new Set(urls)];
|
|
750
751
|
}
|
|
@@ -842,7 +843,15 @@ export class APIClient {
|
|
|
842
843
|
const baseName = path.posix.basename(relativePath).toLowerCase();
|
|
843
844
|
const depth = relativePath.split('/').length - 1;
|
|
844
845
|
let score = 0;
|
|
845
|
-
|
|
846
|
+
const normalizedPath = relativePath.toLowerCase();
|
|
847
|
+
const topLevelDir = normalizedPath.split('/')[0];
|
|
848
|
+
if (['dist', 'build', 'out'].includes(topLevelDir)) {
|
|
849
|
+
score += 320;
|
|
850
|
+
}
|
|
851
|
+
else if (topLevelDir === 'public') {
|
|
852
|
+
score += 180;
|
|
853
|
+
}
|
|
854
|
+
if (normalizedPath === 'index.html') {
|
|
846
855
|
score += 200;
|
|
847
856
|
}
|
|
848
857
|
if (baseName === 'index.html') {
|
|
@@ -896,6 +905,34 @@ export class APIClient {
|
|
|
896
905
|
js: Array.from(js),
|
|
897
906
|
};
|
|
898
907
|
}
|
|
908
|
+
async buildFrontendForPreview(rootPath) {
|
|
909
|
+
const packagePath = path.join(rootPath, 'package.json');
|
|
910
|
+
if (!fs.existsSync(packagePath)) {
|
|
911
|
+
return { attempted: false, succeeded: false };
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
915
|
+
if (!packageJson.scripts?.build) {
|
|
916
|
+
return { attempted: false, succeeded: false };
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
catch (error) {
|
|
920
|
+
return { attempted: false, succeeded: false, error: `Invalid package.json: ${error?.message || String(error)}` };
|
|
921
|
+
}
|
|
922
|
+
const npmCommand = process.platform === 'win32' ? (process.env.ComSpec || 'cmd.exe') : 'npm';
|
|
923
|
+
const npmArgs = process.platform === 'win32'
|
|
924
|
+
? ['/d', '/s', '/c', 'npm run build']
|
|
925
|
+
: ['run', 'build'];
|
|
926
|
+
return await new Promise((resolve) => {
|
|
927
|
+
execFile(npmCommand, npmArgs, { cwd: rootPath, timeout: 150_000, windowsHide: true, maxBuffer: 2 * 1024 * 1024 }, (error) => {
|
|
928
|
+
if (error) {
|
|
929
|
+
resolve({ attempted: true, succeeded: false, error: sanitizeUserFacingErrorText(error.message).slice(0, 300) });
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
resolve({ attempted: true, succeeded: true });
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
}
|
|
899
936
|
gatherFrontendPreviewArtifacts(message = '', context = {}) {
|
|
900
937
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
901
938
|
if (!rootPath || !fs.existsSync(rootPath)) {
|
|
@@ -1051,12 +1088,22 @@ export class APIClient {
|
|
|
1051
1088
|
return { required: false, passed: true };
|
|
1052
1089
|
}
|
|
1053
1090
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
1054
|
-
|
|
1091
|
+
let artifacts = this.gatherFrontendPreviewArtifacts(message, context);
|
|
1092
|
+
let buildResult = null;
|
|
1093
|
+
if (rootPath && artifacts.error?.includes('at least one HTML entry file')) {
|
|
1094
|
+
buildResult = await this.buildFrontendForPreview(rootPath);
|
|
1095
|
+
if (buildResult.attempted) {
|
|
1096
|
+
artifacts = this.gatherFrontendPreviewArtifacts(message, context);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1055
1099
|
if (artifacts.error || !artifacts.htmlPath || typeof artifacts.html !== 'string') {
|
|
1100
|
+
const buildDetail = buildResult?.attempted
|
|
1101
|
+
? ` Build retry ${buildResult.succeeded ? 'completed but produced no HTML entry' : `failed: ${buildResult.error || 'unknown build error'}`}.`
|
|
1102
|
+
: '';
|
|
1056
1103
|
return {
|
|
1057
1104
|
required: true,
|
|
1058
1105
|
passed: false,
|
|
1059
|
-
error: artifacts.error || 'Frontend preview gate could not collect frontend artifacts.'
|
|
1106
|
+
error: `${artifacts.error || 'Frontend preview gate could not collect frontend artifacts.'}${buildDetail}`,
|
|
1060
1107
|
};
|
|
1061
1108
|
}
|
|
1062
1109
|
// Cap artifact sizes to prevent 413 Payload Too Large
|
|
@@ -1083,11 +1130,19 @@ export class APIClient {
|
|
|
1083
1130
|
error: 'Preview proof skipped: payload exceeds size limit.',
|
|
1084
1131
|
};
|
|
1085
1132
|
}
|
|
1133
|
+
const internalServiceKey = process.env.VIGTHORIA_TEMPLATE_SERVICE_KEY
|
|
1134
|
+
|| process.env.V3_SERVICE_KEY;
|
|
1135
|
+
const authToken = this.getAccessToken();
|
|
1086
1136
|
const response = await fetch(`${baseUrl}/preview-proof`, {
|
|
1087
1137
|
method: 'POST',
|
|
1088
1138
|
headers: {
|
|
1089
1139
|
'Content-Type': 'application/json',
|
|
1090
1140
|
Accept: 'application/json',
|
|
1141
|
+
...(authToken ? {
|
|
1142
|
+
Authorization: `Bearer ${authToken}`,
|
|
1143
|
+
Cookie: `vigthoria-auth-token=${authToken}`,
|
|
1144
|
+
} : {}),
|
|
1145
|
+
...(internalServiceKey ? { 'x-vigthoria-service-key': internalServiceKey } : {}),
|
|
1091
1146
|
},
|
|
1092
1147
|
body: proofPayload,
|
|
1093
1148
|
});
|
|
@@ -1772,10 +1827,14 @@ export class APIClient {
|
|
|
1772
1827
|
return finish();
|
|
1773
1828
|
}
|
|
1774
1829
|
/** Normalize common Unix-only commands before Windows cmd/powershell execution. */
|
|
1775
|
-
normalizeWindowsRunCommand(command) {
|
|
1830
|
+
normalizeWindowsRunCommand(command, serverRoot = '') {
|
|
1776
1831
|
let cmd = String(command || '').trim();
|
|
1777
1832
|
if (!cmd)
|
|
1778
1833
|
return cmd;
|
|
1834
|
+
if (serverRoot) {
|
|
1835
|
+
const escapedRoot = serverRoot.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1836
|
+
cmd = cmd.replace(new RegExp(`^\\s*cd\\s+(?:[\"']?${escapedRoot}[\"']?)\\s*(?:&&|;)\\s*`, 'i'), '');
|
|
1837
|
+
}
|
|
1779
1838
|
if (/^\/[^\s/]/.test(cmd) && !/^\/[a-z]:/i.test(cmd)) {
|
|
1780
1839
|
throw new Error('Command starts with "/" which Windows interprets as an invalid switch. Use workspace-relative paths without a leading slash.');
|
|
1781
1840
|
}
|
|
@@ -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}` };
|
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
* Brain Hub client — sync workspace index + fetch account brain context
|
|
3
3
|
* via coder.vigthoria.io (JWT-authenticated).
|
|
4
4
|
*/
|
|
5
|
+
// Brain Hub calls are an optional personalization enhancement to an agent
|
|
6
|
+
// turn, not on the critical path. Native `fetch()` has NO default timeout,
|
|
7
|
+
// so a slow/unresponsive coder.vigthoria.io backend previously caused the
|
|
8
|
+
// ENTIRE agent turn to hang indefinitely with zero console output, well
|
|
9
|
+
// before any "ROUTING DECISION" text is printed (2026-07-10 root-cause).
|
|
10
|
+
const BRAIN_HUB_TIMEOUT_MS = 3500;
|
|
11
|
+
async function fetchWithTimeout(url, init = {}, timeoutMs = BRAIN_HUB_TIMEOUT_MS) {
|
|
12
|
+
const controller = new AbortController();
|
|
13
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
14
|
+
try {
|
|
15
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
5
21
|
export class BrainHubClient {
|
|
6
22
|
apiBase;
|
|
7
23
|
getAuthToken;
|
|
@@ -22,27 +38,39 @@ export class BrainHubClient {
|
|
|
22
38
|
if (!headers.Authorization) {
|
|
23
39
|
return { ok: false, skipped: true, reason: 'not_authenticated' };
|
|
24
40
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
try {
|
|
42
|
+
const resp = await fetchWithTimeout(`${this.apiBase}/api/brain/sync-index`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers,
|
|
45
|
+
body: JSON.stringify(payload),
|
|
46
|
+
});
|
|
47
|
+
const data = await resp.json().catch(() => ({}));
|
|
48
|
+
if (!resp.ok) {
|
|
49
|
+
return { ok: false, error: data.error || `HTTP ${resp.status}` };
|
|
50
|
+
}
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const timedOut = error instanceof Error && error.name === 'AbortError';
|
|
55
|
+
return { ok: false, error: timedOut ? `timeout after ${BRAIN_HUB_TIMEOUT_MS}ms` : String(error) };
|
|
33
56
|
}
|
|
34
|
-
return data;
|
|
35
57
|
}
|
|
36
58
|
async fetchAccountContext(limit = 25) {
|
|
37
59
|
const headers = await this.headers();
|
|
38
60
|
if (!headers.Authorization) {
|
|
39
61
|
return { ok: false, formattedText: '', memories: [] };
|
|
40
62
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
63
|
+
try {
|
|
64
|
+
const resp = await fetchWithTimeout(`${this.apiBase}/api/brain/context?limit=${limit}`, { headers });
|
|
65
|
+
const data = await resp.json().catch(() => ({}));
|
|
66
|
+
if (!resp.ok) {
|
|
67
|
+
return { ok: false, formattedText: '', error: data.error || `HTTP ${resp.status}` };
|
|
68
|
+
}
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const timedOut = error instanceof Error && error.name === 'AbortError';
|
|
73
|
+
return { ok: false, formattedText: '', error: timedOut ? `timeout after ${BRAIN_HUB_TIMEOUT_MS}ms` : String(error) };
|
|
45
74
|
}
|
|
46
|
-
return data;
|
|
47
75
|
}
|
|
48
76
|
}
|