nvicode 0.1.9 → 0.1.11
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 +9 -79
- package/dist/cli.js +123 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,100 +1,30 @@
|
|
|
1
|
-
# NviCode
|
|
1
|
+
# NviCode
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Use top open-source model APIs on NVIDIA Build for free, with `nvicode` paced to `40 RPM` by default.
|
|
3
|
+
Connect Claude Code to NVIDIA or OpenRouter.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
- macOS
|
|
8
|
-
- Ubuntu/Linux
|
|
9
|
-
- WSL
|
|
10
|
-
- Native Windows with Claude Code installed and working from PowerShell, CMD, or Git Bash
|
|
11
|
-
|
|
12
|
-
## Quickstart
|
|
13
|
-
|
|
14
|
-
Install the published package:
|
|
5
|
+
## Install
|
|
15
6
|
|
|
16
7
|
```sh
|
|
17
8
|
npm install -g nvicode
|
|
18
9
|
```
|
|
19
10
|
|
|
20
|
-
|
|
21
|
-
If you skip it, run:
|
|
11
|
+
## Setup
|
|
22
12
|
|
|
23
13
|
```sh
|
|
24
14
|
nvicode select model
|
|
25
15
|
```
|
|
26
16
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- NVIDIA: get a free key from [NVIDIA Build API Keys](https://build.nvidia.com/settings/api-keys)
|
|
30
|
-
- OpenRouter: use your OpenRouter API key
|
|
17
|
+
NVIDIA keys: [https://build.nvidia.com/settings/api-keys](https://build.nvidia.com/settings/api-keys)
|
|
31
18
|
|
|
32
|
-
Launch
|
|
19
|
+
## Launch
|
|
33
20
|
|
|
34
21
|
```sh
|
|
35
22
|
nvicode launch claude
|
|
36
23
|
```
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
### Save your API key
|
|
41
|
-
|
|
42
|
-

|
|
43
|
-
|
|
44
|
-
### Choose a model
|
|
45
|
-
|
|
46
|
-

|
|
47
|
-
|
|
48
|
-
### Launch Claude Code through your selected provider
|
|
49
|
-
|
|
50
|
-

|
|
51
|
-
|
|
52
|
-
## Commands
|
|
53
|
-
|
|
54
|
-
Useful commands:
|
|
55
|
-
|
|
56
|
-
```sh
|
|
57
|
-
nvicode dashboard
|
|
58
|
-
nvicode usage
|
|
59
|
-
nvicode activity
|
|
60
|
-
nvicode models
|
|
61
|
-
nvicode config
|
|
62
|
-
nvicode auth
|
|
63
|
-
nvicode launch claude -p "Reply with exactly OK"
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Provider behavior:
|
|
67
|
-
- NVIDIA: starts a local proxy on `127.0.0.1:8788`, points Claude Code at it with `ANTHROPIC_BASE_URL`, and forwards requests to NVIDIA `chat/completions`.
|
|
68
|
-
- OpenRouter: points Claude Code directly at `https://openrouter.ai/api` using OpenRouter credentials and Anthropic-compatible model ids.
|
|
69
|
-
|
|
70
|
-
In an interactive terminal, `nvicode usage` refreshes live every 2 seconds. When piped or redirected, it prints a single snapshot.
|
|
71
|
-
|
|
72
|
-
`nvicode select model` now asks for provider, optional API key update, and model choice in one guided flow.
|
|
73
|
-
If no API key is saved for the active provider yet, `nvicode` prompts for one on first use.
|
|
74
|
-
By default, the proxy paces upstream NVIDIA requests at `40 RPM`. Override that with `NVICODE_MAX_RPM` if your account has a different limit.
|
|
75
|
-
The usage dashboard compares your local NVIDIA run cost against Claude Opus 4.6 at `$5 / MTok input` and `$25 / MTok output`, based on Anthropic pricing as of `2026-03-30`.
|
|
76
|
-
If your NVIDIA endpoint is not free, override local cost estimates with `NVICODE_INPUT_USD_PER_MTOK` and `NVICODE_OUTPUT_USD_PER_MTOK`.
|
|
77
|
-
Local `usage`, `activity`, and `dashboard` commands are available for NVIDIA proxy sessions. OpenRouter sessions use OpenRouter's direct connection path instead.
|
|
25
|
+
After the first successful launch, plain `claude` continues to use the selected `nvicode` provider and model.
|
|
78
26
|
|
|
79
27
|
## Requirements
|
|
80
28
|
|
|
81
|
-
- Claude Code must already be installed
|
|
82
|
-
- Node.js 20 or newer
|
|
83
|
-
- On native Windows, Claude Code itself requires Git for Windows. See the [Claude Code setup docs](https://code.claude.com/docs/en/setup).
|
|
84
|
-
|
|
85
|
-
## Local Development
|
|
86
|
-
|
|
87
|
-
These steps are only for contributors working from a git checkout. End users do not need them.
|
|
88
|
-
|
|
89
|
-
```sh
|
|
90
|
-
npm install
|
|
91
|
-
npm run build
|
|
92
|
-
npm link
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Notes
|
|
96
|
-
|
|
97
|
-
- `thinking` is disabled by default because some NVIDIA reasoning models can consume the entire output budget and return no visible answer to Claude Code.
|
|
98
|
-
- The proxy supports basic text, tool calls, tool results, and token count estimation.
|
|
99
|
-
- The proxy includes upstream request pacing and retries on NVIDIA `429` responses.
|
|
100
|
-
- Claude Code remains the frontend; the selected provider/model becomes the backend.
|
|
29
|
+
- Claude Code must already be installed
|
|
30
|
+
- Node.js 20 or newer
|
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
|
|
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
|
|
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) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nvicode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "NviCode - Introducing one click Nvidia/OpenRouter keys to Claude Code. Free Claude code.",
|
|
5
5
|
"author": "Dinesh Potla",
|
|
6
6
|
"keywords": [
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"nvicode": "dist/cli.js"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true,maxRetries:10,retryDelay:100})\" && tsc -p tsconfig.json",
|
|
21
21
|
"prepack": "npm run build",
|
|
22
22
|
"postinstall": "node dist/postinstall.js",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|