@vikat/vikat-gateway-cli 1.0.1
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/bin.js +444 -0
- package/package.json +36 -0
package/bin.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
import { chmodSync, createWriteStream, existsSync, fsyncSync, mkdirSync, readFileSync, appendFileSync, renameSync, unlinkSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import { Readable } from "stream";
|
|
8
|
+
import { createInterface } from "readline";
|
|
9
|
+
|
|
10
|
+
const BASE_URL = "https://downloads.vikat.ai";
|
|
11
|
+
// Matches the migration CLI. Without a bound, a hung host hangs the install.
|
|
12
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
13
|
+
|
|
14
|
+
// Parse CLI version from command line arguments
|
|
15
|
+
function parseCliVersion() {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
let cliVersion = "latest"; // Default to latest
|
|
18
|
+
|
|
19
|
+
// Find --cli-version argument
|
|
20
|
+
const versionArgIndex = args.findIndex((arg) => arg.startsWith("--cli-version"));
|
|
21
|
+
|
|
22
|
+
if (versionArgIndex !== -1) {
|
|
23
|
+
const versionArg = args[versionArgIndex];
|
|
24
|
+
|
|
25
|
+
if (versionArg.includes("=")) {
|
|
26
|
+
// Format: --cli-version=v1.2.3
|
|
27
|
+
cliVersion = versionArg.split("=")[1];
|
|
28
|
+
if (!cliVersion) {
|
|
29
|
+
console.error("--cli-version requires a value");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
} else if (versionArgIndex + 1 < args.length) {
|
|
33
|
+
// Format: --cli-version v1.2.3
|
|
34
|
+
cliVersion = args[versionArgIndex + 1];
|
|
35
|
+
} else {
|
|
36
|
+
console.error("--cli-version requires a value");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return validateCliVersion(cliVersion);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Validate a CLI version and NORMALISE it to the form the download server uses.
|
|
45
|
+
//
|
|
46
|
+
// ─── THE BARE VERSION IS THE ONE ON THE SERVER ──────────────────────────────
|
|
47
|
+
//
|
|
48
|
+
// deploy/publish.sh uploads to s3://…/vikat-cli/$VERSION/… with VERSION exactly
|
|
49
|
+
// as release.sh cut it: bare, no "v". This function used to REQUIRE the "v"
|
|
50
|
+
// prefix and pass it straight through into the URL, so the only input form it
|
|
51
|
+
// accepted produced the one path that does not exist:
|
|
52
|
+
//
|
|
53
|
+
// /vikat-cli/v1.6.3/darwin/arm64/vikat -> 404
|
|
54
|
+
// /vikat-cli/1.6.3/darwin/arm64/vikat -> 200
|
|
55
|
+
//
|
|
56
|
+
// Both forms are accepted now, because "v1.7.0" is what a person types and what
|
|
57
|
+
// the git tag looks like, and the bare form is what S3 holds. The prefix is
|
|
58
|
+
// stripped here, once, so every caller below builds a URL that can resolve.
|
|
59
|
+
function validateCliVersion(version) {
|
|
60
|
+
if (version === "latest") {
|
|
61
|
+
return version;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Accept "v1.2.3" or "1.2.3", with an optional pre-release suffix.
|
|
65
|
+
const versionRegex = /^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/;
|
|
66
|
+
const match = versionRegex.exec(version);
|
|
67
|
+
if (match) {
|
|
68
|
+
return match[1]; // normalised: always bare
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.error(`Invalid CLI version format: ${version}`);
|
|
72
|
+
console.error(`CLI version must be "latest", "1.2.3", "v1.2.3", or "v1.2.3-prerelease1"`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const VERSION = parseCliVersion();
|
|
77
|
+
|
|
78
|
+
function getPlatformArchAndBinary() {
|
|
79
|
+
const platform = process.platform;
|
|
80
|
+
const arch = process.arch;
|
|
81
|
+
|
|
82
|
+
let platformDir;
|
|
83
|
+
let archDir;
|
|
84
|
+
let binaryName;
|
|
85
|
+
|
|
86
|
+
if (platform === "darwin") {
|
|
87
|
+
platformDir = "darwin";
|
|
88
|
+
if (arch === "arm64") archDir = "arm64";
|
|
89
|
+
else archDir = "amd64";
|
|
90
|
+
binaryName = "vikat";
|
|
91
|
+
} else if (platform === "linux") {
|
|
92
|
+
platformDir = "linux";
|
|
93
|
+
if (arch === "x64") archDir = "amd64";
|
|
94
|
+
else if (arch === "ia32") archDir = "386";
|
|
95
|
+
else archDir = arch; // fallback
|
|
96
|
+
binaryName = "vikat";
|
|
97
|
+
} else if (platform === "win32") {
|
|
98
|
+
platformDir = "windows";
|
|
99
|
+
if (arch === "x64") archDir = "amd64";
|
|
100
|
+
else if (arch === "ia32") archDir = "386";
|
|
101
|
+
else archDir = arch; // fallback
|
|
102
|
+
binaryName = "vikat.exe";
|
|
103
|
+
} else {
|
|
104
|
+
console.error(`Unsupported platform/arch: ${platform}/${arch}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { platformDir, archDir, binaryName };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function downloadBinary(url, dest) {
|
|
112
|
+
const res = await fetch(url);
|
|
113
|
+
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
console.error(`❌ Download failed: ${res.status} ${res.statusText}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const contentLength = res.headers.get("content-length");
|
|
120
|
+
const totalSize = contentLength ? parseInt(contentLength, 10) : null;
|
|
121
|
+
let downloadedSize = 0;
|
|
122
|
+
|
|
123
|
+
const fileStream = createWriteStream(dest, { flags: "w" });
|
|
124
|
+
await new Promise((resolve, reject) => {
|
|
125
|
+
try {
|
|
126
|
+
// Convert the fetch response body to a Node.js readable stream
|
|
127
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
128
|
+
|
|
129
|
+
// Add progress tracking
|
|
130
|
+
nodeStream.on("data", (chunk) => {
|
|
131
|
+
downloadedSize += chunk.length;
|
|
132
|
+
if (totalSize) {
|
|
133
|
+
const progress = ((downloadedSize / totalSize) * 100).toFixed(1);
|
|
134
|
+
process.stdout.write(`\r⏱️ Downloading Binary: ${progress}% (${formatBytes(downloadedSize)}/${formatBytes(totalSize)})`);
|
|
135
|
+
} else {
|
|
136
|
+
process.stdout.write(`\r⏱️ Downloaded: ${formatBytes(downloadedSize)}`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
nodeStream.pipe(fileStream);
|
|
141
|
+
fileStream.on("finish", () => {
|
|
142
|
+
process.stdout.write("\n");
|
|
143
|
+
|
|
144
|
+
// Ensure file is fully written to disk
|
|
145
|
+
try {
|
|
146
|
+
fsyncSync(fileStream.fd);
|
|
147
|
+
} catch (syncError) {
|
|
148
|
+
// fsync might fail on some systems, ignore
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
resolve();
|
|
152
|
+
});
|
|
153
|
+
fileStream.on("error", reject);
|
|
154
|
+
nodeStream.on("error", reject);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
reject(error);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
chmodSync(dest, 0o755);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check if a specific version exists on the download server
|
|
164
|
+
async function checkVersionExists(version, platformDir, archDir, binaryName) {
|
|
165
|
+
const url = `${BASE_URL}/vikat-cli/${version}/${platformDir}/${archDir}/${binaryName}`;
|
|
166
|
+
const res = await fetch(url, { method: "HEAD" });
|
|
167
|
+
return res.ok;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Resolve the "latest" pointer to a concrete version, then prove that version
|
|
171
|
+
// actually has a binary for this platform before returning it.
|
|
172
|
+
//
|
|
173
|
+
// The two-step matters. version.txt is written LAST by publish.sh, deliberately,
|
|
174
|
+
// so the binaries are in place before anything advertises them — but a partial
|
|
175
|
+
// upload, or a platform that was never built, still leaves the pointer naming a
|
|
176
|
+
// version with no artifact for THIS os/arch. Checking here turns that into a
|
|
177
|
+
// message naming the version and the platform, instead of a bare 404 later.
|
|
178
|
+
async function fetchLatestVersion(platformDir, archDir, binaryName) {
|
|
179
|
+
const url = `${BASE_URL}/vikat-cli/latest/version.txt`;
|
|
180
|
+
let res;
|
|
181
|
+
try {
|
|
182
|
+
res = await fetch(url);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error(`❌ Could not reach the download server: ${error.message}`);
|
|
185
|
+
console.error(` ${url}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
if (!res.ok) {
|
|
189
|
+
console.error(`❌ Could not resolve the latest CLI version (HTTP ${res.status}).`);
|
|
190
|
+
console.error(` ${url}`);
|
|
191
|
+
console.error(`Pass an explicit version instead, e.g. --cli-version 1.7.0`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Trimmed because publish.sh writes it with `printf '%s'` — no trailing
|
|
196
|
+
// newline today — but a hand-corrected pointer file would very likely have
|
|
197
|
+
// one, and a stray "\n" inside a URL path is a 404 nobody can read.
|
|
198
|
+
const raw = (await res.text()).trim();
|
|
199
|
+
const version = validateCliVersion(raw);
|
|
200
|
+
if (version === "latest") {
|
|
201
|
+
console.error(`❌ ${url} contains "latest", which would resolve to itself.`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!(await checkVersionExists(version, platformDir, archDir, binaryName))) {
|
|
206
|
+
console.error(`❌ Latest is ${version}, but no ${platformDir}/${archDir} binary was published for it.`);
|
|
207
|
+
console.error(` ${BASE_URL}/vikat-cli/${version}/${platformDir}/${archDir}/${binaryName}`);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
return version;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Verify the downloaded binary against its SHA-256 checksum.
|
|
214
|
+
//
|
|
215
|
+
// FAILS CLOSED. This warned and returned, and the caller then moved the
|
|
216
|
+
// unverified binary into ~/.vikat/bin — a directory the installer appends to the
|
|
217
|
+
// user's shell PATH. A check that can be skipped by declining to serve one file
|
|
218
|
+
// is not a check: anyone able to serve a malicious binary is equally able to
|
|
219
|
+
// withhold its sidecar. The sibling migration CLI already refused; this now
|
|
220
|
+
// matches it.
|
|
221
|
+
async function verifyChecksum(binaryPath, checksumUrl) {
|
|
222
|
+
let res;
|
|
223
|
+
try {
|
|
224
|
+
res = await fetch(checksumUrl, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
225
|
+
} catch (err) {
|
|
226
|
+
unlinkSync(binaryPath);
|
|
227
|
+
console.error(`❌ Could not fetch the checksum (${err.message}). Refusing to run an unverified binary.`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
if (!res.ok) {
|
|
231
|
+
unlinkSync(binaryPath);
|
|
232
|
+
console.error(`❌ Checksum file not available (${res.status}). Refusing to run an unverified binary.`);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const checksumContent = (await res.text()).trim();
|
|
237
|
+
// Format: "<hash> <filename>" (shasum output)
|
|
238
|
+
const expectedHash = checksumContent.split(/\s+/)[0];
|
|
239
|
+
if (!expectedHash) {
|
|
240
|
+
console.warn("⚠️ Could not parse checksum file, skipping verification");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const fileBuffer = readFileSync(binaryPath);
|
|
245
|
+
const actualHash = createHash("sha256").update(fileBuffer).digest("hex");
|
|
246
|
+
|
|
247
|
+
if (actualHash !== expectedHash) {
|
|
248
|
+
const { unlinkSync } = await import("fs");
|
|
249
|
+
unlinkSync(binaryPath);
|
|
250
|
+
console.error(`❌ Checksum verification failed!`);
|
|
251
|
+
console.error(` Expected: ${expectedHash}`);
|
|
252
|
+
console.error(` Got: ${actualHash}`);
|
|
253
|
+
console.error(` The downloaded binary has been deleted for safety.`);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
console.log("✅ Checksum verified");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function formatBytes(bytes) {
|
|
261
|
+
if (bytes === 0) return "0 B";
|
|
262
|
+
const k = 1024;
|
|
263
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
264
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
265
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Detect the user's shell and return the RC file path and the PATH export line
|
|
269
|
+
function getShellConfig() {
|
|
270
|
+
const home = homedir();
|
|
271
|
+
const shell = (process.env.SHELL || "").toLowerCase();
|
|
272
|
+
|
|
273
|
+
if (process.platform === "win32") {
|
|
274
|
+
return null; // Windows — manual PATH setup
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (shell.endsWith("/fish") || shell.endsWith("/fish.exe")) {
|
|
278
|
+
return {
|
|
279
|
+
rcFile: join(home, ".config", "fish", "config.fish"),
|
|
280
|
+
exportLine: "fish_add_path $HOME/.vikat/bin",
|
|
281
|
+
shellName: "fish",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (shell.endsWith("/zsh")) {
|
|
286
|
+
return {
|
|
287
|
+
rcFile: join(home, ".zshrc"),
|
|
288
|
+
exportLine: 'export PATH="$HOME/.vikat/bin:$PATH"',
|
|
289
|
+
shellName: "zsh",
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (shell.endsWith("/bash") || shell.endsWith("/bash.exe")) {
|
|
294
|
+
const rcFiles =
|
|
295
|
+
process.platform === "darwin"
|
|
296
|
+
? [join(home, ".bash_profile"), join(home, ".bashrc")]
|
|
297
|
+
: [join(home, ".bashrc")];
|
|
298
|
+
return {
|
|
299
|
+
rcFile: rcFiles[0],
|
|
300
|
+
extraRcFiles: rcFiles.slice(1),
|
|
301
|
+
exportLine: 'export PATH="$HOME/.vikat/bin:$PATH"',
|
|
302
|
+
shellName: "bash",
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Check if the PATH export line is already present in the given file
|
|
310
|
+
function hasPathLine(filePath) {
|
|
311
|
+
if (!existsSync(filePath)) return false;
|
|
312
|
+
try {
|
|
313
|
+
const content = readFileSync(filePath, "utf-8");
|
|
314
|
+
return content.includes(".vikat/bin");
|
|
315
|
+
} catch {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Prompt the user with a yes/no question
|
|
321
|
+
async function promptYesNo(question) {
|
|
322
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
326
|
+
return new Promise((resolve) => {
|
|
327
|
+
rl.question(question, (answer) => {
|
|
328
|
+
rl.close();
|
|
329
|
+
const normalized = (answer || "").trim().toLowerCase();
|
|
330
|
+
resolve(normalized === "" || normalized === "y" || normalized === "yes");
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function printStartMessage(message) {
|
|
336
|
+
console.log(`\n${message}`);
|
|
337
|
+
console.log(`Enter 'vikat' when you're ready to start the CLI.`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function main() {
|
|
341
|
+
const { platformDir, archDir, binaryName } = getPlatformArchAndBinary();
|
|
342
|
+
|
|
343
|
+
let namedVersion;
|
|
344
|
+
|
|
345
|
+
if (VERSION === "latest") {
|
|
346
|
+
// ─── "latest" IS A POINTER FILE, NOT A DIRECTORY ────────────────────
|
|
347
|
+
//
|
|
348
|
+
// This used to HEAD /vikat-cli/latest/<os>/<arch>/vikat and, when that
|
|
349
|
+
// 404'd, tell the user to pass --cli-version. It 404s always:
|
|
350
|
+
// deploy/publish.sh writes exactly ONE object under that prefix,
|
|
351
|
+
// vikat-cli/latest/version.txt (publish.sh:250), and puts the binaries
|
|
352
|
+
// under /vikat-cli/$VERSION/ (publish.sh:237). So the documented install
|
|
353
|
+
// — `npx @vikat/vikat-gateway-cli` — could never work, and neither could
|
|
354
|
+
// the workaround it printed.
|
|
355
|
+
//
|
|
356
|
+
// Resolve the pointer first, then fetch the concrete version. That is
|
|
357
|
+
// exactly what the Go self-updater does (cli/internal/update/check.go
|
|
358
|
+
// reads the same version.txt), so the two installers now agree instead
|
|
359
|
+
// of disagreeing.
|
|
360
|
+
namedVersion = await fetchLatestVersion(platformDir, archDir, binaryName);
|
|
361
|
+
} else {
|
|
362
|
+
// For explicitly specified versions, verify it exists on the server
|
|
363
|
+
const versionExists = await checkVersionExists(VERSION, platformDir, archDir, binaryName);
|
|
364
|
+
if (!versionExists) {
|
|
365
|
+
console.error(`❌ CLI version '${VERSION}' not found.`);
|
|
366
|
+
console.error(`Please verify the version exists at: ${BASE_URL}/vikat-cli/`);
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
namedVersion = VERSION;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const downloadUrl = `${BASE_URL}/vikat-cli/${namedVersion}/${platformDir}/${archDir}/${binaryName}`;
|
|
373
|
+
|
|
374
|
+
// Install to ~/.vikat/bin/
|
|
375
|
+
const installDir = join(homedir(), ".vikat", "bin");
|
|
376
|
+
mkdirSync(installDir, { recursive: true });
|
|
377
|
+
const binaryPath = join(installDir, binaryName);
|
|
378
|
+
|
|
379
|
+
// Download to a temp file, verify, then atomically replace
|
|
380
|
+
const tempBinaryPath = `${binaryPath}.download-${process.pid}-${Date.now()}`;
|
|
381
|
+
try {
|
|
382
|
+
await downloadBinary(downloadUrl, tempBinaryPath);
|
|
383
|
+
|
|
384
|
+
const checksumUrl = `${BASE_URL}/vikat-cli/${namedVersion}/${platformDir}/${archDir}/${binaryName}.sha256`;
|
|
385
|
+
await verifyChecksum(tempBinaryPath, checksumUrl);
|
|
386
|
+
renameSync(tempBinaryPath, binaryPath);
|
|
387
|
+
} catch (err) {
|
|
388
|
+
try { unlinkSync(tempBinaryPath); } catch {}
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
391
|
+
console.log(`✅ Installed vikat to ${binaryPath}`);
|
|
392
|
+
|
|
393
|
+
// Shell PATH setup
|
|
394
|
+
const shellConfig = getShellConfig();
|
|
395
|
+
|
|
396
|
+
if (!shellConfig) {
|
|
397
|
+
// Windows — print manual instructions
|
|
398
|
+
console.log(`\nTo complete installation, add the following directory to your PATH:`);
|
|
399
|
+
console.log(` ${installDir}`);
|
|
400
|
+
console.log(`\nYou can do this in System Settings > Environment Variables.`);
|
|
401
|
+
printStartMessage("The installer won't start Vikat automatically.");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Check if PATH is already configured
|
|
406
|
+
const allRcFiles = [shellConfig.rcFile, ...(shellConfig.extraRcFiles || [])];
|
|
407
|
+
const missingRcFiles = allRcFiles.filter((rcFile) => !hasPathLine(rcFile));
|
|
408
|
+
|
|
409
|
+
if (missingRcFiles.length === 0) {
|
|
410
|
+
console.log(`\n✅ PATH already configured for vikat.`);
|
|
411
|
+
printStartMessage("The installer won't start Vikat automatically.");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Prompt user to add PATH
|
|
416
|
+
const rcDisplayName = shellConfig.rcFile.startsWith(homedir()) ? shellConfig.rcFile.slice(homedir().length + 1) : shellConfig.rcFile.split("/").pop();
|
|
417
|
+
const shouldAdd = await promptYesNo(`\nAdd vikat to your PATH in ~/${rcDisplayName}? [Y/n] `);
|
|
418
|
+
|
|
419
|
+
if (!shouldAdd) {
|
|
420
|
+
console.log(`\nSkipped PATH setup. You can manually add this to your shell config:`);
|
|
421
|
+
console.log(` ${shellConfig.exportLine}`);
|
|
422
|
+
printStartMessage("After updating your PATH, start Vikat manually.");
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Append the export line to RC file(s)
|
|
427
|
+
for (const rcFile of missingRcFiles) {
|
|
428
|
+
try {
|
|
429
|
+
mkdirSync(dirname(rcFile), { recursive: true });
|
|
430
|
+
appendFileSync(rcFile, `\n# Added by vikat installer\n${shellConfig.exportLine}\n`);
|
|
431
|
+
console.log(`✅ Added PATH to ${rcFile}`);
|
|
432
|
+
} catch (err) {
|
|
433
|
+
console.error(`⚠️ Failed to update ${rcFile}: ${err.message}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const rcRelative = shellConfig.rcFile.startsWith(homedir()) ? shellConfig.rcFile.slice(homedir().length + 1) : shellConfig.rcFile.split("/").pop();
|
|
438
|
+
printStartMessage(`Run 'source ~/${rcRelative}' or open a new terminal first.`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
main().catch((error) => {
|
|
442
|
+
console.error(`❌ Failed to install Vikat CLI: ${error.message}`);
|
|
443
|
+
process.exit(1);
|
|
444
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vikat/vikat-gateway-cli",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Vikat CLI - Use any harness + any models through Vikat gateway with full observability and governance",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"gateway",
|
|
8
|
+
"openai",
|
|
9
|
+
"anthropic",
|
|
10
|
+
"cli",
|
|
11
|
+
"vikat",
|
|
12
|
+
"ai-gateway"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://vikat.local",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/Vikat-AI/vikat-gateway.git",
|
|
18
|
+
"directory": "npx/vikat-cli"
|
|
19
|
+
},
|
|
20
|
+
"license": "Apache-2.0",
|
|
21
|
+
"author": "Vikat",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"vikat": "bin.js"
|
|
30
|
+
},
|
|
31
|
+
"type": "module",
|
|
32
|
+
"dependencies": {},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin.js"
|
|
35
|
+
]
|
|
36
|
+
}
|