vigthoria-cli 1.10.1 → 1.10.37

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.
@@ -5,6 +5,7 @@ import chalk from 'chalk';
5
5
  import inquirer from 'inquirer';
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
+ import { normalizePersonaMode } from '../utils/persona.js';
8
9
  import { CH } from '../utils/logger.js';
9
10
  export class ConfigCommand {
10
11
  config;
@@ -164,6 +165,12 @@ export class ConfigCommand {
164
165
  'modelsApiUrl': (v) => this.config.set('modelsApiUrl', v),
165
166
  'wsUrl': (v) => this.config.set('wsUrl', v),
166
167
  'selfHostedModelsApiUrl': (v) => this.config.set('selfHostedModelsApiUrl', v === 'null' || v === 'off' ? null : v),
168
+ 'persona': (v) => {
169
+ const mode = normalizePersonaMode(v);
170
+ if (!mode)
171
+ throw new Error('Invalid persona. Use: default or wiener_grant');
172
+ this.config.set('persona', mode);
173
+ },
167
174
  };
168
175
  if (configMap[key]) {
169
176
  configMap[key](value);
@@ -171,7 +178,7 @@ export class ConfigCommand {
171
178
  }
172
179
  else {
173
180
  this.logger.error(`Unknown config key: ${key}`);
174
- console.log(chalk.gray('Available keys: model, theme, autoApply, showDiffs, maxTokens, apiUrl, modelsApiUrl, wsUrl, selfHostedModelsApiUrl'));
181
+ console.log(chalk.gray('Available keys: model, theme, autoApply, showDiffs, maxTokens, persona, apiUrl, modelsApiUrl, wsUrl, selfHostedModelsApiUrl'));
175
182
  }
176
183
  }
177
184
  formatConfigValueForDisplay(key, value) {
@@ -214,6 +221,7 @@ export class ConfigCommand {
214
221
  maxTokens: all.preferences.maxTokens,
215
222
  email: all.email,
216
223
  plan: all.subscription.plan,
224
+ persona: all.persona,
217
225
  };
218
226
  if (key in flatConfig) {
219
227
  console.log(flatConfig[key]);
@@ -231,7 +239,7 @@ export class ConfigCommand {
231
239
  console.log(chalk.gray(' URL: ') + chalk.cyan(this.redactConfigUrl(all.apiUrl)));
232
240
  console.log(chalk.gray(' Models API: ') + chalk.cyan(this.redactConfigUrl(all.modelsApiUrl)));
233
241
  console.log(chalk.gray(' WebSocket: ') + chalk.cyan(this.redactConfigUrl(all.wsUrl)));
234
- console.log(chalk.gray(' Self-hosted Models: ') + chalk.cyan(all.selfHostedModelsApiUrl || 'disabled'));
242
+ console.log(chalk.gray(' Vigthoria Model Endpoint: ') + chalk.cyan(all.selfHostedModelsApiUrl || 'default'));
235
243
  console.log();
236
244
  console.log(chalk.white('Preferences:'));
237
245
  console.log(chalk.gray(' Default Model: ') + chalk.cyan(all.preferences.defaultModel));
@@ -239,6 +247,7 @@ export class ConfigCommand {
239
247
  console.log(chalk.gray(' Auto Apply Fixes: ') + chalk.cyan(all.preferences.autoApplyFixes));
240
248
  console.log(chalk.gray(' Show Diffs: ') + chalk.cyan(all.preferences.showDiffs));
241
249
  console.log(chalk.gray(' Max Tokens: ') + chalk.cyan(all.preferences.maxTokens));
250
+ console.log(chalk.gray(' Persona: ') + chalk.cyan(all.persona));
242
251
  console.log();
243
252
  console.log(chalk.white('Project:'));
244
253
  console.log(chalk.gray(' Ignore Patterns: ') + chalk.gray(all.project.ignorePatterns.join(', ')));
@@ -68,7 +68,6 @@ export class LegionCommand {
68
68
  const serviceKey = this.getLegionServiceKey();
69
69
  if (serviceKey) {
70
70
  headers['X-Service-Key'] = serviceKey;
71
- return headers;
72
71
  }
73
72
  const token = this.config.get('authToken');
74
73
  if (token) {
@@ -861,6 +860,7 @@ export class LegionCommand {
861
860
  }
862
861
  const endpoints = [
863
862
  '/api/viagen6/vigcoin/balance',
863
+ '/api/user/vigcoin/balance',
864
864
  '/api/user/subscription',
865
865
  '/api/user/info',
866
866
  '/api/user/profile',
@@ -868,6 +868,7 @@ export class LegionCommand {
868
868
  '/api/billing/wallet',
869
869
  '/api/billing/credits',
870
870
  ];
871
+ let sawUnauthorized = false;
871
872
  for (const endpoint of endpoints) {
872
873
  try {
873
874
  const response = await fetch(`${baseUrl}${endpoint}`, {
@@ -876,6 +877,9 @@ export class LegionCommand {
876
877
  headers,
877
878
  });
878
879
  if (!response.ok) {
880
+ if (response.status === 401 || response.status === 403) {
881
+ sawUnauthorized = true;
882
+ }
879
883
  continue;
880
884
  }
881
885
  const payload = await this.readJsonResponse(response, `wallet balance request ${endpoint}`);
@@ -901,7 +905,9 @@ export class LegionCommand {
901
905
  vigcoinBalance: null,
902
906
  source: null,
903
907
  purchaseUrl: `${baseUrl}/music/store#vigcoins`,
904
- error: 'Wallet endpoint unavailable from current gateway session',
908
+ error: sawUnauthorized
909
+ ? 'Wallet session expired or unauthorized. Run: vigthoria login'
910
+ : 'Wallet endpoint unavailable from current gateway session',
905
911
  };
906
912
  }
907
913
  async attemptDirectCharge(vigcoinNeeded) {
@@ -0,0 +1,25 @@
1
+ /**
2
+ * wallet.ts — VigCoin wallet management for Vigthoria CLI.
3
+ *
4
+ * vigthoria wallet balance — show current balance
5
+ * vigthoria wallet history [--n 20] — recent transactions
6
+ * vigthoria wallet cloud-status — show cloud access and model pricing
7
+ */
8
+ export interface WalletOptions {
9
+ n?: number;
10
+ json?: boolean;
11
+ }
12
+ export declare class WalletCommand {
13
+ private config;
14
+ private logger;
15
+ constructor();
16
+ private getBaseUrl;
17
+ private getToken;
18
+ private getRefreshToken;
19
+ private refreshAccessToken;
20
+ private parseResponseBody;
21
+ private apiFetch;
22
+ showBalance(opts?: WalletOptions): Promise<void>;
23
+ showHistory(opts?: WalletOptions): Promise<void>;
24
+ showCloudStatus(opts?: WalletOptions): Promise<void>;
25
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * wallet.ts — VigCoin wallet management for Vigthoria CLI.
3
+ *
4
+ * vigthoria wallet balance — show current balance
5
+ * vigthoria wallet history [--n 20] — recent transactions
6
+ * vigthoria wallet cloud-status — show cloud access and model pricing
7
+ */
8
+ import chalk from 'chalk';
9
+ import { Config } from '../utils/config.js';
10
+ import { Logger } from '../utils/logger.js';
11
+ export class WalletCommand {
12
+ config;
13
+ logger;
14
+ constructor() {
15
+ this.config = new Config();
16
+ this.logger = new Logger();
17
+ }
18
+ getBaseUrl() {
19
+ return String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
20
+ }
21
+ getToken() {
22
+ return this.config.get('authToken') || null;
23
+ }
24
+ getRefreshToken() {
25
+ return this.config.get('refreshToken') || null;
26
+ }
27
+ async refreshAccessToken() {
28
+ const refreshToken = this.getRefreshToken();
29
+ if (!refreshToken)
30
+ return false;
31
+ try {
32
+ const response = await fetch(`${this.getBaseUrl()}/api/token/refresh`, {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({ refresh_token: refreshToken }),
36
+ });
37
+ if (!response.ok)
38
+ return false;
39
+ const payload = await response.json();
40
+ const nextToken = payload.token || payload.access_token;
41
+ if (!nextToken)
42
+ return false;
43
+ this.config.set('authToken', nextToken);
44
+ if (payload.refresh_token) {
45
+ this.config.set('refreshToken', payload.refresh_token);
46
+ }
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ async parseResponseBody(resp) {
54
+ const raw = await resp.text();
55
+ if (!raw.trim())
56
+ return {};
57
+ try {
58
+ return JSON.parse(raw);
59
+ }
60
+ catch {
61
+ return { error: raw.slice(0, 240) };
62
+ }
63
+ }
64
+ async apiFetch(path) {
65
+ const url = `${this.getBaseUrl()}${path}`;
66
+ const runRequest = async (token) => fetch(url, {
67
+ headers: {
68
+ Authorization: `Bearer ${token}`,
69
+ Cookie: `vigthoria-auth-token=${token}`,
70
+ },
71
+ });
72
+ let token = this.getToken();
73
+ if (!token)
74
+ throw new Error('Not authenticated. Run: vigthoria login');
75
+ let resp = await runRequest(token);
76
+ if (resp.status === 401 || resp.status === 403) {
77
+ const refreshed = await this.refreshAccessToken();
78
+ if (refreshed) {
79
+ token = this.getToken();
80
+ if (token) {
81
+ resp = await runRequest(token);
82
+ }
83
+ }
84
+ }
85
+ const body = await this.parseResponseBody(resp);
86
+ if (!resp.ok) {
87
+ const err = body.error
88
+ || body.message
89
+ || `HTTP ${resp.status}`;
90
+ throw new Error(err);
91
+ }
92
+ return body;
93
+ }
94
+ async showBalance(opts = {}) {
95
+ try {
96
+ const data = await this.apiFetch('/api/user/vigcoin/balance');
97
+ if (opts.json) {
98
+ console.log(JSON.stringify(data, null, 2));
99
+ return;
100
+ }
101
+ console.log('');
102
+ console.log(chalk.bold.cyan(' VigCoin Wallet'));
103
+ console.log(chalk.gray(' ─────────────────────────────'));
104
+ console.log(` Balance: ${chalk.bold.yellow(data.balance.toLocaleString())} ⓥ`);
105
+ console.log(` Lifetime earned: ${chalk.green(data.lifetimeEarned.toLocaleString())} ⓥ`);
106
+ console.log(` Lifetime spent: ${chalk.red(data.lifetimeSpent.toLocaleString())} ⓥ`);
107
+ if (data.lastUpdated) {
108
+ console.log(` Last updated: ${chalk.gray(new Date(data.lastUpdated).toLocaleString())}`);
109
+ }
110
+ console.log('');
111
+ console.log(chalk.gray(' Top up at: https://hub.vigthoria.io/credits'));
112
+ console.log('');
113
+ }
114
+ catch (err) {
115
+ this.logger.error(err.message);
116
+ process.exit(1);
117
+ }
118
+ }
119
+ async showHistory(opts = {}) {
120
+ const limit = Math.max(1, Math.min(100, opts.n || 20));
121
+ try {
122
+ const data = await this.apiFetch(`/api/user/vigcoin/transactions?limit=${limit}`);
123
+ if (opts.json) {
124
+ console.log(JSON.stringify(data, null, 2));
125
+ return;
126
+ }
127
+ const txs = data.transactions || [];
128
+ console.log('');
129
+ console.log(chalk.bold.cyan(` VigCoin Transaction History (last ${txs.length})`));
130
+ console.log(chalk.gray(' ──────────────────────────────────────────────────────────'));
131
+ if (txs.length === 0) {
132
+ console.log(chalk.gray(' No transactions found.'));
133
+ }
134
+ else {
135
+ for (const tx of txs) {
136
+ const sign = tx.amount >= 0 ? chalk.green(`+${tx.amount}`) : chalk.red(String(tx.amount));
137
+ const date = new Date(tx.created_at).toLocaleString();
138
+ const desc = tx.description || tx.action || tx.type;
139
+ console.log(` ${chalk.gray(date)} ${sign.padStart(8)} ⓥ ${chalk.gray(`→`)} ${chalk.yellow(tx.balance_after)} ⓥ ${desc}`);
140
+ }
141
+ }
142
+ console.log('');
143
+ }
144
+ catch (err) {
145
+ this.logger.error(err.message);
146
+ process.exit(1);
147
+ }
148
+ }
149
+ async showCloudStatus(opts = {}) {
150
+ try {
151
+ const data = await this.apiFetch('/api/user/cloud-access/status');
152
+ if (opts.json) {
153
+ console.log(JSON.stringify(data, null, 2));
154
+ return;
155
+ }
156
+ console.log('');
157
+ console.log(chalk.bold.cyan(' Cloud Access Status'));
158
+ console.log(chalk.gray(' ─────────────────────────────'));
159
+ if (data.isMasterAdmin) {
160
+ console.log(` Access: ${chalk.bold.green('Master Admin — unlimited cloud access')}`);
161
+ }
162
+ else if (data.hasGrant) {
163
+ const models = data.grantDetails?.allowedModels === '*' ? 'All models' : data.grantDetails?.allowedModels;
164
+ const exp = data.grantDetails?.expiresAt ? ` (expires ${new Date(data.grantDetails.expiresAt).toLocaleDateString()})` : '';
165
+ console.log(` Access: ${chalk.green(`Granted — ${models}${exp}`)}`);
166
+ }
167
+ else if (data.cloudAccessAllowed) {
168
+ console.log(` Access: ${chalk.yellow('Enabled — pay per request with VigCoins')}`);
169
+ }
170
+ else {
171
+ console.log(` Access: ${chalk.red('Not enabled — contact admin to request cloud access')}`);
172
+ }
173
+ console.log('');
174
+ if (data.cloudModels) {
175
+ console.log(chalk.bold(' Cloud Model Pricing'));
176
+ console.log(chalk.gray(' ─────────────────────────────'));
177
+ for (const [alias, cost] of Object.entries(data.cloudModels)) {
178
+ const displayName = alias.replace('vigthoria-cloud-', '').replace(/\b\w/g, c => c.toUpperCase());
179
+ console.log(` ${chalk.cyan(`vigthoria-cloud-${displayName.toLowerCase()}`).padEnd(36)} ${chalk.yellow(String(cost))} credits minimum reserve, then token-priced`);
180
+ }
181
+ console.log('');
182
+ console.log(chalk.gray(' Use --model vigthoria-cloud-fast|balanced|code|power|maximum in vigthoria chat'));
183
+ }
184
+ console.log('');
185
+ }
186
+ catch (err) {
187
+ this.logger.error(err.message);
188
+ process.exit(1);
189
+ }
190
+ }
191
+ }
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@
15
15
  * vigthoria workflow - Manage repeatable VigFlow workflows
16
16
  * vigthoria operator - Start BMAD operator mode
17
17
  */
18
- import { Command } from 'commander';
18
+ import { Command, Option } from 'commander';
19
19
  import { ChatCommand } from './commands/chat.js';
20
20
  import { EditCommand } from './commands/edit.js';
21
21
  import { GenerateCommand } from './commands/generate.js';
@@ -36,6 +36,7 @@ import { ReplayCommand } from './commands/replay.js';
36
36
  import { ForkCommand } from './commands/fork.js';
37
37
  import { CancelCommand } from './commands/cancel.js';
38
38
  import { SecurityCommand } from './commands/security.js';
39
+ import { WalletCommand } from './commands/wallet.js';
39
40
  import { Config } from './utils/config.js';
40
41
  import { Logger, CH } from './utils/logger.js';
41
42
  import chalk from 'chalk';
@@ -63,6 +64,56 @@ function getInvokedBinaryName() {
63
64
  const executable = process.argv[1] || 'vigthoria';
64
65
  return path.basename(executable, path.extname(executable)).toLowerCase();
65
66
  }
67
+ function detectRuntimeEnvironment(cwd) {
68
+ const osPlatform = process.platform;
69
+ const normalizedPlatform = osPlatform === 'win32'
70
+ ? 'windows'
71
+ : osPlatform === 'darwin'
72
+ ? 'macos'
73
+ : osPlatform === 'linux'
74
+ ? 'linux'
75
+ : 'unknown';
76
+ const release = os.release();
77
+ const isWSL = normalizedPlatform === 'linux' && (/microsoft/i.test(release)
78
+ || Boolean(process.env.WSL_DISTRO_NAME)
79
+ || Boolean(process.env.WSL_INTEROP));
80
+ let isContainer = false;
81
+ try {
82
+ if (fs.existsSync('/.dockerenv')) {
83
+ isContainer = true;
84
+ }
85
+ else if (fs.existsSync('/proc/1/cgroup')) {
86
+ const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8');
87
+ isContainer = /(docker|containerd|kubepods|podman)/i.test(cgroup);
88
+ }
89
+ }
90
+ catch {
91
+ isContainer = false;
92
+ }
93
+ const isCodespaces = String(process.env.CODESPACES || '').toLowerCase() === 'true';
94
+ const isCI = String(process.env.CI || '').toLowerCase() === 'true';
95
+ const isMobileLikeRuntime = Boolean(process.env.TERMUX_VERSION
96
+ || process.env.ANDROID_ROOT
97
+ || process.env.IOS_SIMULATOR_DEVICE_NAME
98
+ || /\/var\/mobile\//.test(cwd));
99
+ const workspaceKind = cwd.startsWith('/var/www/') || cwd.startsWith('/opt/vigthoria')
100
+ ? 'server-workspace'
101
+ : 'local-machine';
102
+ return {
103
+ osPlatform,
104
+ normalizedPlatform,
105
+ arch: process.arch,
106
+ release,
107
+ isWSL,
108
+ isContainer,
109
+ isCodespaces,
110
+ isCI,
111
+ isMobileLikeRuntime,
112
+ workspaceKind,
113
+ cwd,
114
+ shell: process.env.SHELL || process.env.ComSpec || null,
115
+ };
116
+ }
66
117
  // Get version from package.json dynamically
67
118
  function getPackageMetadataPaths() {
68
119
  return [
@@ -149,7 +200,21 @@ function compareVersions(v1, v2) {
149
200
  return 0;
150
201
  }
151
202
  const VERSION = getVersion();
203
+ function hiddenGrantOption() {
204
+ return new Option('--grant', 'Use optional Wiener Grantler persona for this invocation').hideHelp();
205
+ }
206
+ function allowInsecureCliVersionInstall() {
207
+ return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_ALLOW_INSECURE_CLI_VERSION || ''));
208
+ }
209
+ function extractVersionFromUpdateTarget(target) {
210
+ const match = target.match(/(?:@|[-_])([0-9]+\.[0-9]+\.[0-9]+)(?:\.tgz)?(?:$|[^0-9])/);
211
+ return match ? match[1] : null;
212
+ }
213
+ function isBelowSecureCliBaseline(version) {
214
+ return compareVersions(version, VIGTHORIA_MIN_SECURE_CLI_VERSION) < 0;
215
+ }
152
216
  const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://coder.vigthoria.io/releases/manifest.json";
217
+ const VIGTHORIA_MIN_SECURE_CLI_VERSION = '1.10.22';
153
218
  function resolveManifestEntry(manifest, channel) {
154
219
  const normalized = String(channel || 'stable').trim() || 'stable';
155
220
  if (manifest.channels && manifest.channels[normalized]) {
@@ -524,7 +589,8 @@ export async function main(args) {
524
589
  program
525
590
  .name(invokedBinaryName === 'vigthoria-chat' ? 'vigthoria-chat' : 'vigthoria')
526
591
  .description('AI-powered terminal coding assistant for Vigthoria Coder subscribers')
527
- .version(VERSION);
592
+ .version(VERSION)
593
+ .addOption(hiddenGrantOption());
528
594
  // Chat command - Interactive mode (Agent mode is default for best results)
529
595
  program
530
596
  .command('chat')
@@ -541,6 +607,7 @@ export async function main(args) {
541
607
  .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
542
608
  .option('--auto-approve', 'Auto-approve agent actions (dangerous!)', false)
543
609
  .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
610
+ .addOption(hiddenGrantOption())
544
611
  .action(async (options) => {
545
612
  const chat = new ChatCommand(config, logger);
546
613
  await chat.run({
@@ -555,6 +622,7 @@ export async function main(args) {
555
622
  resume: options.resume,
556
623
  prompt: options.prompt,
557
624
  bridge: options.bridge,
625
+ grant: options.grant || program.opts().grant === true,
558
626
  });
559
627
  });
560
628
  program
@@ -570,6 +638,7 @@ export async function main(args) {
570
638
  .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
571
639
  .option('--auto-approve', 'Auto-approve agent actions (dangerous!)', false)
572
640
  .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
641
+ .addOption(hiddenGrantOption())
573
642
  .action(async (options) => {
574
643
  const chat = new ChatCommand(config, logger);
575
644
  await chat.run({
@@ -584,6 +653,7 @@ export async function main(args) {
584
653
  resume: true,
585
654
  prompt: options.prompt,
586
655
  bridge: options.bridge,
656
+ grant: options.grant || program.opts().grant === true,
587
657
  });
588
658
  });
589
659
  // Agent command - Agentic mode (Vigthoria Autonomous)
@@ -600,6 +670,7 @@ export async function main(args) {
600
670
  .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
601
671
  .option('--auto-approve', 'Auto-approve all actions (dangerous!)', false)
602
672
  .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
673
+ .addOption(hiddenGrantOption())
603
674
  .action(async (options) => {
604
675
  const chat = new ChatCommand(config, logger);
605
676
  await chat.run({
@@ -614,6 +685,7 @@ export async function main(args) {
614
685
  autoApprove: options.autoApprove,
615
686
  prompt: options.prompt,
616
687
  bridge: options.bridge,
688
+ grant: options.grant || program.opts().grant === true,
617
689
  });
618
690
  });
619
691
  program
@@ -628,6 +700,7 @@ export async function main(args) {
628
700
  .option('--save-plan', 'Save the completed BMAD plan into VigFlow for rerun and audit', false)
629
701
  .option('--json', 'Emit machine-readable JSON output for direct prompt runs', false)
630
702
  .option('--bridge <url>', 'Connect to Vigthoria Commando Bridge for remote admin observability')
703
+ .addOption(hiddenGrantOption())
631
704
  .action(async (options) => {
632
705
  const chat = new ChatCommand(config, logger);
633
706
  await chat.run({
@@ -642,6 +715,7 @@ export async function main(args) {
642
715
  json: options.json,
643
716
  prompt: options.prompt,
644
717
  bridge: options.bridge,
718
+ grant: options.grant || program.opts().grant === true,
645
719
  });
646
720
  });
647
721
  program
@@ -1295,10 +1369,25 @@ Examples:
1295
1369
  const offline = isOfflineMode();
1296
1370
  const updateSuppressed = isUpdateCheckSuppressed();
1297
1371
  const subscription = config.get('subscription') || { plan: null, status: null, expiresAt: null };
1372
+ const runtime = detectRuntimeEnvironment(process.cwd());
1373
+ const warnings = [];
1374
+ if (runtime.normalizedPlatform === 'unknown') {
1375
+ warnings.push('Unknown OS platform detected. Run `vigthoria doctor --check-api` before agent tasks.');
1376
+ }
1377
+ if (runtime.isMobileLikeRuntime) {
1378
+ warnings.push('Mobile-like runtime detected (Termux/iOS-style path). Vigthoria CLI agent tooling is optimized for desktop/server shells.');
1379
+ }
1380
+ if (runtime.isWSL) {
1381
+ warnings.push('WSL detected. Prefer Linux-style paths and keep projects inside the WSL filesystem for stable tool execution.');
1382
+ }
1383
+ if (/127\.0\.0\.1:1/.test(modelsApiUrl)) {
1384
+ warnings.push('modelsApiUrl is set to 127.0.0.1:1 (unreachable). Set modelsApiUrl to https://api.vigthoria.io or your local model router.');
1385
+ }
1298
1386
  const report = {
1299
1387
  cliVersion: VERSION,
1300
1388
  nodeVersion: process.version,
1301
1389
  platform: `${process.platform} ${process.arch}`,
1390
+ runtimeEnvironment: runtime,
1302
1391
  cwd: process.cwd(),
1303
1392
  homeDir: os.homedir(),
1304
1393
  configPath: config.getConfigPath(),
@@ -1310,6 +1399,7 @@ Examples:
1310
1399
  subscriptionStatus: subscription.status || null,
1311
1400
  offlineMode: offline,
1312
1401
  updateCheckSuppressed: updateSuppressed,
1402
+ warnings,
1313
1403
  envOverrides: {
1314
1404
  VIGTHORIA_API_URL: process.env.VIGTHORIA_API_URL || null,
1315
1405
  VIGTHORIA_V3_AGENT_URL: process.env.VIGTHORIA_V3_AGENT_URL || null,
@@ -1354,6 +1444,12 @@ Examples:
1354
1444
  if (!options.checkApi) {
1355
1445
  console.log(chalk.gray('\nTip: pass --check-api to verify API reachability.'));
1356
1446
  }
1447
+ if (warnings.length > 0) {
1448
+ console.log(chalk.yellow('\nEnvironment warnings:'));
1449
+ for (const warning of warnings) {
1450
+ console.log(chalk.yellow(`- ${warning}`));
1451
+ }
1452
+ }
1357
1453
  }
1358
1454
  process.exitCode = 0;
1359
1455
  });
@@ -1428,6 +1524,21 @@ Examples:
1428
1524
  return;
1429
1525
  }
1430
1526
  try {
1527
+ if (!allowInsecureCliVersionInstall()) {
1528
+ const detectedVersion = extractVersionFromUpdateTarget(updateTarget);
1529
+ if (!detectedVersion) {
1530
+ console.error(chalk.red('Refusing custom update source: unable to verify CLI version in update target.'));
1531
+ console.error(chalk.gray(`Allowed floor: ${VIGTHORIA_MIN_SECURE_CLI_VERSION}. Set VIGTHORIA_ALLOW_INSECURE_CLI_VERSION=1 only for trusted admin recovery.`));
1532
+ process.exitCode = 1;
1533
+ return;
1534
+ }
1535
+ if (isBelowSecureCliBaseline(detectedVersion)) {
1536
+ console.error(chalk.red(`Refusing insecure CLI version ${detectedVersion}.`));
1537
+ console.error(chalk.gray(`Minimum secure CLI version is ${VIGTHORIA_MIN_SECURE_CLI_VERSION}.`));
1538
+ process.exitCode = 1;
1539
+ return;
1540
+ }
1541
+ }
1431
1542
  if (options.check) {
1432
1543
  console.log(chalk.cyan(`Update source configured: ${updateTarget}`));
1433
1544
  console.log(chalk.gray('Run `vigthoria update --from <target>` to install from this source'));
@@ -1465,6 +1576,12 @@ Examples:
1465
1576
  process.exitCode = 1;
1466
1577
  return;
1467
1578
  }
1579
+ if (!allowInsecureCliVersionInstall() && isBelowSecureCliBaseline(entry.version)) {
1580
+ console.error(chalk.red(`Refusing insecure manifest CLI version ${entry.version} for channel ${channel}.`));
1581
+ console.error(chalk.gray(`Minimum secure CLI version is ${VIGTHORIA_MIN_SECURE_CLI_VERSION}.`));
1582
+ process.exitCode = 1;
1583
+ return;
1584
+ }
1468
1585
  const currentVersion = VERSION;
1469
1586
  const comparison = compareVersions(entry.version, currentVersion);
1470
1587
  if (!allowDowngrade && comparison <= 0) {
@@ -1533,6 +1650,12 @@ Examples:
1533
1650
  windowsHide: true,
1534
1651
  }).trim();
1535
1652
  const currentVersion = VERSION;
1653
+ if (!allowInsecureCliVersionInstall() && isBelowSecureCliBaseline(latestVersion)) {
1654
+ console.error(chalk.red(`Refusing insecure npm latest CLI version ${latestVersion}.`));
1655
+ console.error(chalk.gray(`Minimum secure CLI version is ${VIGTHORIA_MIN_SECURE_CLI_VERSION}.`));
1656
+ process.exitCode = 1;
1657
+ return;
1658
+ }
1536
1659
  // Use semantic version comparison (1.6.0 > 1.5.9)
1537
1660
  const comparison = compareVersions(latestVersion, currentVersion);
1538
1661
  if (comparison <= 0) {
@@ -1627,6 +1750,39 @@ Examples:
1627
1750
  });
1628
1751
  });
1629
1752
  }
1753
+ // Wallet command — balance, transaction history, cloud access status
1754
+ const walletCommand = program
1755
+ .command('wallet')
1756
+ .alias('w')
1757
+ .description('Manage your VigCoin wallet and cloud access');
1758
+ walletCommand
1759
+ .command('balance')
1760
+ .alias('bal')
1761
+ .description('Show current VigCoin balance')
1762
+ .option('--json', 'Emit JSON output', false)
1763
+ .action(async (options) => {
1764
+ const wallet = new WalletCommand();
1765
+ await wallet.showBalance({ json: options.json });
1766
+ });
1767
+ walletCommand
1768
+ .command('history')
1769
+ .alias('hist')
1770
+ .description('Show recent VigCoin transactions')
1771
+ .option('-n, --n <number>', 'Number of transactions to show (default: 20)', parseInt)
1772
+ .option('--json', 'Emit JSON output', false)
1773
+ .action(async (options) => {
1774
+ const wallet = new WalletCommand();
1775
+ await wallet.showHistory({ n: options.n, json: options.json });
1776
+ });
1777
+ walletCommand
1778
+ .command('cloud-status')
1779
+ .alias('cs')
1780
+ .description('Show cloud model access status and pricing')
1781
+ .option('--json', 'Emit JSON output', false)
1782
+ .action(async (options) => {
1783
+ const wallet = new WalletCommand();
1784
+ await wallet.showCloudStatus({ json: options.json });
1785
+ });
1630
1786
  try {
1631
1787
  // Default to chat if no command
1632
1788
  if (args.length === 2) {