vigthoria-cli 1.13.24 → 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 +14 -5
- 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
|
@@ -5,6 +5,7 @@ import { clearGatewayPreflightCache } from '../utils/cli-state.js';
|
|
|
5
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 {
|
|
@@ -271,6 +272,9 @@ export async function login(email, password) {
|
|
|
271
272
|
identity: { userId: identity?.id, email: identity?.email },
|
|
272
273
|
v3ServiceKey: null,
|
|
273
274
|
});
|
|
275
|
+
const issuedSubscription = normalizeSubscriptionResponse(result);
|
|
276
|
+
if (issuedSubscription)
|
|
277
|
+
new Config().setSubscription(issuedSubscription);
|
|
274
278
|
// Only present the newly issued token to secondary authenticated
|
|
275
279
|
// endpoints after its structure and Coder acceptance are proven.
|
|
276
280
|
const v3ServiceKey = await fetchV3ServiceKey(normalizedBase, token);
|
|
@@ -469,12 +473,14 @@ export async function handleLogin(options = {}) {
|
|
|
469
473
|
});
|
|
470
474
|
if (subResponse.ok) {
|
|
471
475
|
const sub = await subResponse.json();
|
|
476
|
+
const normalizedSubscription = normalizeSubscriptionResponse(sub);
|
|
472
477
|
const subRecord = (sub.subscription && typeof sub.subscription === 'object') ? sub.subscription : {};
|
|
473
478
|
const userRecord = (sub.user && typeof sub.user === 'object') ? sub.user : {};
|
|
474
|
-
const plan = String(sub.plan || subRecord.plan || userRecord.plan || userRecord.subscription_plan || '').trim();
|
|
475
479
|
const role = String(sub.role || subRecord.role || userRecord.role || userRecord.user_role || '').trim();
|
|
476
|
-
if (
|
|
477
|
-
|
|
480
|
+
if (normalizedSubscription)
|
|
481
|
+
new Config().setSubscription(normalizedSubscription);
|
|
482
|
+
if (normalizedSubscription) {
|
|
483
|
+
console.log(`Plan: ${normalizedSubscription.plan.toUpperCase()}`);
|
|
478
484
|
}
|
|
479
485
|
if (role) {
|
|
480
486
|
console.log(`Role: ${role}`);
|
|
@@ -608,8 +614,11 @@ export function registerAuthCommands(program) {
|
|
|
608
614
|
try {
|
|
609
615
|
const user = await whoami();
|
|
610
616
|
if (!user) {
|
|
611
|
-
|
|
612
|
-
|
|
617
|
+
throw new CliCommandError('Not logged in.', {
|
|
618
|
+
code: 'AUTHENTICATION_REQUIRED',
|
|
619
|
+
category: 'authentication',
|
|
620
|
+
status: 401,
|
|
621
|
+
});
|
|
613
622
|
}
|
|
614
623
|
console.log(chalk.green('Logged in.'));
|
|
615
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)')
|
|
@@ -142,11 +142,13 @@ export function registerProductAndRunCommands(program, config, logger) {
|
|
|
142
142
|
});
|
|
143
143
|
repoCommand
|
|
144
144
|
.command('review [reviewId]')
|
|
145
|
-
.description('Show repository security review
|
|
145
|
+
.description('Show or clean repository security review state')
|
|
146
|
+
.option('--cleanup', 'Remove the quarantined payload and review object while retaining a hash-only audit record', false)
|
|
147
|
+
.option('-y, --yes', 'Confirm cleanup without an interactive prompt', false)
|
|
146
148
|
.option('--json', 'Emit machine-readable JSON output', false)
|
|
147
149
|
.action(async (reviewId, options) => {
|
|
148
150
|
const repo = new RepoCommand(config, logger);
|
|
149
|
-
await repo.review(reviewId, { json: options.json });
|
|
151
|
+
await repo.review(reviewId, { json: options.json, cleanup: options.cleanup, yes: options.yes });
|
|
150
152
|
});
|
|
151
153
|
repoCommand
|
|
152
154
|
.command('pull <name>')
|
package/dist/commands/repo.d.ts
CHANGED
package/dist/commands/repo.js
CHANGED
|
@@ -502,6 +502,43 @@ export class RepoCommand {
|
|
|
502
502
|
*/
|
|
503
503
|
async review(reviewId, options = {}) {
|
|
504
504
|
this.requireAuth();
|
|
505
|
+
if (options.cleanup) {
|
|
506
|
+
if (!reviewId) {
|
|
507
|
+
throw new CliCommandError('A review ID is required with --cleanup', {
|
|
508
|
+
code: 'REPOSITORY_REVIEW_ID_REQUIRED', category: 'usage',
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
if (!options.yes) {
|
|
512
|
+
if (options.json || !process.stdin.isTTY) {
|
|
513
|
+
throw new CliCommandError('Repository review cleanup requires --yes in JSON or non-interactive mode', {
|
|
514
|
+
code: 'CONFIRMATION_REQUIRED', category: 'usage',
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
const answer = await inquirer.prompt([{
|
|
518
|
+
type: 'confirm',
|
|
519
|
+
name: 'confirmed',
|
|
520
|
+
message: `Permanently remove quarantined payload for review ${reviewId}?`,
|
|
521
|
+
default: false,
|
|
522
|
+
}]);
|
|
523
|
+
if (!answer.confirmed) {
|
|
524
|
+
throw new CliCommandError('Repository security review cleanup cancelled', {
|
|
525
|
+
code: 'REPOSITORY_REVIEW_CLEANUP_CANCELLED', category: 'cancelled',
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const response = await this.repoFetch(`/api/repo/security-reviews/${encodeURIComponent(reviewId)}`, { method: 'DELETE' });
|
|
530
|
+
const payload = await readResponsePayload(response);
|
|
531
|
+
if (!response.ok) {
|
|
532
|
+
throw new RepositoryApiError(response.status, payload, 'Failed to clean repository security review');
|
|
533
|
+
}
|
|
534
|
+
if (options.json) {
|
|
535
|
+
console.log(formatSuccessJson('repo review', payload));
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
console.log(chalk.green(`\n✓ Removed quarantined repository review ${reviewId}; a hash-only audit record was retained.\n`));
|
|
539
|
+
}
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
505
542
|
const spinner = createSpinner(reviewId ? 'Loading security review...' : 'Loading security reviews...').start();
|
|
506
543
|
try {
|
|
507
544
|
const apiPath = reviewId
|
|
@@ -8,10 +8,13 @@ interface SecurityOptions {
|
|
|
8
8
|
}
|
|
9
9
|
export declare class SecurityCommand {
|
|
10
10
|
private logger;
|
|
11
|
+
private client;
|
|
11
12
|
constructor(_config: Config, logger: Logger);
|
|
12
13
|
private getMcpBaseUrl;
|
|
13
14
|
private resolveDir;
|
|
14
15
|
private execute;
|
|
16
|
+
private useRemoteMcp;
|
|
17
|
+
private runScan;
|
|
15
18
|
scan(options: SecurityOptions): Promise<void>;
|
|
16
19
|
score(options: SecurityOptions): Promise<void>;
|
|
17
20
|
fix(options: SecurityOptions): Promise<void>;
|