vigthoria-cli 1.13.23 ā 1.13.25
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/completions/_vigthoria +1 -1
- package/completions/vigthoria.fish +1 -1
- package/dist/commands/auth.js +69 -17
- package/dist/commands/chat.js +41 -1
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +33 -9
- package/dist/commands/game.d.ts +4 -0
- package/dist/commands/game.js +19 -1
- package/dist/commands/hub.d.ts +2 -0
- package/dist/commands/hub.js +61 -78
- package/dist/commands/legion.d.ts +1 -0
- package/dist/commands/legion.js +7 -7
- package/dist/commands/platform-registration.js +1 -1
- package/dist/commands/product-run-registration.js +4 -2
- package/dist/commands/repo.d.ts +2 -0
- package/dist/commands/repo.js +37 -0
- package/dist/commands/security.d.ts +3 -0
- package/dist/commands/security.js +29 -9
- package/dist/index.js +16 -2
- package/dist/utils/api.js +14 -11
- package/dist/utils/chat-prompt-policy.js +1 -1
- package/dist/utils/code-operations-service.js +43 -14
- package/dist/utils/config.js +5 -2
- package/dist/utils/local-security-service.d.ts +23 -0
- package/dist/utils/local-security-service.js +210 -0
- package/dist/utils/model-transport-service.js +66 -6
- package/dist/utils/network-policy.d.ts +1 -1
- package/dist/utils/network-policy.js +3 -2
- package/dist/utils/preview-screenshot-adapter.js +2 -2
- package/dist/utils/secret-policy.js +27 -18
- package/dist/utils/subscription-policy.d.ts +11 -0
- package/dist/utils/subscription-policy.js +32 -0
- package/dist/utils/v3-workspace-service.js +5 -1
- package/dist/utils/vigflow-client.js +2 -2
- package/package.json +3 -11
- package/release-policy.json +2 -1
- package/scripts/release/generate-release-evidence.mjs +49 -0
- package/scripts/release/publish-cli-release.mjs +19 -8
- package/scripts/release/validate-no-go-gates.sh +2 -0
package/completions/_vigthoria
CHANGED
|
@@ -36,7 +36,7 @@ _vigthoria() {
|
|
|
36
36
|
'replay:Replay events from a V3 agent run step-by-step'
|
|
37
37
|
'repo:Push and pull projects to/from your Vigthoria Community Repository'
|
|
38
38
|
'review:Review code quality'
|
|
39
|
-
'security:Run security scans, scores, and fix plans
|
|
39
|
+
'security:Run bounded local security scans, scores, and reviewed fix plans'
|
|
40
40
|
'status:Show authentication and subscription status'
|
|
41
41
|
'update:Check for updates and upgrade Vigthoria CLI. Default manifest: https://extension.vigthoria.io/downloads/manifest.json'
|
|
42
42
|
'v4:Launch Vigthoria V4 Operating Agent (DeerFlow 2.0 harness)'
|
|
@@ -32,7 +32,7 @@ complete -c vigthoria -c vig -c vigthoria-chat -n '__fish_use_subcommand' -a 'pr
|
|
|
32
32
|
complete -c vigthoria -c vig -c vigthoria-chat -n '__fish_use_subcommand' -a 'replay' -d 'Replay events from a V3 agent run step-by-step'
|
|
33
33
|
complete -c vigthoria -c vig -c vigthoria-chat -n '__fish_use_subcommand' -a 'repo' -d 'Push and pull projects to/from your Vigthoria Community Repository'
|
|
34
34
|
complete -c vigthoria -c vig -c vigthoria-chat -n '__fish_use_subcommand' -a 'review' -d 'Review code quality'
|
|
35
|
-
complete -c vigthoria -c vig -c vigthoria-chat -n '__fish_use_subcommand' -a 'security' -d 'Run security scans, scores, and fix plans
|
|
35
|
+
complete -c vigthoria -c vig -c vigthoria-chat -n '__fish_use_subcommand' -a 'security' -d 'Run bounded local security scans, scores, and reviewed fix plans'
|
|
36
36
|
complete -c vigthoria -c vig -c vigthoria-chat -n '__fish_use_subcommand' -a 'status' -d 'Show authentication and subscription status'
|
|
37
37
|
complete -c vigthoria -c vig -c vigthoria-chat -n '__fish_use_subcommand' -a 'update' -d 'Check for updates and upgrade Vigthoria CLI. Default manifest: https://extension.vigthoria.io/downloads/manifest.json'
|
|
38
38
|
complete -c vigthoria -c vig -c vigthoria-chat -n '__fish_use_subcommand' -a 'v4' -d 'Launch Vigthoria V4 Operating Agent (DeerFlow 2.0 harness)'
|
package/dist/commands/auth.js
CHANGED
|
@@ -2,9 +2,10 @@ import chalk from 'chalk';
|
|
|
2
2
|
import readline from 'readline';
|
|
3
3
|
import { Config } from '../utils/config.js';
|
|
4
4
|
import { clearGatewayPreflightCache } from '../utils/cli-state.js';
|
|
5
|
-
import { AuthSessionService } from '../utils/auth-session.js';
|
|
5
|
+
import { AuthSessionService, validateTokenStructure } from '../utils/auth-session.js';
|
|
6
6
|
import { assertTrustedEndpoint, guardedFetch } from '../utils/network-policy.js';
|
|
7
7
|
import { CliCommandError, commandFailure } from '../utils/command-contract.js';
|
|
8
|
+
import { normalizeSubscriptionResponse } from '../utils/subscription-policy.js';
|
|
8
9
|
const DEFAULT_API_URL = 'https://coder.vigthoria.io';
|
|
9
10
|
const KNOWN_AUTH_BASE_URLS = ['https://coder.vigthoria.io'];
|
|
10
11
|
class HttpError extends Error {
|
|
@@ -228,13 +229,17 @@ export async function login(email, password) {
|
|
|
228
229
|
}
|
|
229
230
|
const authBases = getAuthBaseCandidates(getApiUrl());
|
|
230
231
|
const endpointVariants = [
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
// `/api/login` is the public Coder credential exchange. Older routes are
|
|
233
|
+
// compatibility fallbacks and may only be tried when a route is absent;
|
|
234
|
+
// retrying a decisive auth response consumes rate-limit budget and can
|
|
235
|
+
// turn an authorization or server-contract failure into a false
|
|
236
|
+
// "invalid password" report.
|
|
234
237
|
{ path: '/api/login', body: { email: resolvedEmail, password: resolvedPassword } },
|
|
238
|
+
{ path: '/api/login-json', body: { identifier: resolvedEmail, password: resolvedPassword } },
|
|
239
|
+
{ path: '/api/auth/login', body: { email: resolvedEmail, password: resolvedPassword } },
|
|
240
|
+
{ path: '/auth/login', body: { email: resolvedEmail, password: resolvedPassword } },
|
|
235
241
|
];
|
|
236
242
|
const attempted = [];
|
|
237
|
-
const failures = [];
|
|
238
243
|
for (const base of authBases) {
|
|
239
244
|
for (const variant of endpointVariants) {
|
|
240
245
|
const endpoint = `${trimTrailingSlash(base)}${variant.path}`;
|
|
@@ -246,19 +251,35 @@ export async function login(email, password) {
|
|
|
246
251
|
});
|
|
247
252
|
const token = extractAuthToken(result);
|
|
248
253
|
if (!token) {
|
|
249
|
-
|
|
250
|
-
|
|
254
|
+
throw new CliCommandError('The Vigthoria login service accepted the request but did not issue an access token.', {
|
|
255
|
+
code: 'LOGIN_TOKEN_MISSING', category: 'authentication', status: 502,
|
|
256
|
+
});
|
|
251
257
|
}
|
|
252
258
|
const normalizedBase = trimTrailingSlash(base);
|
|
253
|
-
|
|
259
|
+
try {
|
|
260
|
+
validateTokenStructure(token, normalizedBase);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
throw new CliCommandError(`The Vigthoria login service issued a token that the CLI cannot safely accept: ${humanMessage(error)}`, {
|
|
264
|
+
code: 'LOGIN_TOKEN_INVALID', category: 'authentication', status: 502, cause: error,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
254
267
|
const identity = extractAuthUser(result, resolvedEmail);
|
|
255
268
|
const refreshToken = result.tokens?.refresh_token || undefined;
|
|
256
269
|
await new AuthSessionService(new Config()).validateAndPersistToken(token, {
|
|
257
270
|
apiUrl: normalizedBase,
|
|
258
271
|
refreshToken,
|
|
259
272
|
identity: { userId: identity?.id, email: identity?.email },
|
|
260
|
-
v3ServiceKey:
|
|
273
|
+
v3ServiceKey: null,
|
|
261
274
|
});
|
|
275
|
+
const issuedSubscription = normalizeSubscriptionResponse(result);
|
|
276
|
+
if (issuedSubscription)
|
|
277
|
+
new Config().setSubscription(issuedSubscription);
|
|
278
|
+
// Only present the newly issued token to secondary authenticated
|
|
279
|
+
// endpoints after its structure and Coder acceptance are proven.
|
|
280
|
+
const v3ServiceKey = await fetchV3ServiceKey(normalizedBase, token);
|
|
281
|
+
if (v3ServiceKey)
|
|
282
|
+
new Config().setAuth({ token, v3ServiceKey });
|
|
262
283
|
const config = {
|
|
263
284
|
apiUrl: normalizedBase,
|
|
264
285
|
token,
|
|
@@ -270,15 +291,41 @@ export async function login(email, password) {
|
|
|
270
291
|
return config;
|
|
271
292
|
}
|
|
272
293
|
catch (error) {
|
|
294
|
+
if (error instanceof CliCommandError)
|
|
295
|
+
throw error;
|
|
273
296
|
if (error instanceof HttpError && error.code === 'SUBSCRIPTION_REQUIRED') {
|
|
274
297
|
throw error;
|
|
275
298
|
}
|
|
276
|
-
|
|
277
|
-
|
|
299
|
+
if (error instanceof HttpError && (error.status === 404 || error.status === 405)) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (error instanceof HttpError && error.status === 401) {
|
|
303
|
+
throw new CliCommandError('Invalid email or password. Please check your credentials and try again.', {
|
|
304
|
+
code: error.code || 'LOGIN_FAILED', category: 'authentication', status: error.status, cause: error,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
if (error instanceof HttpError && error.status === 403) {
|
|
308
|
+
throw new CliCommandError(humanMessage(error), {
|
|
309
|
+
code: error.code || 'LOGIN_FORBIDDEN', category: 'authorization', status: error.status, cause: error,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (error instanceof HttpError && error.status === 429) {
|
|
313
|
+
throw new CliCommandError('Too many login attempts. Wait for the authentication rate limit to reset, then try once.', {
|
|
314
|
+
code: 'LOGIN_RATE_LIMITED', category: 'authentication', status: error.status, cause: error,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
if (error instanceof HttpError && error.status >= 500) {
|
|
318
|
+
throw new CliCommandError(`Vigthoria authentication is temporarily unavailable: ${humanMessage(error)}`, {
|
|
319
|
+
code: error.code || 'LOGIN_SERVICE_UNAVAILABLE', category: 'network', status: error.status, cause: error,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
throw error;
|
|
278
323
|
}
|
|
279
324
|
}
|
|
280
325
|
}
|
|
281
|
-
throw new
|
|
326
|
+
throw new CliCommandError(`No supported login route is available on the configured Vigthoria endpoint. Tried ${attempted.join(', ')}.`, {
|
|
327
|
+
code: 'LOGIN_ROUTE_UNAVAILABLE', category: 'configuration', status: 404,
|
|
328
|
+
});
|
|
282
329
|
}
|
|
283
330
|
catch (error) {
|
|
284
331
|
const message = humanMessage(error);
|
|
@@ -426,12 +473,14 @@ export async function handleLogin(options = {}) {
|
|
|
426
473
|
});
|
|
427
474
|
if (subResponse.ok) {
|
|
428
475
|
const sub = await subResponse.json();
|
|
476
|
+
const normalizedSubscription = normalizeSubscriptionResponse(sub);
|
|
429
477
|
const subRecord = (sub.subscription && typeof sub.subscription === 'object') ? sub.subscription : {};
|
|
430
478
|
const userRecord = (sub.user && typeof sub.user === 'object') ? sub.user : {};
|
|
431
|
-
const plan = String(sub.plan || subRecord.plan || userRecord.plan || userRecord.subscription_plan || '').trim();
|
|
432
479
|
const role = String(sub.role || subRecord.role || userRecord.role || userRecord.user_role || '').trim();
|
|
433
|
-
if (
|
|
434
|
-
|
|
480
|
+
if (normalizedSubscription)
|
|
481
|
+
new Config().setSubscription(normalizedSubscription);
|
|
482
|
+
if (normalizedSubscription) {
|
|
483
|
+
console.log(`Plan: ${normalizedSubscription.plan.toUpperCase()}`);
|
|
435
484
|
}
|
|
436
485
|
if (role) {
|
|
437
486
|
console.log(`Role: ${role}`);
|
|
@@ -565,8 +614,11 @@ export function registerAuthCommands(program) {
|
|
|
565
614
|
try {
|
|
566
615
|
const user = await whoami();
|
|
567
616
|
if (!user) {
|
|
568
|
-
|
|
569
|
-
|
|
617
|
+
throw new CliCommandError('Not logged in.', {
|
|
618
|
+
code: 'AUTHENTICATION_REQUIRED',
|
|
619
|
+
category: 'authentication',
|
|
620
|
+
status: 401,
|
|
621
|
+
});
|
|
570
622
|
}
|
|
571
623
|
console.log(chalk.green('Logged in.'));
|
|
572
624
|
console.log(user.email || user.name || user.id || 'Authenticated user');
|
package/dist/commands/chat.js
CHANGED
|
@@ -20,6 +20,7 @@ import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session
|
|
|
20
20
|
import { V4Command } from './v4.js';
|
|
21
21
|
import { renderDynamicHelp } from '../utils/command-menu.js';
|
|
22
22
|
import { resolvePromptWorkspace } from '../utils/prompt-workspace-resolver.js';
|
|
23
|
+
import { FileUtils } from '../utils/files.js';
|
|
23
24
|
import { isDirectModeFollowUpQuestion, sanitizeDirectModeOutput, stripHiddenThoughtBlocks } from '../utils/direct-output-policy.js';
|
|
24
25
|
import { createLiveOutcome, evaluateExecutorSuccess, handleRunCompleteEvent, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
|
|
25
26
|
import { looksLikeMarkdownReport, renderMarkdownToTerminal, summarizeMarkdownReport, } from '../utils/terminalMarkdown.js';
|
|
@@ -2028,6 +2029,42 @@ export class ChatCommand {
|
|
|
2028
2029
|
}
|
|
2029
2030
|
}
|
|
2030
2031
|
async runSimplePrompt(prompt) {
|
|
2032
|
+
let directWorkspaceGrounding = '';
|
|
2033
|
+
if (this.directPromptMode && !this.agentMode && !this.operatorMode && this.isRepoGroundedPrompt(prompt)) {
|
|
2034
|
+
const expectedFiles = this.api.extractExpectedWorkspaceFiles(prompt).slice(0, 8);
|
|
2035
|
+
if (expectedFiles.length === 0) {
|
|
2036
|
+
throw new CliCommandError('Grounded --no-agent chat requires an explicit workspace-relative file path. Use agent mode for repository-wide inspection.', {
|
|
2037
|
+
code: 'GROUNDING_FILE_REQUIRED', category: 'usage',
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
const reader = new FileUtils(this.currentProjectPath, this.config.get('project')?.ignorePatterns || []);
|
|
2041
|
+
const excerpts = [];
|
|
2042
|
+
let totalBytes = 0;
|
|
2043
|
+
for (const requestedPath of expectedFiles) {
|
|
2044
|
+
const file = reader.readFile(requestedPath);
|
|
2045
|
+
if (!file) {
|
|
2046
|
+
throw new CliCommandError(`Grounded no-agent chat could not safely read the requested file: ${path.basename(requestedPath)}`, {
|
|
2047
|
+
code: 'GROUNDING_FILE_UNAVAILABLE', category: 'execution',
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
const remaining = 32 * 1024 - totalBytes;
|
|
2051
|
+
if (remaining <= 0)
|
|
2052
|
+
break;
|
|
2053
|
+
const content = file.content.slice(0, Math.min(16 * 1024, remaining));
|
|
2054
|
+
totalBytes += Buffer.byteLength(content, 'utf8');
|
|
2055
|
+
excerpts.push(`FILE ${file.relativePath.replace(/\\/g, '/')} (${file.lines} lines):\n${content}`);
|
|
2056
|
+
}
|
|
2057
|
+
if (expectedFiles.length > 0 && excerpts.length === 0) {
|
|
2058
|
+
throw new CliCommandError('Grounded no-agent chat found no safe readable requested files.', {
|
|
2059
|
+
code: 'GROUNDING_FILE_UNAVAILABLE', category: 'execution',
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
directWorkspaceGrounding = [
|
|
2063
|
+
'Repository grounding is provided below from the local workspace.',
|
|
2064
|
+
'Treat it as the only source of truth. Never invent content that is absent.',
|
|
2065
|
+
excerpts.join('\n\n'),
|
|
2066
|
+
].join('\n\n');
|
|
2067
|
+
}
|
|
2031
2068
|
if (!this.directPromptMode && !this.operatorMode) {
|
|
2032
2069
|
const isWriteFollowUp = isConfirmationFollowUp(prompt) || isWritePermissionGrant(prompt);
|
|
2033
2070
|
if (isWriteFollowUp && (this.lastActionableUserInput || this.getPreviousActionablePrompt())) {
|
|
@@ -2090,7 +2127,10 @@ export class ChatCommand {
|
|
|
2090
2127
|
else {
|
|
2091
2128
|
this.messages.push({
|
|
2092
2129
|
role: 'system',
|
|
2093
|
-
content:
|
|
2130
|
+
content: [
|
|
2131
|
+
'Answer the user\'s question directly and concisely. Do not describe tools, platform constraints, or capabilities unless explicitly asked. If the user\'s instruction is to produce specific output, produce exactly that output with no preamble.',
|
|
2132
|
+
directWorkspaceGrounding,
|
|
2133
|
+
].filter(Boolean).join('\n\n'),
|
|
2094
2134
|
});
|
|
2095
2135
|
}
|
|
2096
2136
|
}
|
package/dist/commands/config.js
CHANGED
|
@@ -18,7 +18,7 @@ export class ConfigCommand {
|
|
|
18
18
|
}
|
|
19
19
|
async run(options) {
|
|
20
20
|
if (options.reset) {
|
|
21
|
-
await this.resetConfig();
|
|
21
|
+
await this.resetConfig(Boolean(options.yes));
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
if (options.set) {
|
|
@@ -151,17 +151,36 @@ export class ConfigCommand {
|
|
|
151
151
|
setConfig(keyValue) {
|
|
152
152
|
const [key, ...valueParts] = keyValue.split('=');
|
|
153
153
|
const value = valueParts.join('=');
|
|
154
|
-
if (!key || value
|
|
154
|
+
if (!keyValue.includes('=') || !key || !value) {
|
|
155
155
|
throw new CliCommandError('Invalid format. Use: vigthoria config --set key=value', {
|
|
156
156
|
code: 'INVALID_CONFIG_ASSIGNMENT', category: 'usage',
|
|
157
157
|
});
|
|
158
158
|
}
|
|
159
|
+
const parseBoolean = (v) => {
|
|
160
|
+
if (v !== 'true' && v !== 'false')
|
|
161
|
+
throw new Error('Boolean configuration values must be true or false.');
|
|
162
|
+
return v === 'true';
|
|
163
|
+
};
|
|
159
164
|
const configMap = {
|
|
160
|
-
'model': (v) =>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
'model': (v) => {
|
|
166
|
+
const allowed = new Set(['architect', 'code', 'assistant', 'code-35b', 'code-9b', 'balanced', 'balanced-4b']);
|
|
167
|
+
if (!allowed.has(v))
|
|
168
|
+
throw new Error(`Invalid model: ${v}`);
|
|
169
|
+
this.config.set('preferences', { ...this.config.get('preferences'), defaultModel: v });
|
|
170
|
+
},
|
|
171
|
+
'theme': (v) => {
|
|
172
|
+
if (v !== 'dark' && v !== 'light')
|
|
173
|
+
throw new Error('Theme must be dark or light.');
|
|
174
|
+
this.config.set('preferences', { ...this.config.get('preferences'), theme: v });
|
|
175
|
+
},
|
|
176
|
+
'autoApply': (v) => this.config.set('preferences', { ...this.config.get('preferences'), autoApplyFixes: parseBoolean(v) }),
|
|
177
|
+
'showDiffs': (v) => this.config.set('preferences', { ...this.config.get('preferences'), showDiffs: parseBoolean(v) }),
|
|
178
|
+
'maxTokens': (v) => {
|
|
179
|
+
const parsed = Number(v);
|
|
180
|
+
if (!Number.isSafeInteger(parsed) || parsed < 256 || parsed > 1_000_000)
|
|
181
|
+
throw new Error('maxTokens must be an integer between 256 and 1000000.');
|
|
182
|
+
this.config.set('preferences', { ...this.config.get('preferences'), maxTokens: parsed });
|
|
183
|
+
},
|
|
165
184
|
'apiUrl': (v) => this.config.set('apiUrl', assertTrustedEndpoint(v, { audience: 'coder' }).toString().replace(/\/$/, '')),
|
|
166
185
|
'modelsApiUrl': (v) => this.config.set('modelsApiUrl', assertTrustedEndpoint(v, { audience: 'models' }).toString().replace(/\/$/, '')),
|
|
167
186
|
'wsUrl': (v) => this.config.set('wsUrl', assertTrustedWebSocketEndpoint(v, 'coder').toString().replace(/\/$/, '')),
|
|
@@ -257,8 +276,13 @@ export class ConfigCommand {
|
|
|
257
276
|
console.log(chalk.gray(`Config file: ${this.config.getConfigPath()}`));
|
|
258
277
|
console.log();
|
|
259
278
|
}
|
|
260
|
-
async resetConfig() {
|
|
261
|
-
|
|
279
|
+
async resetConfig(confirmedByFlag = false) {
|
|
280
|
+
if (!confirmedByFlag && !process.stdin.isTTY) {
|
|
281
|
+
throw new CliCommandError('Configuration reset requires --yes in non-interactive mode.', {
|
|
282
|
+
code: 'CONFIRMATION_REQUIRED', category: 'usage',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const { confirm } = confirmedByFlag ? { confirm: true } : await inquirer.prompt([
|
|
262
286
|
{
|
|
263
287
|
type: 'confirm',
|
|
264
288
|
name: 'confirm',
|
package/dist/commands/game.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ type CommonOptions = {
|
|
|
4
4
|
project?: string;
|
|
5
5
|
packageManager?: Manager;
|
|
6
6
|
};
|
|
7
|
+
export declare function gameProcessInvocation(pm: Manager, args: readonly string[], platform?: NodeJS.Platform, comSpec?: string): {
|
|
8
|
+
executable: string;
|
|
9
|
+
args: string[];
|
|
10
|
+
};
|
|
7
11
|
export declare class GameCommand {
|
|
8
12
|
private logger;
|
|
9
13
|
constructor(logger: Logger);
|
package/dist/commands/game.js
CHANGED
|
@@ -24,7 +24,25 @@ const manager = (root, requested) => {
|
|
|
24
24
|
return 'bun';
|
|
25
25
|
return 'npm';
|
|
26
26
|
};
|
|
27
|
-
|
|
27
|
+
export function gameProcessInvocation(pm, args, platform = process.platform, comSpec = path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'cmd.exe')) {
|
|
28
|
+
const executable = platform === 'win32' && pm !== 'bun' ? `${pm}.cmd` : pm;
|
|
29
|
+
assertProcessNetworkAllowed(executable, args);
|
|
30
|
+
if (platform !== 'win32' || !executable.endsWith('.cmd'))
|
|
31
|
+
return { executable, args: [...args] };
|
|
32
|
+
// Node does not directly execute CMD wrappers reliably on current Windows
|
|
33
|
+
// releases. This adapter is deliberately narrower than a general shell:
|
|
34
|
+
// the package manager is an enum and every token must be inert cmd.exe data.
|
|
35
|
+
const safeToken = /^[A-Za-z0-9@._:/\\=-]+$/;
|
|
36
|
+
if (!/^(?:install|run)$/.test(String(args[0] || '')) || args.some((arg) => !safeToken.test(arg))) {
|
|
37
|
+
throw new CliCommandError('Unsafe Windows package-manager argument rejected.', {
|
|
38
|
+
code: 'GAME_PROCESS_ARGUMENT_REJECTED',
|
|
39
|
+
category: 'usage',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const command = [executable, ...args].map((token) => `"${token}"`).join(' ');
|
|
43
|
+
return { executable: comSpec, args: ['/d', '/s', '/c', command] };
|
|
44
|
+
}
|
|
45
|
+
const run = (pm, args, cwd, timeoutMs = 10 * 60_000) => new Promise((resolve, reject) => { const invocation = gameProcessInvocation(pm, args); const child = spawn(invocation.executable, invocation.args, { cwd, stdio: 'inherit', windowsHide: true, env: safeChildProcessEnv() }); let interrupted = false; const onInterrupt = () => { interrupted = true; child.kill('SIGINT'); }; process.once('SIGINT', onInterrupt); const timer = timeoutMs > 0 ? setTimeout(() => { child.kill('SIGTERM'); reject(new CliCommandError(`${pm} command timed out after ${timeoutMs}ms`, { code: 'GAME_PROCESS_TIMEOUT' })); }, timeoutMs) : null; const finish = () => { if (timer)
|
|
28
46
|
clearTimeout(timer); process.removeListener('SIGINT', onInterrupt); }; child.once('error', error => { finish(); reject(error); }); child.once('exit', (code, signal) => { finish(); if (interrupted)
|
|
29
47
|
reject(new CliCommandError(`${pm} command cancelled by user`, { code: 'GAME_PROCESS_CANCELLED', category: 'cancelled' }));
|
|
30
48
|
else
|
package/dist/commands/hub.d.ts
CHANGED
package/dist/commands/hub.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { guardedFetch } from '../utils/network-policy.js';
|
|
9
9
|
import { CliCommandError, commandFailure } from '../utils/command-contract.js';
|
|
10
|
-
const API_BASE = 'https://
|
|
10
|
+
const API_BASE = 'https://hub.vigthoria.io';
|
|
11
11
|
export class HubCommand {
|
|
12
12
|
config;
|
|
13
13
|
constructor(config, _logger) {
|
|
@@ -26,23 +26,47 @@ export class HubCommand {
|
|
|
26
26
|
}
|
|
27
27
|
return token;
|
|
28
28
|
}
|
|
29
|
+
normalizeModule(raw) {
|
|
30
|
+
const unit = String(raw.pricing?.unit || raw.unit || 'request');
|
|
31
|
+
const rawCost = raw.pricing?.cost ?? raw.creditsPerUnit ?? 0;
|
|
32
|
+
const cost = typeof rawCost === 'number' ? rawCost : Number.NaN;
|
|
33
|
+
const displayCost = typeof rawCost === 'string' ? rawCost : `${rawCost} credits per ${unit.toLowerCase()}`;
|
|
34
|
+
return {
|
|
35
|
+
id: String(raw.id || ''),
|
|
36
|
+
name: String(raw.name || raw.id || 'Unknown module'),
|
|
37
|
+
description: String(raw.description || ''),
|
|
38
|
+
endpoint: String(raw.endpoint || ''),
|
|
39
|
+
documentation: String(raw.documentation || 'https://hub.vigthoria.io/subscriptions'),
|
|
40
|
+
pricing: { unit, cost: Number.isFinite(cost) ? cost : 0, example: displayCost },
|
|
41
|
+
status: String(raw.status || 'available'),
|
|
42
|
+
category: String(raw.category || 'api'),
|
|
43
|
+
tags: Array.isArray(raw.tags) ? raw.tags.map(String) : [],
|
|
44
|
+
unit,
|
|
45
|
+
creditsPerUnit: raw.creditsPerUnit,
|
|
46
|
+
euroPerUnit: raw.euroPerUnit,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async moduleCatalog() {
|
|
50
|
+
const response = await guardedFetch(`${API_BASE}/api/modules`, {}, { audience: 'hub' });
|
|
51
|
+
if (!response.ok)
|
|
52
|
+
throw new CliCommandError(`Failed to load module catalog: ${response.statusText}`, { code: 'HUB_CATALOG_FAILED', status: response.status });
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
if (!Array.isArray(data.modules))
|
|
55
|
+
throw new CliCommandError('Module Hub returned a malformed catalog.', { code: 'HUB_CATALOG_INVALID' });
|
|
56
|
+
return data.modules.map((module) => this.normalizeModule(module)).filter((module) => module.id);
|
|
57
|
+
}
|
|
29
58
|
/**
|
|
30
59
|
* Search for modules by natural language query
|
|
31
60
|
*/
|
|
32
61
|
async search(query) {
|
|
33
62
|
console.log(chalk.cyan('\nš Searching Vigthoria Module Hub...\n'));
|
|
34
63
|
try {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}, { audience: 'hub' });
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
throw new Error(`Search failed: ${response.statusText}`);
|
|
44
|
-
}
|
|
45
|
-
const data = await response.json();
|
|
64
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
65
|
+
const results = (await this.moduleCatalog()).map((module) => ({
|
|
66
|
+
...module,
|
|
67
|
+
score: terms.reduce((score, term) => score + (`${module.id} ${module.name} ${module.description}`.toLowerCase().includes(term) ? 1 : 0), 0),
|
|
68
|
+
})).filter((module) => module.score > 0).sort((a, b) => b.score - a.score);
|
|
69
|
+
const data = { results };
|
|
46
70
|
if (data.results.length === 0) {
|
|
47
71
|
console.log(chalk.yellow('No modules found matching your query.'));
|
|
48
72
|
console.log(chalk.gray('Try: vigthoria hub list to see all available modules\n'));
|
|
@@ -50,7 +74,7 @@ export class HubCommand {
|
|
|
50
74
|
}
|
|
51
75
|
console.log(chalk.green(`Found ${data.results.length} module(s):\n`));
|
|
52
76
|
data.results.forEach((module, index) => {
|
|
53
|
-
const statusColor = module.status === '
|
|
77
|
+
const statusColor = module.status === 'available' ? chalk.green : chalk.gray;
|
|
54
78
|
console.log(chalk.bold.white(` ${index + 1}. ${module.name}`));
|
|
55
79
|
console.log(chalk.gray(` ${module.description.substring(0, 80)}...`));
|
|
56
80
|
console.log(chalk.cyan(` š° ${module.pricing.example}`));
|
|
@@ -82,7 +106,8 @@ export class HubCommand {
|
|
|
82
106
|
if (!response.ok) {
|
|
83
107
|
throw new CliCommandError(`Failed to list modules: ${response.statusText}`, { code: 'HUB_LIST_FAILED', status: response.status });
|
|
84
108
|
}
|
|
85
|
-
const
|
|
109
|
+
const rawData = await response.json();
|
|
110
|
+
const data = { modules: Array.isArray(rawData.modules) ? rawData.modules.map((module) => this.normalizeModule(module)) : [] };
|
|
86
111
|
console.log(chalk.bold.white('Available Modules:\n'));
|
|
87
112
|
// Group by category
|
|
88
113
|
const grouped = data.modules.reduce((acc, module) => {
|
|
@@ -116,36 +141,15 @@ export class HubCommand {
|
|
|
116
141
|
* Activate a module for the current user
|
|
117
142
|
*/
|
|
118
143
|
async activate(moduleId) {
|
|
119
|
-
|
|
144
|
+
this.getAuthToken();
|
|
120
145
|
console.log(chalk.cyan(`\nš Activating module: ${moduleId}...\n`));
|
|
121
146
|
try {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}, { audience: 'hub' });
|
|
129
|
-
if (!response.ok) {
|
|
130
|
-
const errorData = await response.json();
|
|
131
|
-
throw new Error(errorData.error || response.statusText);
|
|
132
|
-
}
|
|
133
|
-
const data = await response.json();
|
|
134
|
-
console.log(chalk.green(`ā
${data.message}`));
|
|
135
|
-
console.log();
|
|
136
|
-
console.log(chalk.white('š Documentation:'));
|
|
137
|
-
console.log(chalk.gray(` ${data.documentation}`));
|
|
138
|
-
console.log();
|
|
139
|
-
console.log(chalk.white('š Available Endpoints:'));
|
|
140
|
-
data.endpoints.forEach((ep) => {
|
|
141
|
-
console.log(chalk.gray(` ${ep.method.padEnd(6)} ${ep.path}`));
|
|
142
|
-
console.log(chalk.gray(` ${ep.description}`));
|
|
143
|
-
});
|
|
144
|
-
console.log();
|
|
145
|
-
console.log(chalk.cyan('š” Quick Start:'));
|
|
146
|
-
console.log(chalk.gray(` Use your API key in requests:`));
|
|
147
|
-
console.log(chalk.gray(` Authorization: Bearer ${authToken.substring(0, 8)}...`));
|
|
148
|
-
console.log();
|
|
147
|
+
const module = (await this.moduleCatalog()).find((entry) => entry.id === moduleId);
|
|
148
|
+
if (!module)
|
|
149
|
+
throw new CliCommandError(`Unknown Hub module: ${moduleId}`, { code: 'HUB_MODULE_NOT_FOUND', category: 'usage' });
|
|
150
|
+
console.log(chalk.green(`ā
${module.name} is published in the Vigthoria module catalog.`));
|
|
151
|
+
console.log(chalk.gray('Hub modules do not use a separate activation mutation; the destination service evaluates account authorization and billing when used.'));
|
|
152
|
+
console.log(chalk.gray(`Documentation: ${module.documentation}`));
|
|
149
153
|
}
|
|
150
154
|
catch (error) {
|
|
151
155
|
throw commandFailure(error, { code: 'HUB_ACTIVATION_FAILED' });
|
|
@@ -155,32 +159,18 @@ export class HubCommand {
|
|
|
155
159
|
* Show currently active modules for user
|
|
156
160
|
*/
|
|
157
161
|
async active() {
|
|
158
|
-
|
|
159
|
-
console.log(chalk.cyan('\nš¦
|
|
162
|
+
this.getAuthToken();
|
|
163
|
+
console.log(chalk.cyan('\nš¦ Published Vigthoria Modules\n'));
|
|
160
164
|
try {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}, { audience: 'hub' });
|
|
166
|
-
if (!response.ok) {
|
|
167
|
-
throw new Error(`Failed to fetch: ${response.statusText}`);
|
|
168
|
-
}
|
|
169
|
-
const data = await response.json();
|
|
170
|
-
if (data.allAccess) {
|
|
171
|
-
console.log(chalk.green('š You have access to ALL modules!\n'));
|
|
172
|
-
}
|
|
173
|
-
console.log(chalk.white(`Tier: ${chalk.cyan.bold(data.tier.toUpperCase())}`));
|
|
174
|
-
console.log();
|
|
175
|
-
if (data.activeModules.length === 0) {
|
|
176
|
-
console.log(chalk.yellow('No modules activated yet.'));
|
|
177
|
-
console.log(chalk.gray('\nActivate modules with: vigthoria hub activate <module>'));
|
|
178
|
-
}
|
|
165
|
+
const activeModules = await this.moduleCatalog();
|
|
166
|
+
console.log(chalk.white('Available modules (final authorization occurs at execution time):'));
|
|
167
|
+
if (activeModules.length === 0)
|
|
168
|
+
console.log(chalk.yellow('No modules are currently published.'));
|
|
179
169
|
else {
|
|
180
|
-
|
|
181
|
-
data.activeModules.forEach((module) => {
|
|
170
|
+
activeModules.forEach((module) => {
|
|
182
171
|
console.log(chalk.green(` ā
${module.name}`));
|
|
183
|
-
|
|
172
|
+
if (module.endpoint)
|
|
173
|
+
console.log(chalk.gray(` Endpoint: ${module.endpoint}`));
|
|
184
174
|
console.log(chalk.yellow(` Cost: ${module.pricing.example}`));
|
|
185
175
|
console.log();
|
|
186
176
|
});
|
|
@@ -196,11 +186,9 @@ export class HubCommand {
|
|
|
196
186
|
async info(moduleId) {
|
|
197
187
|
console.log(chalk.cyan(`\nš Module Info: ${moduleId}\n`));
|
|
198
188
|
try {
|
|
199
|
-
const
|
|
200
|
-
if (!
|
|
201
|
-
throw new
|
|
202
|
-
}
|
|
203
|
-
const module = await response.json();
|
|
189
|
+
const module = (await this.moduleCatalog()).find((entry) => entry.id === moduleId);
|
|
190
|
+
if (!module)
|
|
191
|
+
throw new CliCommandError(`Unknown Hub module: ${moduleId}`, { code: 'HUB_MODULE_NOT_FOUND', category: 'usage' });
|
|
204
192
|
console.log(chalk.bold.white(`${module.name}`));
|
|
205
193
|
console.log(chalk.gray('ā'.repeat(50)));
|
|
206
194
|
console.log();
|
|
@@ -247,7 +235,7 @@ export class HubCommand {
|
|
|
247
235
|
console.log(chalk.gray(`External URL: ${module.external}`));
|
|
248
236
|
}
|
|
249
237
|
console.log();
|
|
250
|
-
console.log(chalk.cyan('
|
|
238
|
+
console.log(chalk.cyan('Authorization: ') + chalk.white('evaluated by the destination service when the module is used'));
|
|
251
239
|
console.log();
|
|
252
240
|
}
|
|
253
241
|
catch (error) {
|
|
@@ -269,12 +257,7 @@ export class HubCommand {
|
|
|
269
257
|
console.log(chalk.gray(' ⢠"image generation for social media"\n'));
|
|
270
258
|
console.log(chalk.cyan('Use: vigthoria hub search "<your use case>"'));
|
|
271
259
|
console.log(chalk.gray('The AI will find the best modules for your needs.\n'));
|
|
272
|
-
|
|
273
|
-
console.log(chalk.
|
|
274
|
-
console.log(chalk.gray(' Starter ā¬49/mo 50 credits'));
|
|
275
|
-
console.log(chalk.gray(' Business ā¬199/mo 500 credits'));
|
|
276
|
-
console.log(chalk.gray(' Enterprise ā¬499/mo 2000 credits'));
|
|
277
|
-
console.log();
|
|
278
|
-
console.log(chalk.cyan('Get started: https://landing.vigthoria.io/api-keys.html\n'));
|
|
260
|
+
console.log(chalk.gray('Current unit pricing is loaded from the live module catalog; account authorization is evaluated at execution time.'));
|
|
261
|
+
console.log(chalk.cyan('Manage access: https://hub.vigthoria.io/subscriptions\n'));
|
|
279
262
|
}
|
|
280
263
|
}
|
|
@@ -19,6 +19,7 @@ export type LegionOptions = {
|
|
|
19
19
|
password?: string;
|
|
20
20
|
[key: string]: any;
|
|
21
21
|
};
|
|
22
|
+
export declare function hasMasterAdminCortexAccess(plan: string, masterAccess: boolean, isMasterAdmin: boolean): boolean;
|
|
22
23
|
export declare class LegionCommand {
|
|
23
24
|
private config;
|
|
24
25
|
private logger;
|
package/dist/commands/legion.js
CHANGED
|
@@ -33,6 +33,12 @@ function buildServerHyperloopUrls() {
|
|
|
33
33
|
`http://${internalHost}:${port}${apiPath}`,
|
|
34
34
|
];
|
|
35
35
|
}
|
|
36
|
+
export function hasMasterAdminCortexAccess(plan, masterAccess, isMasterAdmin) {
|
|
37
|
+
const normalizedPlan = String(plan || '').trim().toLowerCase().replace(/-/g, '_');
|
|
38
|
+
if (isMasterAdmin || normalizedPlan === 'master_admin' || normalizedPlan === 'master_admin_plan')
|
|
39
|
+
return true;
|
|
40
|
+
return masterAccess && ['admin', 'enterprise', 'enterprise_ai', 'supreme_ai'].includes(normalizedPlan);
|
|
41
|
+
}
|
|
36
42
|
const CORTEX_WARN_BUDGET_USD = 3.5;
|
|
37
43
|
const CORTEX_HARD_BUDGET_USD = 5.0;
|
|
38
44
|
const CORTEX_MAX_ROUNDS = 2;
|
|
@@ -759,13 +765,7 @@ export class LegionCommand {
|
|
|
759
765
|
};
|
|
760
766
|
}
|
|
761
767
|
isMasterAdminFree(plan, masterAccess, isMasterAdmin) {
|
|
762
|
-
|
|
763
|
-
return false;
|
|
764
|
-
}
|
|
765
|
-
if (isMasterAdmin) {
|
|
766
|
-
return true;
|
|
767
|
-
}
|
|
768
|
-
return plan === 'master_admin';
|
|
768
|
+
return hasMasterAdminCortexAccess(plan, masterAccess, isMasterAdmin);
|
|
769
769
|
}
|
|
770
770
|
isCortexTestMode() {
|
|
771
771
|
return process.env.VIGTHORIA_CORTEX_TEST_MODE === '1'
|
|
@@ -33,7 +33,7 @@ export function registerPlatformCommands(program, config, logger) {
|
|
|
33
33
|
.option('--json', 'Emit JSON output', false).action(async (address, options) => new DeviceCommand(config, logger).disconnect(address, options));
|
|
34
34
|
device.action(async () => new DeviceCommand(config, logger).status({}));
|
|
35
35
|
const security = program.command('security').alias('vsec')
|
|
36
|
-
.description('Run security scans, scores, and fix plans
|
|
36
|
+
.description('Run bounded local security scans, scores, and reviewed fix plans');
|
|
37
37
|
security.command('scan').description('Scan project for security issues').option('-d, --dir <path>', 'Directory to scan (default: current directory)')
|
|
38
38
|
.option('--json', 'Emit JSON output', false).action(async (options) => new SecurityCommand(config, logger).scan({ dir: options.dir, json: options.json }));
|
|
39
39
|
security.command('score').description('Calculate project security score').option('-d, --dir <path>', 'Directory to score (default: current directory)')
|