claude-rpc 0.3.8
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/LICENSE +21 -0
- package/README.md +300 -0
- package/bin/claude-rpc.js +2 -0
- package/config.example.json +67 -0
- package/package.json +53 -0
- package/src/badge.js +144 -0
- package/src/cli.js +765 -0
- package/src/daemon.js +324 -0
- package/src/default-config.js +91 -0
- package/src/format.js +657 -0
- package/src/git.js +74 -0
- package/src/hook.js +169 -0
- package/src/insights.js +138 -0
- package/src/install.js +280 -0
- package/src/languages.js +114 -0
- package/src/paths.js +59 -0
- package/src/pricing.js +73 -0
- package/src/scanner.js +721 -0
- package/src/server.js +1584 -0
- package/src/state.js +73 -0
- package/src/tui.js +420 -0
package/src/languages.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// File extension → language map. Used by the scanner to bucket edits by
|
|
2
|
+
// language and by the dashboards to show per-language totals.
|
|
3
|
+
|
|
4
|
+
const EXT_TO_LANG = {
|
|
5
|
+
// Web / JS
|
|
6
|
+
'.js': 'JavaScript', '.mjs': 'JavaScript', '.cjs': 'JavaScript',
|
|
7
|
+
'.jsx': 'JavaScript',
|
|
8
|
+
'.ts': 'TypeScript', '.tsx': 'TypeScript', '.mts': 'TypeScript', '.cts': 'TypeScript',
|
|
9
|
+
'.vue': 'Vue', '.svelte': 'Svelte',
|
|
10
|
+
'.html': 'HTML', '.htm': 'HTML',
|
|
11
|
+
'.css': 'CSS', '.scss': 'SCSS', '.sass': 'Sass', '.less': 'Less',
|
|
12
|
+
'.json': 'JSON', '.jsonc': 'JSON', '.json5': 'JSON',
|
|
13
|
+
|
|
14
|
+
// Backend / systems
|
|
15
|
+
'.py': 'Python', '.pyi': 'Python', '.pyw': 'Python',
|
|
16
|
+
'.rb': 'Ruby',
|
|
17
|
+
'.go': 'Go',
|
|
18
|
+
'.rs': 'Rust',
|
|
19
|
+
'.java': 'Java', '.kt': 'Kotlin', '.kts': 'Kotlin', '.scala': 'Scala', '.groovy': 'Groovy',
|
|
20
|
+
'.c': 'C', '.h': 'C',
|
|
21
|
+
'.cpp': 'C++', '.cc': 'C++', '.cxx': 'C++', '.hpp': 'C++', '.hh': 'C++', '.hxx': 'C++',
|
|
22
|
+
'.cs': 'C#',
|
|
23
|
+
'.php': 'PHP',
|
|
24
|
+
'.swift': 'Swift',
|
|
25
|
+
'.m': 'Objective-C', '.mm': 'Objective-C++',
|
|
26
|
+
'.zig': 'Zig',
|
|
27
|
+
'.lua': 'Lua',
|
|
28
|
+
'.dart': 'Dart',
|
|
29
|
+
'.elm': 'Elm',
|
|
30
|
+
'.ex': 'Elixir', '.exs': 'Elixir',
|
|
31
|
+
'.erl': 'Erlang',
|
|
32
|
+
'.hs': 'Haskell',
|
|
33
|
+
'.ml': 'OCaml', '.mli': 'OCaml',
|
|
34
|
+
'.clj': 'Clojure', '.cljs': 'ClojureScript',
|
|
35
|
+
'.r': 'R', '.R': 'R',
|
|
36
|
+
'.jl': 'Julia',
|
|
37
|
+
'.nim': 'Nim',
|
|
38
|
+
'.cr': 'Crystal',
|
|
39
|
+
'.v': 'V',
|
|
40
|
+
|
|
41
|
+
// Shell / config
|
|
42
|
+
'.sh': 'Shell', '.bash': 'Shell', '.zsh': 'Shell', '.fish': 'Shell',
|
|
43
|
+
'.ps1': 'PowerShell', '.psm1': 'PowerShell',
|
|
44
|
+
'.bat': 'Batch', '.cmd': 'Batch',
|
|
45
|
+
'.yml': 'YAML', '.yaml': 'YAML',
|
|
46
|
+
'.toml': 'TOML',
|
|
47
|
+
'.ini': 'INI', '.cfg': 'INI', '.conf': 'INI',
|
|
48
|
+
'.env': 'Env',
|
|
49
|
+
'.xml': 'XML',
|
|
50
|
+
'.dockerfile': 'Dockerfile',
|
|
51
|
+
|
|
52
|
+
// Docs
|
|
53
|
+
'.md': 'Markdown', '.mdx': 'Markdown', '.markdown': 'Markdown',
|
|
54
|
+
'.rst': 'reStructuredText',
|
|
55
|
+
'.tex': 'LaTeX',
|
|
56
|
+
'.txt': 'Text',
|
|
57
|
+
|
|
58
|
+
// Data / queries
|
|
59
|
+
'.sql': 'SQL',
|
|
60
|
+
'.graphql': 'GraphQL', '.gql': 'GraphQL',
|
|
61
|
+
'.proto': 'Protobuf',
|
|
62
|
+
|
|
63
|
+
// Notebooks
|
|
64
|
+
'.ipynb': 'Notebook',
|
|
65
|
+
|
|
66
|
+
// Build / lock
|
|
67
|
+
'.lock': 'Lockfile',
|
|
68
|
+
'.gradle': 'Gradle',
|
|
69
|
+
'.cmake': 'CMake',
|
|
70
|
+
'.make': 'Make',
|
|
71
|
+
'.mk': 'Make',
|
|
72
|
+
|
|
73
|
+
// Mobile / native
|
|
74
|
+
'.xib': 'Interface Builder',
|
|
75
|
+
'.storyboard': 'Interface Builder',
|
|
76
|
+
|
|
77
|
+
// Misc
|
|
78
|
+
'.gitignore': 'Git',
|
|
79
|
+
'.gitattributes': 'Git',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Filenames without extension that we still want to classify.
|
|
83
|
+
const FILENAME_TO_LANG = {
|
|
84
|
+
'Dockerfile': 'Dockerfile',
|
|
85
|
+
'Makefile': 'Make',
|
|
86
|
+
'GNUmakefile': 'Make',
|
|
87
|
+
'Rakefile': 'Ruby',
|
|
88
|
+
'Gemfile': 'Ruby',
|
|
89
|
+
'Procfile': 'Config',
|
|
90
|
+
'CMakeLists.txt': 'CMake',
|
|
91
|
+
'package.json': 'JSON',
|
|
92
|
+
'tsconfig.json': 'JSON',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function fileBasename(path) {
|
|
96
|
+
const norm = String(path || '').replace(/\\/g, '/');
|
|
97
|
+
const idx = norm.lastIndexOf('/');
|
|
98
|
+
return idx === -1 ? norm : norm.slice(idx + 1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function fileExt(path) {
|
|
102
|
+
const base = fileBasename(path);
|
|
103
|
+
const idx = base.lastIndexOf('.');
|
|
104
|
+
if (idx <= 0) return '';
|
|
105
|
+
return base.slice(idx).toLowerCase();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function languageOf(path) {
|
|
109
|
+
if (!path) return null;
|
|
110
|
+
const base = fileBasename(path);
|
|
111
|
+
if (FILENAME_TO_LANG[base]) return FILENAME_TO_LANG[base];
|
|
112
|
+
const ext = fileExt(path);
|
|
113
|
+
return EXT_TO_LANG[ext] || null;
|
|
114
|
+
}
|
package/src/paths.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { homedir, tmpdir } from 'node:os';
|
|
2
|
+
import { join, dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
// Detect packaged mode. Covers both pkg (process.pkg) and Node SEA (where
|
|
8
|
+
// process.execPath is the renamed exe rather than node/node.exe).
|
|
9
|
+
export const IS_PACKAGED = typeof process.pkg !== 'undefined'
|
|
10
|
+
|| !/[\\/]node(\.exe)?$/i.test(process.execPath || '');
|
|
11
|
+
|
|
12
|
+
export const ROOT = resolve(__dirname, '..');
|
|
13
|
+
|
|
14
|
+
// In packaged mode, persist user config in the per-OS app-data directory.
|
|
15
|
+
// In dev mode, keep config.json next to the source tree for easy iteration.
|
|
16
|
+
// Windows: %APPDATA%\claude-rpc\
|
|
17
|
+
// macOS: ~/Library/Application Support/claude-rpc/
|
|
18
|
+
// Linux: $XDG_CONFIG_HOME/claude-rpc/ (default ~/.config/claude-rpc/)
|
|
19
|
+
function userConfigDir() {
|
|
20
|
+
if (process.platform === 'win32') {
|
|
21
|
+
const appdata = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');
|
|
22
|
+
return join(appdata, 'claude-rpc');
|
|
23
|
+
}
|
|
24
|
+
if (process.platform === 'darwin') {
|
|
25
|
+
return join(homedir(), 'Library', 'Application Support', 'claude-rpc');
|
|
26
|
+
}
|
|
27
|
+
const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
28
|
+
return join(xdg, 'claude-rpc');
|
|
29
|
+
}
|
|
30
|
+
export const USER_CONFIG_DIR = userConfigDir();
|
|
31
|
+
export const CONFIG_PATH = IS_PACKAGED
|
|
32
|
+
? join(USER_CONFIG_DIR, 'config.json')
|
|
33
|
+
: join(ROOT, 'config.json');
|
|
34
|
+
|
|
35
|
+
// Canonical home for the packaged exe. `setup` copies the running binary here
|
|
36
|
+
// (from wherever the user launched it — Downloads, Desktop, etc.) so the path
|
|
37
|
+
// baked into Claude Code's hooks survives manual updates that drop the new exe
|
|
38
|
+
// at a different filesystem location.
|
|
39
|
+
export const CANONICAL_EXE_NAME = process.platform === 'win32' ? 'claude-rpc.exe' : 'claude-rpc';
|
|
40
|
+
export const CANONICAL_INSTALL_DIR = join(USER_CONFIG_DIR, 'bin');
|
|
41
|
+
export const CANONICAL_EXE = join(CANONICAL_INSTALL_DIR, CANONICAL_EXE_NAME);
|
|
42
|
+
|
|
43
|
+
// In packaged mode the "scripts" are sub-commands of the exe itself; in dev
|
|
44
|
+
// they're the source .js files.
|
|
45
|
+
export const HOOK_SCRIPT = IS_PACKAGED ? process.execPath : join(ROOT, 'src', 'hook.js');
|
|
46
|
+
export const DAEMON_SCRIPT = IS_PACKAGED ? process.execPath : join(ROOT, 'src', 'daemon.js');
|
|
47
|
+
export const EXE_PATH = IS_PACKAGED ? process.execPath : null;
|
|
48
|
+
|
|
49
|
+
export const STATE_DIR = join(tmpdir(), 'claude-rpc');
|
|
50
|
+
export const STATE_PATH = join(STATE_DIR, 'state.json');
|
|
51
|
+
export const PID_PATH = join(STATE_DIR, 'daemon.pid');
|
|
52
|
+
export const LOG_PATH = join(STATE_DIR, 'daemon.log');
|
|
53
|
+
export const DATA_DIR = join(homedir(), '.claude-rpc');
|
|
54
|
+
export const AGGREGATE_PATH = join(DATA_DIR, 'aggregate.json');
|
|
55
|
+
export const SCAN_CACHE_PATH = join(DATA_DIR, 'scan-cache.json');
|
|
56
|
+
export const EVENTS_LOG_PATH = join(DATA_DIR, 'events.jsonl');
|
|
57
|
+
export const CLAUDE_HOME = join(homedir(), '.claude');
|
|
58
|
+
export const CLAUDE_PROJECTS = join(CLAUDE_HOME, 'projects');
|
|
59
|
+
export const CLAUDE_SETTINGS = join(CLAUDE_HOME, 'settings.json');
|
package/src/pricing.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Approximate Anthropic API pricing per million tokens, in USD.
|
|
2
|
+
// Numbers here are public list prices and change over time — they are NOT
|
|
3
|
+
// what your Claude Code subscription actually charges. Treat the resulting
|
|
4
|
+
// `costEstimate` as a usage-weighted rough order of magnitude, not a bill.
|
|
5
|
+
//
|
|
6
|
+
// To customize: edit this file and run `claude-rpc rescan`.
|
|
7
|
+
|
|
8
|
+
const PRICING = {
|
|
9
|
+
// Opus 4.x family
|
|
10
|
+
'opus-4': { input: 15.00, output: 75.00, cacheRead: 1.50, cacheWrite: 18.75 },
|
|
11
|
+
'opus-4-5': { input: 15.00, output: 75.00, cacheRead: 1.50, cacheWrite: 18.75 },
|
|
12
|
+
'opus-4-6': { input: 15.00, output: 75.00, cacheRead: 1.50, cacheWrite: 18.75 },
|
|
13
|
+
'opus-4-7': { input: 15.00, output: 75.00, cacheRead: 1.50, cacheWrite: 18.75 },
|
|
14
|
+
|
|
15
|
+
// Sonnet 4.x family
|
|
16
|
+
'sonnet-4': { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },
|
|
17
|
+
'sonnet-4-5': { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },
|
|
18
|
+
'sonnet-4-6': { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },
|
|
19
|
+
|
|
20
|
+
// Haiku 4.x family
|
|
21
|
+
'haiku-4': { input: 1.00, output: 5.00, cacheRead: 0.10, cacheWrite: 1.25 },
|
|
22
|
+
'haiku-4-5': { input: 1.00, output: 5.00, cacheRead: 0.10, cacheWrite: 1.25 },
|
|
23
|
+
|
|
24
|
+
// Generic fallbacks by tier.
|
|
25
|
+
'opus': { input: 15.00, output: 75.00, cacheRead: 1.50, cacheWrite: 18.75 },
|
|
26
|
+
'sonnet': { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },
|
|
27
|
+
'haiku': { input: 1.00, output: 5.00, cacheRead: 0.10, cacheWrite: 1.25 },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const DEFAULT = PRICING.sonnet;
|
|
31
|
+
|
|
32
|
+
// Map a model id like "claude-opus-4-7-20251101" to a pricing key.
|
|
33
|
+
export function pricingKeyFor(modelId) {
|
|
34
|
+
if (!modelId) return 'sonnet';
|
|
35
|
+
const s = String(modelId).toLowerCase();
|
|
36
|
+
// Most specific match wins.
|
|
37
|
+
const candidates = Object.keys(PRICING).sort((a, b) => b.length - a.length);
|
|
38
|
+
for (const key of candidates) {
|
|
39
|
+
if (s.includes(key)) return key;
|
|
40
|
+
}
|
|
41
|
+
return 'sonnet';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function ratesFor(modelId) {
|
|
45
|
+
return PRICING[pricingKeyFor(modelId)] || DEFAULT;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// usage = { input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens }
|
|
49
|
+
export function costFor({ model, usage }) {
|
|
50
|
+
if (!usage) return 0;
|
|
51
|
+
const r = ratesFor(model);
|
|
52
|
+
const M = 1_000_000;
|
|
53
|
+
return (
|
|
54
|
+
((usage.input_tokens || 0) * r.input)
|
|
55
|
+
+ ((usage.output_tokens || 0) * r.output)
|
|
56
|
+
+ ((usage.cache_read_input_tokens || 0) * r.cacheRead)
|
|
57
|
+
+ ((usage.cache_creation_input_tokens || 0) * r.cacheWrite)
|
|
58
|
+
) / M;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Round to two decimal places for display, but keep sub-cent precision when
|
|
62
|
+
// the value is small enough that the rounded form would be "$0.00".
|
|
63
|
+
export function fmtCost(usd) {
|
|
64
|
+
if (!usd) return '$0';
|
|
65
|
+
if (usd < 0.01) return `$${usd.toFixed(4)}`;
|
|
66
|
+
if (usd < 1) return `$${usd.toFixed(2)}`;
|
|
67
|
+
if (usd < 100) return `$${usd.toFixed(2)}`;
|
|
68
|
+
if (usd < 1000) return `$${Math.round(usd)}`;
|
|
69
|
+
if (usd < 10_000) return `$${(usd / 1000).toFixed(2)}k`;
|
|
70
|
+
return `$${(usd / 1000).toFixed(1)}k`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { PRICING };
|