nvicode 0.1.7 → 0.1.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/README.md CHANGED
@@ -19,19 +19,30 @@ npm install -g nvicode
19
19
 
20
20
  Set up provider, key, and model:
21
21
 
22
- - NVIDIA: get a free key from [NVIDIA Build API Keys](https://build.nvidia.com/settings/api-keys)
23
- - OpenRouter: use your OpenRouter API key
24
-
25
22
  ```sh
26
23
  nvicode select model
27
24
  ```
28
25
 
26
+ The setup flow asks for provider, API key, and model:
27
+
28
+ - NVIDIA: get a free key from [NVIDIA Build API Keys](https://build.nvidia.com/settings/api-keys)
29
+ - OpenRouter: use your OpenRouter API key
30
+
29
31
  Launch Claude Code through your selected provider:
30
32
 
31
33
  ```sh
32
34
  nvicode launch claude
33
35
  ```
34
36
 
37
+ The first successful `nvicode launch claude` also installs persistent plain-`claude` routing.
38
+ After that, restarting your terminal or PC and running:
39
+
40
+ ```sh
41
+ claude
42
+ ```
43
+
44
+ will continue using your selected `nvicode` provider and model.
45
+
35
46
  ## Screenshots
36
47
 
37
48
  ### Save your API key
package/dist/cli.js CHANGED
@@ -12,6 +12,7 @@ import { createProxyServer } from "./proxy.js";
12
12
  import { getRecommendedModels } from "./models.js";
13
13
  import { filterRecordsSince, formatDuration, formatInteger, formatTimestamp, formatUsd, readUsageRecords, summarizeUsage, } from "./usage.js";
14
14
  const __filename = fileURLToPath(import.meta.url);
15
+ const NVICODE_WRAPPER_MARKER = "managed by nvicode";
15
16
  const usage = () => {
16
17
  console.log(`nvicode
17
18
 
@@ -41,6 +42,47 @@ const getPathExts = () => {
41
42
  };
42
43
  const unique = (values) => [...new Set(values)];
43
44
  const getProviderLabel = (provider) => provider === "openrouter" ? "OpenRouter" : "NVIDIA";
45
+ const getClaudeCommandNames = () => isWindows ? ["claude.exe", "claude.cmd", "claude.bat", "claude"] : ["claude"];
46
+ const getClaudeNativeNames = () => isWindows
47
+ ? ["claude-native.exe", "claude-native.cmd", "claude-native.bat", "claude-native"]
48
+ : ["claude-native"];
49
+ const pathExists = async (targetPath) => {
50
+ try {
51
+ await fs.access(targetPath, constants.F_OK);
52
+ return true;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ };
58
+ const readIfExists = async (targetPath) => {
59
+ try {
60
+ return await fs.readFile(targetPath, "utf8");
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ };
66
+ const isManagedClaudeWrapper = async (targetPath) => {
67
+ const contents = await readIfExists(targetPath);
68
+ return contents?.includes(NVICODE_WRAPPER_MARKER) ?? false;
69
+ };
70
+ const renderClaudeWrapper = () => {
71
+ if (isWindows) {
72
+ return [
73
+ "@echo off",
74
+ `REM ${NVICODE_WRAPPER_MARKER}`,
75
+ `"${process.execPath}" "${__filename}" launch claude %*`,
76
+ "",
77
+ ].join("\r\n");
78
+ }
79
+ return [
80
+ "#!/bin/sh",
81
+ `# ${NVICODE_WRAPPER_MARKER}`,
82
+ `exec "${process.execPath}" "${__filename}" launch claude "$@"`,
83
+ "",
84
+ ].join("\n");
85
+ };
44
86
  const question = async (prompt) => {
45
87
  const rl = createInterface({
46
88
  input: process.stdin,
@@ -424,11 +466,81 @@ const resolveClaudeVersionEntry = async (entryPath) => {
424
466
  }
425
467
  return null;
426
468
  };
469
+ const findExistingClaudeNativeInDirectory = async (directory) => {
470
+ for (const name of getClaudeNativeNames()) {
471
+ const candidate = path.join(directory, name);
472
+ if (await isExecutable(candidate)) {
473
+ return candidate;
474
+ }
475
+ }
476
+ return null;
477
+ };
478
+ const resolvePersistentClaudeCommand = async () => {
479
+ for (const name of getClaudeCommandNames()) {
480
+ const found = await findExecutableInPath(name);
481
+ if (found) {
482
+ return found;
483
+ }
484
+ }
485
+ return null;
486
+ };
487
+ const getWrapperInstallPaths = async (claudeCommandPath) => {
488
+ const directory = path.dirname(claudeCommandPath);
489
+ const existingNative = await findExistingClaudeNativeInDirectory(directory);
490
+ if (existingNative) {
491
+ return {
492
+ wrapperPath: claudeCommandPath,
493
+ nativePath: existingNative,
494
+ };
495
+ }
496
+ if (isWindows && path.extname(claudeCommandPath).toLowerCase() === ".exe") {
497
+ return {
498
+ wrapperPath: path.join(directory, "claude.cmd"),
499
+ nativePath: path.join(directory, "claude-native.exe"),
500
+ };
501
+ }
502
+ const extension = path.extname(claudeCommandPath);
503
+ return {
504
+ wrapperPath: claudeCommandPath,
505
+ nativePath: path.join(directory, `claude-native${extension}`),
506
+ };
507
+ };
508
+ const writeExecutableTextFile = async (targetPath, contents) => {
509
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
510
+ await fs.writeFile(targetPath, contents, "utf8");
511
+ if (!isWindows) {
512
+ await fs.chmod(targetPath, 0o755);
513
+ }
514
+ };
515
+ const ensurePersistentClaudeRouting = async () => {
516
+ const claudeCommandPath = await resolvePersistentClaudeCommand();
517
+ if (!claudeCommandPath) {
518
+ return "skipped";
519
+ }
520
+ const wrapperContents = renderClaudeWrapper();
521
+ const { wrapperPath, nativePath } = await getWrapperInstallPaths(claudeCommandPath);
522
+ if (await isManagedClaudeWrapper(wrapperPath)) {
523
+ const currentWrapper = await readIfExists(wrapperPath);
524
+ if (currentWrapper === wrapperContents) {
525
+ return "already";
526
+ }
527
+ await writeExecutableTextFile(wrapperPath, wrapperContents);
528
+ return "updated";
529
+ }
530
+ if (!(await pathExists(nativePath))) {
531
+ await fs.rename(claudeCommandPath, nativePath);
532
+ }
533
+ else if (claudeCommandPath !== wrapperPath && await pathExists(wrapperPath)) {
534
+ await fs.rm(wrapperPath, { force: true });
535
+ }
536
+ else if (claudeCommandPath === wrapperPath) {
537
+ await fs.rm(wrapperPath, { force: true });
538
+ }
539
+ await writeExecutableTextFile(wrapperPath, wrapperContents);
540
+ return "installed";
541
+ };
427
542
  const resolveClaudeBinary = async () => {
428
- const nativeNames = isWindows
429
- ? ["claude-native.exe", "claude-native.cmd", "claude-native.bat", "claude-native"]
430
- : ["claude-native"];
431
- for (const name of nativeNames) {
543
+ for (const name of getClaudeNativeNames()) {
432
544
  const nativeInPath = await findExecutableInPath(name);
433
545
  if (nativeInPath) {
434
546
  return nativeInPath;
@@ -467,12 +579,9 @@ const resolveClaudeBinary = async () => {
467
579
  catch {
468
580
  // continue
469
581
  }
470
- const cliNames = isWindows
471
- ? ["claude.exe", "claude.cmd", "claude.bat", "claude"]
472
- : ["claude"];
473
- for (const name of cliNames) {
582
+ for (const name of getClaudeCommandNames()) {
474
583
  const claudeInPath = await findExecutableInPath(name);
475
- if (claudeInPath) {
584
+ if (claudeInPath && !(await isManagedClaudeWrapper(claudeInPath))) {
476
585
  return claudeInPath;
477
586
  }
478
587
  }
@@ -509,6 +618,7 @@ const spawnClaudeProcess = (claudeBinary, args, env) => {
509
618
  };
510
619
  const runLaunchClaude = async (args) => {
511
620
  const config = await ensureConfigured();
621
+ const routingStatus = await ensurePersistentClaudeRouting().catch(() => "skipped");
512
622
  const claudeBinary = await resolveClaudeBinary();
513
623
  const activeModel = getActiveModel(config);
514
624
  const activeApiKey = getActiveApiKey(config);
@@ -541,6 +651,10 @@ const runLaunchClaude = async (args) => {
541
651
  if (config.provider === "nvidia") {
542
652
  await ensureProxyRunning(config);
543
653
  }
654
+ if (process.stdout.isTTY &&
655
+ (routingStatus === "installed" || routingStatus === "updated")) {
656
+ console.error("nvicode installed persistent `claude` routing. Future plain `claude` launches will use the selected nvicode provider and model.");
657
+ }
544
658
  const child = spawnClaudeProcess(claudeBinary, args, env);
545
659
  await new Promise((resolve, reject) => {
546
660
  child.on("exit", (code, signal) => {
@@ -0,0 +1,46 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { loadConfig } from "./config.js";
6
+ const isTruthy = (value) => value === "1" || value === "true" || value === "yes";
7
+ const isGlobalInstall = () => process.env.npm_config_global === "true" ||
8
+ process.env.npm_config_location === "global";
9
+ const isInteractive = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
10
+ const main = async () => {
11
+ if (isTruthy(process.env.NVICODE_SKIP_POSTINSTALL) || isTruthy(process.env.CI)) {
12
+ return;
13
+ }
14
+ if (!isGlobalInstall()) {
15
+ return;
16
+ }
17
+ if (!isInteractive()) {
18
+ console.log("nvicode installed. Run `nvicode select model` to finish setup.");
19
+ return;
20
+ }
21
+ const config = await loadConfig();
22
+ if (config.nvidiaApiKey || config.openrouterApiKey) {
23
+ console.log("nvicode is already configured. Run `nvicode select model` to change provider, key, or model.");
24
+ return;
25
+ }
26
+ console.log("");
27
+ console.log("nvicode installed. Starting guided setup.");
28
+ console.log("If you want to skip now, press Ctrl+C and run `nvicode select model` later.");
29
+ console.log("");
30
+ const cliPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "cli.js");
31
+ const result = spawnSync(process.execPath, [cliPath, "select", "model"], {
32
+ stdio: "inherit",
33
+ env: {
34
+ ...process.env,
35
+ NVICODE_FROM_POSTINSTALL: "1",
36
+ },
37
+ });
38
+ if (result.error || result.status !== 0) {
39
+ console.log("");
40
+ console.log("nvicode setup was skipped or did not complete.");
41
+ console.log("Run `nvicode select model` any time to finish setup.");
42
+ }
43
+ };
44
+ void main().catch(() => {
45
+ console.log("nvicode installed. Run `nvicode select model` to finish setup.");
46
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nvicode",
3
- "version": "0.1.7",
3
+ "version": "0.1.10",
4
4
  "description": "NviCode - Introducing one click Nvidia/OpenRouter keys to Claude Code. Free Claude code.",
5
5
  "author": "Dinesh Potla",
6
6
  "keywords": [
@@ -19,6 +19,7 @@
19
19
  "scripts": {
20
20
  "build": "tsc -p tsconfig.json",
21
21
  "prepack": "npm run build",
22
+ "postinstall": "node dist/postinstall.js",
22
23
  "typecheck": "tsc --noEmit",
23
24
  "dev": "tsx src/cli.ts"
24
25
  },