vigthoria-cli 1.13.8 → 1.13.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/index.js +5 -0
- package/dist/commands/legion.js +21 -46
- package/dist/commands/update.js +120 -20
- package/dist/commands/wallet.d.ts +2 -0
- package/dist/commands/wallet.js +35 -5
- package/dist/index.js +55 -41
- package/install.ps1 +1 -1
- package/install.sh +44 -11
- package/package.json +1 -1
package/dist/commands/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { RepoCommand } from './repo.js';
|
|
|
18
18
|
import { ReviewCommand } from './review.js';
|
|
19
19
|
import { WorkflowCommand } from './workflow.js';
|
|
20
20
|
import { V4Command } from './v4.js';
|
|
21
|
+
import { WalletCommand } from './wallet.js';
|
|
21
22
|
function isRegisterableCommand(value) {
|
|
22
23
|
return Boolean(value && typeof value === 'object');
|
|
23
24
|
}
|
|
@@ -136,6 +137,10 @@ const commandRegistry = [
|
|
|
136
137
|
name: 'history',
|
|
137
138
|
handler: createClassRegistrar(HistoryCommand),
|
|
138
139
|
},
|
|
140
|
+
{
|
|
141
|
+
name: 'wallet',
|
|
142
|
+
handler: createClassRegistrar(WalletCommand),
|
|
143
|
+
},
|
|
139
144
|
{
|
|
140
145
|
name: 'replay',
|
|
141
146
|
handler: createClassRegistrar(ReplayCommand),
|
package/dist/commands/legion.js
CHANGED
|
@@ -144,7 +144,7 @@ export class LegionCommand {
|
|
|
144
144
|
console.log(chalk.gray(' --repro-cmd <cmd> Run a local reproducibility command before spend'));
|
|
145
145
|
console.log(chalk.gray(' --expect-repro-fail Require repro command to fail before execution'));
|
|
146
146
|
console.log(chalk.gray(' --approve Skip initial confirmation prompt and execute'));
|
|
147
|
-
console.log(chalk.gray(' --auto-charge
|
|
147
|
+
console.log(chalk.gray(' --auto-charge Charge the canonical PAYG wallet when balance is sufficient'));
|
|
148
148
|
console.log(chalk.gray(' --timeout <sec> Abort remote execution if no result within timeout (default: 120)'));
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
@@ -195,7 +195,7 @@ export class LegionCommand {
|
|
|
195
195
|
if (!billingGate.canProceed) {
|
|
196
196
|
const resolved = await this.resolveBillingInsufficientFunds(currentQuote, billingGate, options);
|
|
197
197
|
if (!resolved) {
|
|
198
|
-
console.log(chalk.yellow('Cortex cancelled due to insufficient
|
|
198
|
+
console.log(chalk.yellow('Cortex cancelled due to insufficient PAYG credit balance.'));
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
201
|
billingGate = await this.evaluateBillingGate(currentQuote);
|
|
@@ -301,7 +301,7 @@ export class LegionCommand {
|
|
|
301
301
|
console.log(chalk.gray(` Next round: ${nextRound}`));
|
|
302
302
|
console.log(chalk.gray(` Failed step: ${execution.failedStepId || 'unknown'} (${execution.failedWorker || 'unknown'})`));
|
|
303
303
|
console.log(chalk.gray(` Additional estimate: $${nextQuote.finalUsd.toFixed(4)} / ${nextQuote.vigcoinRequired.toFixed(3)} VIG`));
|
|
304
|
-
const answer = (await rl.question('Confirm additional
|
|
304
|
+
const answer = (await rl.question('Confirm additional PAYG credit deduction and continue? (y/N): ')).trim().toLowerCase();
|
|
305
305
|
return answer === 'y' || answer === 'yes';
|
|
306
306
|
}
|
|
307
307
|
finally {
|
|
@@ -844,7 +844,7 @@ export class LegionCommand {
|
|
|
844
844
|
available: true,
|
|
845
845
|
vigcoinBalance: 0,
|
|
846
846
|
source: 'forced_low_credit',
|
|
847
|
-
purchaseUrl:
|
|
847
|
+
purchaseUrl: 'https://hub.vigthoria.io/credits',
|
|
848
848
|
};
|
|
849
849
|
}
|
|
850
850
|
if (forcedBalanceRaw) {
|
|
@@ -854,7 +854,7 @@ export class LegionCommand {
|
|
|
854
854
|
available: true,
|
|
855
855
|
vigcoinBalance: forced,
|
|
856
856
|
source: 'forced_balance',
|
|
857
|
-
purchaseUrl:
|
|
857
|
+
purchaseUrl: 'https://hub.vigthoria.io/credits',
|
|
858
858
|
};
|
|
859
859
|
}
|
|
860
860
|
}
|
|
@@ -892,7 +892,7 @@ export class LegionCommand {
|
|
|
892
892
|
available: true,
|
|
893
893
|
vigcoinBalance: balance,
|
|
894
894
|
source: endpoint,
|
|
895
|
-
purchaseUrl: this.getPurchaseUrlFromPayload(baseUrl, payloadObj) ||
|
|
895
|
+
purchaseUrl: this.getPurchaseUrlFromPayload(baseUrl, payloadObj) || 'https://hub.vigthoria.io/credits',
|
|
896
896
|
};
|
|
897
897
|
}
|
|
898
898
|
catch (err) {
|
|
@@ -904,7 +904,7 @@ export class LegionCommand {
|
|
|
904
904
|
available: false,
|
|
905
905
|
vigcoinBalance: null,
|
|
906
906
|
source: null,
|
|
907
|
-
purchaseUrl:
|
|
907
|
+
purchaseUrl: 'https://hub.vigthoria.io/credits',
|
|
908
908
|
error: sawUnauthorized
|
|
909
909
|
? 'Wallet session expired or unauthorized. Run: vigthoria login'
|
|
910
910
|
: 'Wallet endpoint unavailable from current gateway session',
|
|
@@ -913,12 +913,10 @@ export class LegionCommand {
|
|
|
913
913
|
async attemptDirectCharge(vigcoinNeeded) {
|
|
914
914
|
const baseUrl = this.getBillingBaseUrl();
|
|
915
915
|
const headers = this.getHeaders();
|
|
916
|
-
const amount = Math.max(
|
|
916
|
+
const amount = Math.round(Math.max(0.01, vigcoinNeeded) * 1000000) / 1000000;
|
|
917
917
|
const requestId = `cortex_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
918
918
|
const chargePayloads = [
|
|
919
919
|
{ endpoint: '/api/viagen6/vigcoin/charge', body: { amount, reason: 'cortex_legion', requestId } },
|
|
920
|
-
{ endpoint: '/api/wallet/charge', body: { amount, currency: 'VIGCOIN', reason: 'cortex_legion', requestId } },
|
|
921
|
-
{ endpoint: '/api/billing/topup', body: { vigcoin: amount, reason: 'cortex_legion', requestId } },
|
|
922
920
|
];
|
|
923
921
|
for (const attempt of chargePayloads) {
|
|
924
922
|
try {
|
|
@@ -929,11 +927,11 @@ export class LegionCommand {
|
|
|
929
927
|
body: JSON.stringify(attempt.body),
|
|
930
928
|
});
|
|
931
929
|
const payload = await response.json().catch((err) => {
|
|
932
|
-
this.logger.warn(this.formatLegionError(`
|
|
930
|
+
this.logger.warn(this.formatLegionError(`PAYG wallet charge response ${attempt.endpoint}`, err));
|
|
933
931
|
return {};
|
|
934
932
|
});
|
|
935
933
|
const payloadObj = (payload && typeof payload === 'object') ? payload : {};
|
|
936
|
-
const checkoutUrl = this.getPurchaseUrlFromPayload(baseUrl, payloadObj) ||
|
|
934
|
+
const checkoutUrl = this.getPurchaseUrlFromPayload(baseUrl, payloadObj) || 'https://hub.vigthoria.io/credits';
|
|
937
935
|
if (!response.ok) {
|
|
938
936
|
continue;
|
|
939
937
|
}
|
|
@@ -948,13 +946,13 @@ export class LegionCommand {
|
|
|
948
946
|
};
|
|
949
947
|
}
|
|
950
948
|
catch (err) {
|
|
951
|
-
this.logger.warn(this.formatLegionError(`
|
|
949
|
+
this.logger.warn(this.formatLegionError(`PAYG wallet charge request ${attempt.endpoint}`, err));
|
|
952
950
|
continue;
|
|
953
951
|
}
|
|
954
952
|
}
|
|
955
953
|
return {
|
|
956
954
|
ok: false,
|
|
957
|
-
checkoutUrl:
|
|
955
|
+
checkoutUrl: 'https://hub.vigthoria.io/credits',
|
|
958
956
|
note: 'No direct charge endpoint accepted this request',
|
|
959
957
|
};
|
|
960
958
|
}
|
|
@@ -962,18 +960,19 @@ export class LegionCommand {
|
|
|
962
960
|
if (gate.masterAdminFree) {
|
|
963
961
|
return true;
|
|
964
962
|
}
|
|
965
|
-
const spinner = createSpinner('Charging
|
|
963
|
+
const spinner = createSpinner('Charging Vigthoria Credits for Cortex execution...').start();
|
|
966
964
|
const result = await this.attemptDirectCharge(billingQuote.vigcoinRequired);
|
|
967
965
|
spinner.stop();
|
|
968
966
|
if (!result.ok) {
|
|
969
967
|
console.log(chalk.red(result.note || 'Wallet charge failed.'));
|
|
970
|
-
console.log(chalk.yellow(`Complete purchase first: ${result.checkoutUrl ||
|
|
968
|
+
console.log(chalk.yellow(`Complete purchase first: ${result.checkoutUrl || 'https://hub.vigthoria.io/credits'}`));
|
|
971
969
|
return false;
|
|
972
970
|
}
|
|
973
|
-
console.log(chalk.green('
|
|
971
|
+
console.log(chalk.green('PAYG wallet charged for Cortex execution.'));
|
|
974
972
|
return true;
|
|
975
973
|
}
|
|
976
974
|
async resolveBillingInsufficientFunds(billingQuote, gate, options) {
|
|
975
|
+
void options;
|
|
977
976
|
this.printBillingGateSummary(billingQuote, gate);
|
|
978
977
|
if (!gate.wallet.available) {
|
|
979
978
|
console.log(chalk.red('Unable to verify wallet balance from server. Execution is blocked.'));
|
|
@@ -982,20 +981,8 @@ export class LegionCommand {
|
|
|
982
981
|
}
|
|
983
982
|
return false;
|
|
984
983
|
}
|
|
985
|
-
if (options.autoCharge) {
|
|
986
|
-
const spinner = createSpinner('Attempting direct wallet charge...').start();
|
|
987
|
-
const result = await this.attemptDirectCharge(billingQuote.vigcoinRequired - (gate.wallet.vigcoinBalance || 0));
|
|
988
|
-
spinner.stop();
|
|
989
|
-
if (result.ok) {
|
|
990
|
-
console.log(chalk.green('Direct charge succeeded. Re-checking wallet balance...'));
|
|
991
|
-
return true;
|
|
992
|
-
}
|
|
993
|
-
console.log(chalk.yellow(result.note || 'Direct charge did not complete.'));
|
|
994
|
-
console.log(chalk.yellow(`Complete purchase first: ${result.checkoutUrl || gate.wallet.purchaseUrl || `${this.getBillingBaseUrl()}/billing`}`));
|
|
995
|
-
return false;
|
|
996
|
-
}
|
|
997
984
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
998
|
-
console.log(chalk.yellow('Low balance detected
|
|
985
|
+
console.log(chalk.yellow('Low balance detected. Top up the PAYG wallet before retrying.'));
|
|
999
986
|
if (gate.wallet.purchaseUrl) {
|
|
1000
987
|
console.log(chalk.gray(`Billing portal: ${gate.wallet.purchaseUrl}`));
|
|
1001
988
|
}
|
|
@@ -1003,21 +990,9 @@ export class LegionCommand {
|
|
|
1003
990
|
}
|
|
1004
991
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1005
992
|
try {
|
|
1006
|
-
const answer = (await rl.question('
|
|
1007
|
-
if (answer === 'c' || answer === 'charge') {
|
|
1008
|
-
const spinner = createSpinner('Attempting direct wallet charge...').start();
|
|
1009
|
-
const result = await this.attemptDirectCharge(billingQuote.vigcoinRequired - (gate.wallet.vigcoinBalance || 0));
|
|
1010
|
-
spinner.stop();
|
|
1011
|
-
if (result.ok) {
|
|
1012
|
-
console.log(chalk.green('Direct charge succeeded. Re-checking wallet balance...'));
|
|
1013
|
-
return true;
|
|
1014
|
-
}
|
|
1015
|
-
console.log(chalk.yellow(result.note || 'Direct charge did not complete.'));
|
|
1016
|
-
console.log(chalk.yellow(`Complete purchase first: ${result.checkoutUrl || gate.wallet.purchaseUrl || `${this.getBillingBaseUrl()}/billing`}`));
|
|
1017
|
-
return false;
|
|
1018
|
-
}
|
|
993
|
+
const answer = (await rl.question('PAYG credits low. Choose action: [p]urchase first, [n] cancel: ')).trim().toLowerCase();
|
|
1019
994
|
if (answer === 'p' || answer === 'purchase') {
|
|
1020
|
-
console.log(chalk.yellow(`Purchase
|
|
995
|
+
console.log(chalk.yellow(`Purchase Vigthoria Credits first: ${gate.wallet.purchaseUrl || 'https://hub.vigthoria.io/credits'}`));
|
|
1021
996
|
return false;
|
|
1022
997
|
}
|
|
1023
998
|
return false;
|
|
@@ -1035,8 +1010,8 @@ export class LegionCommand {
|
|
|
1035
1010
|
}
|
|
1036
1011
|
console.log(chalk.gray(` Estimated total (USD): $${billingQuote.retryAdjustedUsd.toFixed(4)}`) + chalk.gray(' (retry-adjusted expected)'));
|
|
1037
1012
|
console.log(chalk.gray(` Platform fee: +${billingQuote.platformFeePct.toFixed(0)}% (already included in all estimates)`));
|
|
1038
|
-
console.log(chalk.gray(`
|
|
1039
|
-
console.log(chalk.gray(`
|
|
1013
|
+
console.log(chalk.gray(` Credit rate: 1 credit = $${billingQuote.vigcoinRateUsd.toFixed(4)}`));
|
|
1014
|
+
console.log(chalk.gray(` Credits required: ${billingQuote.vigcoinRequired.toFixed(3)}`));
|
|
1040
1015
|
if (gate.wallet.vigcoinBalance !== null) {
|
|
1041
1016
|
const color = gate.wallet.vigcoinBalance >= billingQuote.vigcoinRequired ? chalk.green : chalk.red;
|
|
1042
1017
|
console.log(chalk.gray(' Wallet balance: ') + color(gate.wallet.vigcoinBalance.toFixed(3)) + (gate.wallet.source ? chalk.gray(` (source: ${gate.wallet.source})`) : ''));
|
package/dist/commands/update.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
3
6
|
import * as path from 'node:path';
|
|
4
7
|
import { fileURLToPath } from 'node:url';
|
|
5
8
|
import { promisify } from 'node:util';
|
|
6
|
-
import { installUpdateWindows } from '../utils/tools.js';
|
|
7
9
|
// ESM shim — __filename is unavailable under "type": "module".
|
|
8
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
11
|
const execFileAsync = promisify(execFile);
|
|
12
|
+
const DEFAULT_UPDATE_MANIFEST = 'https://extension.vigthoria.io/downloads/manifest.json';
|
|
13
|
+
const TRUSTED_RELEASE_HOSTS = new Set([
|
|
14
|
+
'extension.vigthoria.io',
|
|
15
|
+
'landing.vigthoria.io',
|
|
16
|
+
'cli.vigthoria.io',
|
|
17
|
+
'coder.vigthoria.io',
|
|
18
|
+
'market.vigthoria.io',
|
|
19
|
+
]);
|
|
10
20
|
function markSuccessExit() {
|
|
11
21
|
process.exitCode = 0;
|
|
12
22
|
}
|
|
@@ -99,6 +109,53 @@ async function getLatestVersion(packageName) {
|
|
|
99
109
|
throw new Error(`Unable to check the latest published version: ${message}`);
|
|
100
110
|
}
|
|
101
111
|
}
|
|
112
|
+
function validateTrustedHttpsUrl(value, label) {
|
|
113
|
+
let parsed;
|
|
114
|
+
try {
|
|
115
|
+
parsed = new URL(value);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
throw new Error(`${label} is not a valid URL`);
|
|
119
|
+
}
|
|
120
|
+
if (parsed.protocol !== 'https:' || !TRUSTED_RELEASE_HOSTS.has(parsed.hostname)) {
|
|
121
|
+
throw new Error(`${label} must use HTTPS on an approved Vigthoria release host`);
|
|
122
|
+
}
|
|
123
|
+
return parsed;
|
|
124
|
+
}
|
|
125
|
+
async function getStableRelease() {
|
|
126
|
+
const manifestUrl = process.env.VIGTHORIA_UPDATE_MANIFEST_URL
|
|
127
|
+
|| DEFAULT_UPDATE_MANIFEST;
|
|
128
|
+
validateTrustedHttpsUrl(manifestUrl, 'Update manifest URL');
|
|
129
|
+
const controller = new AbortController();
|
|
130
|
+
const timeout = setTimeout(() => controller.abort(), 20_000);
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch(manifestUrl, {
|
|
133
|
+
headers: { Accept: 'application/json' },
|
|
134
|
+
redirect: 'follow',
|
|
135
|
+
signal: controller.signal,
|
|
136
|
+
});
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
throw new Error(`manifest returned HTTP ${response.status}`);
|
|
139
|
+
}
|
|
140
|
+
validateTrustedHttpsUrl(response.url || manifestUrl, 'Resolved manifest URL');
|
|
141
|
+
const document = await response.json();
|
|
142
|
+
const stable = document.channels?.stable;
|
|
143
|
+
const version = String(stable?.version || '').trim();
|
|
144
|
+
const url = String(stable?.url || '').trim();
|
|
145
|
+
const sha256 = String(stable?.sha256 || '').trim().toLowerCase();
|
|
146
|
+
if (!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/.test(version)) {
|
|
147
|
+
throw new Error('stable manifest version is invalid');
|
|
148
|
+
}
|
|
149
|
+
validateTrustedHttpsUrl(url, 'Stable release URL');
|
|
150
|
+
if (!/^[a-f0-9]{64}$/.test(sha256)) {
|
|
151
|
+
throw new Error('stable release SHA-256 is missing or invalid');
|
|
152
|
+
}
|
|
153
|
+
return { version, url, sha256 };
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
clearTimeout(timeout);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
102
159
|
async function getVersionInfo() {
|
|
103
160
|
const packageJson = readOwnPackageJson();
|
|
104
161
|
const packageName = packageJson.name;
|
|
@@ -106,16 +163,28 @@ async function getVersionInfo() {
|
|
|
106
163
|
if (!packageName || !current) {
|
|
107
164
|
throw new Error('The CLI package metadata must include both name and version.');
|
|
108
165
|
}
|
|
109
|
-
|
|
166
|
+
let release;
|
|
167
|
+
let latest;
|
|
168
|
+
try {
|
|
169
|
+
release = await getStableRelease();
|
|
170
|
+
latest = release.version;
|
|
171
|
+
}
|
|
172
|
+
catch (manifestError) {
|
|
173
|
+
const reason = manifestError instanceof Error
|
|
174
|
+
? manifestError.message
|
|
175
|
+
: String(manifestError);
|
|
176
|
+
console.warn(`Verified release manifest unavailable (${reason}); checking npm.`);
|
|
177
|
+
latest = await getLatestVersion(packageName);
|
|
178
|
+
}
|
|
110
179
|
return {
|
|
111
180
|
current,
|
|
112
181
|
latest,
|
|
113
182
|
packageName,
|
|
114
183
|
updateAvailable: compareVersions(current, latest) < 0,
|
|
184
|
+
release,
|
|
115
185
|
};
|
|
116
186
|
}
|
|
117
|
-
function
|
|
118
|
-
const target = `${packageName}@latest`;
|
|
187
|
+
function resolveInstallTargetCommand(packageManager, target, installGlobal) {
|
|
119
188
|
switch (packageManager) {
|
|
120
189
|
case 'pnpm':
|
|
121
190
|
return { command: 'pnpm', args: installGlobal ? ['add', '--global', target] : ['add', '-D', target] };
|
|
@@ -128,6 +197,9 @@ function resolveInstallCommand(packageManager, packageName, installGlobal) {
|
|
|
128
197
|
return { command: 'npm', args: installGlobal ? ['install', '--global', target] : ['install', '--save-dev', target] };
|
|
129
198
|
}
|
|
130
199
|
}
|
|
200
|
+
function resolveInstallCommand(packageManager, packageName, installGlobal) {
|
|
201
|
+
return resolveInstallTargetCommand(packageManager, `${packageName}@latest`, installGlobal);
|
|
202
|
+
}
|
|
131
203
|
function formatCommand(command) {
|
|
132
204
|
return [command.command, ...command.args].map(quoteCmdArg).join(' ');
|
|
133
205
|
}
|
|
@@ -135,6 +207,39 @@ async function runInstall(packageManager, packageName, installGlobal) {
|
|
|
135
207
|
const installCommand = resolveInstallCommand(packageManager, packageName, installGlobal);
|
|
136
208
|
await execPackageManager(installCommand.command, installCommand.args, 120_000);
|
|
137
209
|
}
|
|
210
|
+
async function downloadVerifiedRelease(release) {
|
|
211
|
+
const directory = await mkdtemp(path.join(tmpdir(), 'vigthoria-cli-update-'));
|
|
212
|
+
const archive = path.join(directory, `vigthoria-cli-${release.version}.tgz`);
|
|
213
|
+
try {
|
|
214
|
+
const controller = new AbortController();
|
|
215
|
+
const timeout = setTimeout(() => controller.abort(), 60_000);
|
|
216
|
+
let response;
|
|
217
|
+
try {
|
|
218
|
+
response = await fetch(release.url, {
|
|
219
|
+
redirect: 'follow',
|
|
220
|
+
signal: controller.signal,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
clearTimeout(timeout);
|
|
225
|
+
}
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
throw new Error(`release download returned HTTP ${response.status}`);
|
|
228
|
+
}
|
|
229
|
+
validateTrustedHttpsUrl(response.url || release.url, 'Resolved release URL');
|
|
230
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
231
|
+
const digest = createHash('sha256').update(bytes).digest('hex');
|
|
232
|
+
if (digest !== release.sha256) {
|
|
233
|
+
throw new Error(`release checksum mismatch (expected ${release.sha256}, received ${digest})`);
|
|
234
|
+
}
|
|
235
|
+
await writeFile(archive, bytes, { mode: 0o600 });
|
|
236
|
+
return { directory, archive };
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
await rm(directory, { recursive: true, force: true });
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
138
243
|
export async function updateCommand(options = {}) {
|
|
139
244
|
try {
|
|
140
245
|
const info = await getVersionInfo();
|
|
@@ -152,26 +257,21 @@ export async function updateCommand(options = {}) {
|
|
|
152
257
|
}
|
|
153
258
|
const packageManager = options.packageManager ?? 'npm';
|
|
154
259
|
const installGlobal = options.global ?? true;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
console.warn(`Manual installation command: ${formatCommand(fallbackCommand)}`);
|
|
163
|
-
console.warn('If automatic fallback fails, run the command above manually or download the latest Windows installer from the Vigthoria release page.');
|
|
164
|
-
await execPackageManager(fallbackCommand.command, fallbackCommand.args, 120_000);
|
|
165
|
-
console.log('Package manager installation finished successfully.');
|
|
166
|
-
}
|
|
167
|
-
else if (!installerResult.success) {
|
|
168
|
-
throw new Error(installerResult.error || 'Windows installer failed to start.');
|
|
260
|
+
if (info.release) {
|
|
261
|
+
console.log(`Installing checksum-verified ${info.packageName}@${info.release.version} `
|
|
262
|
+
+ `with ${packageManager}...`);
|
|
263
|
+
const downloaded = await downloadVerifiedRelease(info.release);
|
|
264
|
+
try {
|
|
265
|
+
const installCommand = resolveInstallTargetCommand(packageManager, downloaded.archive, installGlobal);
|
|
266
|
+
await execPackageManager(installCommand.command, installCommand.args, 120_000);
|
|
169
267
|
}
|
|
170
|
-
|
|
171
|
-
|
|
268
|
+
finally {
|
|
269
|
+
await rm(downloaded.directory, { recursive: true, force: true });
|
|
172
270
|
}
|
|
271
|
+
console.log('Verified release installation finished successfully.');
|
|
173
272
|
}
|
|
174
273
|
else {
|
|
274
|
+
console.log(`Installing ${info.packageName}@latest with ${packageManager}...`);
|
|
175
275
|
await runInstall(packageManager, info.packageName, installGlobal);
|
|
176
276
|
console.log('Package manager installation finished successfully.');
|
|
177
277
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* vigthoria wallet history [--n 20] — recent transactions
|
|
6
6
|
* vigthoria wallet cloud-status — show cloud access and model pricing
|
|
7
7
|
*/
|
|
8
|
+
import type { Command } from 'commander';
|
|
8
9
|
export interface WalletOptions {
|
|
9
10
|
n?: number;
|
|
10
11
|
json?: boolean;
|
|
@@ -13,6 +14,7 @@ export declare class WalletCommand {
|
|
|
13
14
|
private config;
|
|
14
15
|
private logger;
|
|
15
16
|
constructor();
|
|
17
|
+
register(program: Command): void;
|
|
16
18
|
private getBaseUrl;
|
|
17
19
|
private getToken;
|
|
18
20
|
private getRefreshToken;
|
package/dist/commands/wallet.js
CHANGED
|
@@ -15,6 +15,31 @@ export class WalletCommand {
|
|
|
15
15
|
this.config = new Config();
|
|
16
16
|
this.logger = new Logger();
|
|
17
17
|
}
|
|
18
|
+
register(program) {
|
|
19
|
+
const wallet = program
|
|
20
|
+
.command('wallet')
|
|
21
|
+
.description('Inspect monetary Vigthoria Credits used for Cloud, API, Video, and other PAYG services');
|
|
22
|
+
wallet
|
|
23
|
+
.command('balance')
|
|
24
|
+
.description('Show purchased and paid-subscription credit balances')
|
|
25
|
+
.option('--json', 'Emit machine-readable JSON output', false)
|
|
26
|
+
.action(async (options) => this.showBalance(options));
|
|
27
|
+
wallet
|
|
28
|
+
.command('history')
|
|
29
|
+
.description('Show monetary wallet transactions')
|
|
30
|
+
.option('-n, --n <count>', 'Maximum transactions to show', '20')
|
|
31
|
+
.option('--json', 'Emit machine-readable JSON output', false)
|
|
32
|
+
.action(async (options) => this.showHistory({
|
|
33
|
+
n: Number(options.n || 20),
|
|
34
|
+
json: options.json,
|
|
35
|
+
}));
|
|
36
|
+
wallet
|
|
37
|
+
.command('cloud-status')
|
|
38
|
+
.description('Show PAYG cloud eligibility and model pricing')
|
|
39
|
+
.option('--json', 'Emit machine-readable JSON output', false)
|
|
40
|
+
.action(async (options) => this.showCloudStatus(options));
|
|
41
|
+
wallet.action(async () => this.showBalance());
|
|
42
|
+
}
|
|
18
43
|
getBaseUrl() {
|
|
19
44
|
return String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
|
|
20
45
|
}
|
|
@@ -99,16 +124,21 @@ export class WalletCommand {
|
|
|
99
124
|
return;
|
|
100
125
|
}
|
|
101
126
|
console.log('');
|
|
102
|
-
console.log(chalk.bold.cyan('
|
|
127
|
+
console.log(chalk.bold.cyan(' Vigthoria PAYG Wallet'));
|
|
103
128
|
console.log(chalk.gray(' ─────────────────────────────'));
|
|
104
|
-
console.log(`
|
|
105
|
-
|
|
106
|
-
|
|
129
|
+
console.log(` PAYG balance: ${chalk.bold.yellow(data.balance.toLocaleString())} credits`);
|
|
130
|
+
const purchased = Number(data.purchasedBalance || 0);
|
|
131
|
+
const subscription = Number(data.subscriptionBalance || 0);
|
|
132
|
+
console.log(` Purchased: ${chalk.green(purchased.toLocaleString())} credits`);
|
|
133
|
+
console.log(` Paid subscription: ${chalk.cyan(subscription.toLocaleString())} credits`);
|
|
134
|
+
console.log(` Lifetime funded: ${chalk.green(data.lifetimeEarned.toLocaleString())} credits`);
|
|
135
|
+
console.log(` Lifetime PAYG spend: ${chalk.red(data.lifetimeSpent.toLocaleString())} credits`);
|
|
107
136
|
if (data.lastUpdated) {
|
|
108
137
|
console.log(` Last updated: ${chalk.gray(new Date(data.lastUpdated).toLocaleString())}`);
|
|
109
138
|
}
|
|
110
139
|
console.log('');
|
|
111
|
-
console.log(chalk.gray('
|
|
140
|
+
console.log(chalk.gray(' Music Points and other promotional rewards are product-only and are not included.'));
|
|
141
|
+
console.log(chalk.gray(' Top up monetary credits at: https://hub.vigthoria.io/credits'));
|
|
112
142
|
console.log('');
|
|
113
143
|
}
|
|
114
144
|
catch (err) {
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { ChatCommand } from './commands/chat.js';
|
|
|
20
20
|
import { EditCommand } from './commands/edit.js';
|
|
21
21
|
import { GenerateCommand } from './commands/generate.js';
|
|
22
22
|
import { ExplainCommand } from './commands/explain.js';
|
|
23
|
-
import { handleLogin, handleLogout, statusAction } from './commands/auth.js';
|
|
23
|
+
import { handleLogin, handleLogout, registerAuthCommands, statusAction } from './commands/auth.js';
|
|
24
24
|
import { ConfigCommand } from './commands/config.js';
|
|
25
25
|
import { ReviewCommand } from './commands/review.js';
|
|
26
26
|
import { HubCommand } from './commands/hub.js';
|
|
@@ -39,6 +39,7 @@ import { ForkCommand } from './commands/fork.js';
|
|
|
39
39
|
import { CancelCommand } from './commands/cancel.js';
|
|
40
40
|
import { BackgroundCommand } from './commands/background.js';
|
|
41
41
|
import { SecurityCommand } from './commands/security.js';
|
|
42
|
+
import { WalletCommand } from './commands/wallet.js';
|
|
42
43
|
import { V4Command } from './commands/v4.js';
|
|
43
44
|
import { runV4Menu } from './commands/v4-menu.js';
|
|
44
45
|
import { Config } from './utils/config.js';
|
|
@@ -51,6 +52,7 @@ import { fileURLToPath } from 'url';
|
|
|
51
52
|
import { createHash } from 'crypto';
|
|
52
53
|
import axios from 'axios';
|
|
53
54
|
import { renderDynamicHelp, selectInteractiveCommand } from './utils/command-menu.js';
|
|
55
|
+
import { isIOSEnvironment, resolveClientOsSlug } from './utils/clientManifest.js';
|
|
54
56
|
// ESM shim — TypeScript emits ESM under "type": "module", so __dirname
|
|
55
57
|
// is not available natively. Restore the CommonJS-style helper so the
|
|
56
58
|
// pre-existing package-discovery logic continues to work unchanged.
|
|
@@ -285,6 +287,23 @@ async function downloadFile(url, targetPath) {
|
|
|
285
287
|
});
|
|
286
288
|
fs.writeFileSync(targetPath, Buffer.from(response.data));
|
|
287
289
|
}
|
|
290
|
+
const TRUSTED_RELEASE_HOSTS = new Set([
|
|
291
|
+
'extension.vigthoria.io',
|
|
292
|
+
'landing.vigthoria.io',
|
|
293
|
+
'coder.vigthoria.io',
|
|
294
|
+
'market.vigthoria.io',
|
|
295
|
+
]);
|
|
296
|
+
function validateReleaseUrl(rawUrl, allowLocalhost = false) {
|
|
297
|
+
const parsed = new URL(rawUrl);
|
|
298
|
+
const local = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname === '::1';
|
|
299
|
+
if (parsed.protocol !== 'https:' && !(allowLocalhost && local && parsed.protocol === 'http:')) {
|
|
300
|
+
throw new Error('Release URLs must use HTTPS (HTTP is allowed only for an explicit localhost manifest).');
|
|
301
|
+
}
|
|
302
|
+
if (!TRUSTED_RELEASE_HOSTS.has(parsed.hostname) && !(allowLocalhost && local)) {
|
|
303
|
+
throw new Error(`Untrusted release host: ${parsed.hostname}`);
|
|
304
|
+
}
|
|
305
|
+
return parsed;
|
|
306
|
+
}
|
|
288
307
|
function sha256File(filePath) {
|
|
289
308
|
const hash = createHash('sha256');
|
|
290
309
|
const data = fs.readFileSync(filePath);
|
|
@@ -1459,7 +1478,7 @@ Examples:
|
|
|
1459
1478
|
.option('--cortex', 'Vigthoria Cortex: maximum intelligence execution')
|
|
1460
1479
|
.option('--approve', 'Auto-approve Cortex execution prompt')
|
|
1461
1480
|
.option('--no-approve', 'Require interactive approval before execution')
|
|
1462
|
-
.option('--auto-charge', '
|
|
1481
|
+
.option('--auto-charge', 'Compatibility flag; low balances still require a verified User Hub top-up')
|
|
1463
1482
|
.option('--plan-only', 'Run Cortex estimator only; do not execute Legion job')
|
|
1464
1483
|
.option('--force-budget', 'Allow execution when estimated budget exceeds the hard safe-stop ceiling')
|
|
1465
1484
|
.option('--ignore-preflight', 'Bypass mandatory local preflight checks (no warranty)')
|
|
@@ -1558,6 +1577,8 @@ Examples:
|
|
|
1558
1577
|
.action(async () => {
|
|
1559
1578
|
await statusAction();
|
|
1560
1579
|
});
|
|
1580
|
+
registerAuthCommands(program);
|
|
1581
|
+
new WalletCommand().register(program);
|
|
1561
1582
|
program
|
|
1562
1583
|
.command('doctor')
|
|
1563
1584
|
.description('Run local Vigthoria CLI diagnostics (no network calls unless --check-api)')
|
|
@@ -1569,10 +1590,23 @@ Examples:
|
|
|
1569
1590
|
const offline = isOfflineMode();
|
|
1570
1591
|
const updateSuppressed = isUpdateCheckSuppressed();
|
|
1571
1592
|
const subscription = config.get('subscription') || { plan: null, status: null, expiresAt: null };
|
|
1593
|
+
const iosSandbox = isIOSEnvironment();
|
|
1594
|
+
const platformSupport = iosSandbox
|
|
1595
|
+
? 'experimental-mobile-terminal-sandbox'
|
|
1596
|
+
: process.platform === 'darwin'
|
|
1597
|
+
? 'supported-macos-desktop'
|
|
1598
|
+
: process.platform === 'win32' || process.platform === 'linux'
|
|
1599
|
+
? 'supported-desktop'
|
|
1600
|
+
: 'unsupported';
|
|
1572
1601
|
const report = {
|
|
1573
1602
|
cliVersion: VERSION,
|
|
1574
1603
|
nodeVersion: process.version,
|
|
1575
1604
|
platform: `${process.platform} ${process.arch}`,
|
|
1605
|
+
clientOs: resolveClientOsSlug(),
|
|
1606
|
+
platformSupport,
|
|
1607
|
+
platformLimitations: iosSandbox
|
|
1608
|
+
? 'Not a native iOS app. Requires an iSH-like Linux terminal with Node.js >=20.19, npm and filesystem access; Workbench and device tooling are unavailable.'
|
|
1609
|
+
: null,
|
|
1576
1610
|
cwd: process.cwd(),
|
|
1577
1611
|
homeDir: os.homedir(),
|
|
1578
1612
|
configPath: config.getConfigPath(),
|
|
@@ -1693,6 +1727,9 @@ Examples:
|
|
|
1693
1727
|
let manifestEntry = null;
|
|
1694
1728
|
if (manifestUrl) {
|
|
1695
1729
|
try {
|
|
1730
|
+
const explicitLocalManifest = Boolean(options.manifest)
|
|
1731
|
+
&& ['localhost', '127.0.0.1', '::1'].includes(new URL(manifestUrl).hostname);
|
|
1732
|
+
validateReleaseUrl(manifestUrl, explicitLocalManifest);
|
|
1696
1733
|
console.log(chalk.cyan(`Checking manifest channel ${channel}...`));
|
|
1697
1734
|
const response = await axios.get(manifestUrl, {
|
|
1698
1735
|
timeout: 10000,
|
|
@@ -1704,6 +1741,13 @@ Examples:
|
|
|
1704
1741
|
console.log(chalk.yellow(`Manifest has no installable entry for channel ${channel}; will check npm.`));
|
|
1705
1742
|
manifestEntry = null;
|
|
1706
1743
|
}
|
|
1744
|
+
else {
|
|
1745
|
+
validateReleaseUrl(manifestEntry.url, explicitLocalManifest);
|
|
1746
|
+
if (!/^[a-f0-9]{64}$/i.test(String(manifestEntry.sha256 || ''))) {
|
|
1747
|
+
console.log(chalk.yellow('Manifest entry has no valid SHA-256 checksum; refusing it and checking npm.'));
|
|
1748
|
+
manifestEntry = null;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1707
1751
|
}
|
|
1708
1752
|
catch (error) {
|
|
1709
1753
|
console.log(chalk.yellow(`Manifest check failed: ${error.message}`));
|
|
@@ -1755,22 +1799,16 @@ Examples:
|
|
|
1755
1799
|
try {
|
|
1756
1800
|
console.log(chalk.cyan(`Downloading release package (${manifestEntry.version})...`));
|
|
1757
1801
|
await downloadFile(manifestEntry.url, tmpFile);
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
return;
|
|
1767
|
-
}
|
|
1768
|
-
console.log(chalk.green('Checksum verification passed'));
|
|
1769
|
-
}
|
|
1770
|
-
else {
|
|
1771
|
-
console.log(chalk.yellow.bold('WARNING: manifest entry has no sha256.'));
|
|
1772
|
-
console.log(chalk.yellow(' The release artifact will be installed without integrity verification.'));
|
|
1802
|
+
const expected = String(manifestEntry.sha256).toLowerCase();
|
|
1803
|
+
const actual = sha256File(tmpFile).toLowerCase();
|
|
1804
|
+
if (actual !== expected) {
|
|
1805
|
+
console.error(chalk.red('Release checksum verification failed'));
|
|
1806
|
+
console.error(chalk.red(`Expected: ${expected}`));
|
|
1807
|
+
console.error(chalk.red(`Actual: ${actual}`));
|
|
1808
|
+
process.exitCode = 1;
|
|
1809
|
+
return;
|
|
1773
1810
|
}
|
|
1811
|
+
console.log(chalk.green('Checksum verification passed'));
|
|
1774
1812
|
console.log(chalk.cyan('Installing update...'));
|
|
1775
1813
|
await installGlobalPackageWithNpm(tmpFile);
|
|
1776
1814
|
console.log(chalk.green(`Updated to version ${manifestEntry.version}`));
|
|
@@ -1864,30 +1902,6 @@ Examples:
|
|
|
1864
1902
|
.action(() => {
|
|
1865
1903
|
console.log(renderDynamicHelp(program));
|
|
1866
1904
|
});
|
|
1867
|
-
const codingCommandDefinitions = [
|
|
1868
|
-
{ name: 'edit', description: 'Edit code by describing the desired change', instruction: 'Edit the project according to this request' },
|
|
1869
|
-
{ name: 'generate', description: 'Generate code from a prompt', instruction: 'Generate code for this request' },
|
|
1870
|
-
{ name: 'explain', description: 'Explain code, errors, or project behavior', instruction: 'Explain this request clearly and practically' },
|
|
1871
|
-
{ name: 'review', description: 'Review code and suggest concrete improvements', instruction: 'Review the project or code for this request' },
|
|
1872
|
-
{ name: 'fix', description: 'Fix bugs, build failures, or test failures', instruction: 'Fix this issue in the project' },
|
|
1873
|
-
];
|
|
1874
|
-
for (const commandDefinition of codingCommandDefinitions) {
|
|
1875
|
-
program
|
|
1876
|
-
.command(commandDefinition.name)
|
|
1877
|
-
.description(commandDefinition.description)
|
|
1878
|
-
.argument('[request...]', 'Request text for the coding assistant')
|
|
1879
|
-
.option('-m, --model <model>', 'Model to use', 'code')
|
|
1880
|
-
.option('-p, --project <path>', 'Project directory', process.cwd())
|
|
1881
|
-
.action(async (requestParts = [], options) => {
|
|
1882
|
-
const requestText = requestParts.join(' ').trim();
|
|
1883
|
-
const chat = new ChatCommand(config, logger, program);
|
|
1884
|
-
await chat.run({
|
|
1885
|
-
model: options.model || 'code',
|
|
1886
|
-
project: options.project || process.cwd(),
|
|
1887
|
-
prompt: requestText ? `${commandDefinition.instruction}: ${requestText}` : commandDefinition.instruction,
|
|
1888
|
-
});
|
|
1889
|
-
});
|
|
1890
|
-
}
|
|
1891
1905
|
try {
|
|
1892
1906
|
// Default to chat if no command
|
|
1893
1907
|
if (args.length === 2) {
|
package/install.ps1
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
$ErrorActionPreference = "Stop"
|
|
6
6
|
|
|
7
7
|
# Configuration
|
|
8
|
-
$CLI_VERSION = "1.13.
|
|
8
|
+
$CLI_VERSION = "1.13.10"
|
|
9
9
|
$INSTALL_DIR = "$env:USERPROFILE\.vigthoria"
|
|
10
10
|
$NPM_PACKAGE = "vigthoria-cli"
|
|
11
11
|
$GIT_PACKAGE_URL = "git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
|
package/install.sh
CHANGED
|
@@ -26,12 +26,13 @@ else
|
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
28
|
# Configuration
|
|
29
|
-
CLI_VERSION="1.13.
|
|
29
|
+
CLI_VERSION="1.13.10"
|
|
30
30
|
INSTALL_DIR="$HOME/.vigthoria"
|
|
31
31
|
REPO_URL="https://market.vigthoria.io/vigthoria/vigthoria-cli"
|
|
32
32
|
GIT_PACKAGE_URL="git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
|
|
33
33
|
MANIFEST_URL="${VIGTHORIA_UPDATE_MANIFEST_URL:-https://extension.vigthoria.io/downloads/manifest.json}"
|
|
34
34
|
HOSTED_TARBALL_URL="https://extension.vigthoria.io/downloads/vigthoria-cli-${CLI_VERSION}.tgz"
|
|
35
|
+
HOSTED_TARBALL_SHA256=""
|
|
35
36
|
|
|
36
37
|
resolve_release_manifest() {
|
|
37
38
|
if ! command -v curl >/dev/null 2>&1 || ! command -v python3 >/dev/null 2>&1; then
|
|
@@ -52,24 +53,34 @@ try:
|
|
|
52
53
|
stable = (data.get('channels') or {}).get('stable') or {}
|
|
53
54
|
version = str(stable.get('version') or '').strip()
|
|
54
55
|
url = str(stable.get('url') or '').strip()
|
|
56
|
+
sha256 = str(stable.get('sha256') or '').strip().lower()
|
|
55
57
|
if version:
|
|
56
58
|
print(version)
|
|
57
59
|
if url:
|
|
58
60
|
print(url)
|
|
61
|
+
if sha256:
|
|
62
|
+
print(sha256)
|
|
59
63
|
except Exception:
|
|
60
64
|
pass
|
|
61
65
|
" 2>/dev/null || true)"
|
|
62
66
|
|
|
63
67
|
if [[ -n "$resolved" ]]; then
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
# macOS ships Bash 3.2, which does not provide mapfile/readarray.
|
|
69
|
+
# Parse the two-line response with POSIX sed instead.
|
|
70
|
+
manifest_version="$(printf '%s\n' "$resolved" | sed -n '1p')"
|
|
71
|
+
manifest_url="$(printf '%s\n' "$resolved" | sed -n '2p')"
|
|
72
|
+
manifest_sha256="$(printf '%s\n' "$resolved" | sed -n '3p')"
|
|
73
|
+
if [[ -n "$manifest_version" ]]; then
|
|
74
|
+
CLI_VERSION="$manifest_version"
|
|
67
75
|
fi
|
|
68
|
-
if [[ -n "$
|
|
69
|
-
HOSTED_TARBALL_URL="$
|
|
70
|
-
elif [[ -n "$
|
|
76
|
+
if [[ -n "$manifest_url" ]]; then
|
|
77
|
+
HOSTED_TARBALL_URL="$manifest_url"
|
|
78
|
+
elif [[ -n "$manifest_version" ]]; then
|
|
71
79
|
HOSTED_TARBALL_URL="https://extension.vigthoria.io/downloads/vigthoria-cli-${CLI_VERSION}.tgz"
|
|
72
80
|
fi
|
|
81
|
+
if [[ "$manifest_sha256" =~ ^[a-f0-9]{64}$ ]]; then
|
|
82
|
+
HOSTED_TARBALL_SHA256="$manifest_sha256"
|
|
83
|
+
fi
|
|
73
84
|
fi
|
|
74
85
|
}
|
|
75
86
|
|
|
@@ -177,10 +188,32 @@ install_cli() {
|
|
|
177
188
|
# Create install directory
|
|
178
189
|
mkdir -p "$INSTALL_DIR"
|
|
179
190
|
|
|
180
|
-
# Option 1:
|
|
181
|
-
if
|
|
182
|
-
echo "
|
|
183
|
-
|
|
191
|
+
# Option 1: Download and verify the exact hosted release package.
|
|
192
|
+
if [[ -n "$HOSTED_TARBALL_SHA256" ]]; then
|
|
193
|
+
echo "Downloading checksum-verified hosted release package..."
|
|
194
|
+
RELEASE_TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/vigthoria-cli-install.XXXXXX")"
|
|
195
|
+
RELEASE_ARCHIVE="$RELEASE_TMP_DIR/vigthoria-cli-${CLI_VERSION}.tgz"
|
|
196
|
+
if ! curl -fsSL "$HOSTED_TARBALL_URL" -o "$RELEASE_ARCHIVE"; then
|
|
197
|
+
rm -rf "$RELEASE_TMP_DIR"
|
|
198
|
+
echo -e "${RED}✗ Failed to download verified release package${NC}"
|
|
199
|
+
exit 1
|
|
200
|
+
fi
|
|
201
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
202
|
+
ACTUAL_SHA256="$(shasum -a 256 "$RELEASE_ARCHIVE" | awk '{print $1}')"
|
|
203
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
204
|
+
ACTUAL_SHA256="$(sha256sum "$RELEASE_ARCHIVE" | awk '{print $1}')"
|
|
205
|
+
else
|
|
206
|
+
rm -rf "$RELEASE_TMP_DIR"
|
|
207
|
+
echo -e "${RED}✗ SHA-256 verification tool is unavailable${NC}"
|
|
208
|
+
exit 1
|
|
209
|
+
fi
|
|
210
|
+
if [[ "$ACTUAL_SHA256" != "$HOSTED_TARBALL_SHA256" ]]; then
|
|
211
|
+
rm -rf "$RELEASE_TMP_DIR"
|
|
212
|
+
echo -e "${RED}✗ Release checksum verification failed${NC}"
|
|
213
|
+
exit 1
|
|
214
|
+
fi
|
|
215
|
+
npm install -g --force "$RELEASE_ARCHIVE"
|
|
216
|
+
rm -rf "$RELEASE_TMP_DIR"
|
|
184
217
|
elif npm view vigthoria-cli version &> /dev/null; then
|
|
185
218
|
echo "Installing from npm registry..."
|
|
186
219
|
npm install -g --force vigthoria-cli
|