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.
@@ -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 };