claude-code-rust 0.13.2 → 0.13.4

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
@@ -7,7 +7,6 @@ A native Rust terminal interface for Claude Code. Drop-in replacement for Anthro
7
7
  [![CI](https://github.com/srothgan/claude-code-rust/actions/workflows/pr.yml/badge.svg)](https://github.com/srothgan/claude-code-rust/actions/workflows/pr.yml)
8
8
  [![Docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://srothgan.github.io/claude-code-rust/)
9
9
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
10
- [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-green.svg)](https://nodejs.org/)
11
10
 
12
11
  <p align="center">
13
12
  <img src="assets/banner.png" alt="Claude Code Rust running a Read tool call with syntax-highlighted output" width="900">
@@ -19,7 +18,6 @@ Claude Code Rust replaces the stock Claude Code terminal interface with a native
19
18
 
20
19
  ## Requisites
21
20
 
22
- - Node.js 18+ (for the Agent SDK bridge)
23
21
  - Existing Claude Code authentication (`~/.claude/config.json`)
24
22
 
25
23
  ## Install
@@ -64,7 +62,7 @@ The stock Claude Code TUI runs on Node.js with React Ink, which renders by redra
64
62
  - **Scrollback**: Hijacks the terminal's native scrollback, erasing history you can no longer scroll back to
65
63
  - **Paste**: Large pastes can flood stdout and freeze the terminal
66
64
 
67
- Claude Code Rust addresses these by compiling to a single native binary with diffed, direct terminal control via Crossterm and Ratatui no full-frame redraws, no Node runtime overhead.
65
+ Claude Code Rust addresses these with a native terminal UI that uses diffed, direct terminal control via Crossterm and Ratatui -- no full-frame redraws and no React Ink rendering loop.
68
66
 
69
67
  ## Documentation
70
68
 
@@ -0,0 +1,8 @@
1
+ export async function dispatchCancelTurnCommand(command, deps) {
2
+ const session = deps.sessionById(command.session_id);
3
+ if (!session) {
4
+ deps.slashError(command.session_id, `unknown session: ${command.session_id}`, deps.requestId);
5
+ return;
6
+ }
7
+ await session.query.interrupt();
8
+ }
@@ -16,7 +16,7 @@ import { mapAvailableModels, resolveCurrentModel, currentModelsEqual, } from "./
16
16
  import { shouldEmitStartupAuthRequiredForAccount } from "./account_metadata.js";
17
17
  export { mapAvailableModels, resolveCurrentModel } from "./model_metadata.js";
18
18
  export { shouldEmitStartupAuthRequiredForAccount } from "./account_metadata.js";
19
- const BRIDGE_RUNTIME_PROCESS_NAME = process.platform === "win32" ? "claude-rs-bridge-node.exe" : "claude-rs-bridge-node";
19
+ const BRIDGE_RUNTIME_PROCESS_NAME = process.platform === "win32" ? "claude-rs-bridge-bun.exe" : "claude-rs-bridge-bun";
20
20
  const BRIDGE_RUNTIME_GUARD_PROMPT = `Do not terminate the Claude Rust bridge runtime process \`${BRIDGE_RUNTIME_PROCESS_NAME}\`; ` +
21
21
  "when cleaning up development servers, only stop processes by explicit PIDs you started in this session.";
22
22
  const STARTUP_FALLBACK_MODEL_ALIAS = "fable";
@@ -312,7 +312,7 @@ export async function createSession(params) {
312
312
  error_message: message,
313
313
  },
314
314
  });
315
- throw new Error(`query() failed: node_executable=${process.execPath}; cwd=${params.cwd}; ` +
315
+ throw new Error(`query() failed: runtime_executable=${process.execPath}; cwd=${params.cwd}; ` +
316
316
  `resume=${params.resume ?? "<none>"}; ` +
317
317
  `CLAUDE_CODE_EXECUTABLE=${claudeCodeExecutable ?? "<unset>"}; error=${message}`);
318
318
  }
@@ -506,6 +506,9 @@ function logSdkProcessSpawnStarted(options, includeArgsPreview) {
506
506
  },
507
507
  });
508
508
  }
509
+ export function resolveClaudeCodeSpawnCommand(command) {
510
+ return command === "bun" ? process.execPath : command;
511
+ }
509
512
  function logSdkProcessSpawned(sessionId, child, cwd) {
510
513
  bridgeLogger.info({
511
514
  target: LOG_TARGETS.BRIDGE_SDK,
@@ -607,7 +610,7 @@ export function buildQueryOptions(params) {
607
610
  includePartialMessages: true,
608
611
  promptSuggestions: true,
609
612
  enableFileCheckpointing: true,
610
- executable: "node",
613
+ executable: "bun",
611
614
  ...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
612
615
  ...(settings ? { settings } : {}),
613
616
  ...modelOption,
@@ -628,8 +631,10 @@ export function buildQueryOptions(params) {
628
631
  }
629
632
  },
630
633
  spawnClaudeCodeProcess: (options) => {
631
- logSdkProcessSpawnStarted(options, params.enableSpawnDebug);
632
- const child = spawnChild(options.command, options.args, {
634
+ const command = resolveClaudeCodeSpawnCommand(options.command);
635
+ const spawnOptions = { ...options, command };
636
+ logSdkProcessSpawnStarted(spawnOptions, params.enableSpawnDebug);
637
+ const child = spawnChild(command, options.args, {
633
638
  cwd: options.cwd,
634
639
  env: options.env,
635
640
  signal: options.signal,
@@ -14,6 +14,7 @@ import { mapSdkSlashCommands, updateAvailableCommands } from "./bridge/available
14
14
  import { mapSdkAccountInfo } from "./bridge/account_metadata.js";
15
15
  import { MCP_STALE_STATUS_REVALIDATION_COOLDOWN_MS, emitReconciledMcpSnapshotFromStatuses, handleMcpAuthenticateCommand, handleMcpClearAuthCommand, handleMcpOauthCallbackUrlCommand, handleMcpReconnectCommand, handleMcpSetServersCommand, handleMcpStatusCommand, handleMcpToggleCommand, staleMcpAuthCandidates, } from "./bridge/mcp.js";
16
16
  import { bridgeLogger, LOG_TARGETS, logBridgeCommandReceived } from "./bridge/logger.js";
17
+ import { dispatchCancelTurnCommand } from "./bridge/command_dispatch.js";
17
18
  // Re-exports: all symbols that tests and external consumers import from bridge.js.
18
19
  export { AsyncQueue } from "./bridge/shared.js";
19
20
  export { asRecordOrNull } from "./bridge/shared.js";
@@ -27,7 +28,7 @@ export { permissionOptionsFromSuggestions, permissionResultFromOutcome, } from "
27
28
  export { mapSessionMessagesToUpdates, mapSdkSessions, } from "./bridge/history.js";
28
29
  export { handleSdkMessage, handleTaskSystemMessage } from "./bridge/message_handlers.js";
29
30
  export { mapAvailableAgents } from "./bridge/agents.js";
30
- export { buildQueryOptions } from "./bridge/session_lifecycle.js";
31
+ export { buildQueryOptions, resolveClaudeCodeSpawnCommand, } from "./bridge/session_lifecycle.js";
31
32
  export { mapAvailableModels } from "./bridge/model_metadata.js";
32
33
  export { bridgeMcpConfigToSdk, mapMcpServerStatus, mapMcpServerStatusConfig, } from "./bridge/mcp_metadata.js";
33
34
  export { apiProviderIsExternal, isKnownApiProvider, mapSdkAccountInfo, shouldEmitStartupAuthRequiredForAccount, } from "./bridge/account_metadata.js";
@@ -678,12 +679,7 @@ async function handleCommand(command, requestId) {
678
679
  return;
679
680
  }
680
681
  case "cancel_turn": {
681
- const session = sessionById(command.session_id);
682
- if (!session) {
683
- slashError(command.session_id, `unknown session: ${command.session_id}`, requestId);
684
- return;
685
- }
686
- await session.query.interrupt();
682
+ await dispatchCancelTurnCommand(command, { requestId, sessionById, slashError });
687
683
  return;
688
684
  }
689
685
  case "set_model": {
package/bin/claude-rs.js CHANGED
@@ -8,38 +8,109 @@ const path = require("node:path");
8
8
  const TARGETS = {
9
9
  "darwin:arm64": {
10
10
  packageName: "@srothgan/claude-code-rust-darwin-arm64",
11
- exe: "claude-rs"
11
+ exe: "claude-rs",
12
+ display: "darwin:arm64"
12
13
  },
13
14
  "darwin:x64": {
14
15
  packageName: "@srothgan/claude-code-rust-darwin-x64",
15
- exe: "claude-rs"
16
+ exe: "claude-rs",
17
+ display: "darwin:x64"
16
18
  },
17
19
  "linux:x64": {
18
20
  packageName: "@srothgan/claude-code-rust-linux-x64-gnu",
19
- exe: "claude-rs"
21
+ exe: "claude-rs",
22
+ libc: "glibc",
23
+ display: "linux:x64 glibc"
24
+ },
25
+ "linux:arm64": {
26
+ packageName: "@srothgan/claude-code-rust-linux-arm64-gnu",
27
+ exe: "claude-rs",
28
+ libc: "glibc",
29
+ display: "linux:arm64 glibc"
20
30
  },
21
31
  "win32:x64": {
22
32
  packageName: "@srothgan/claude-code-rust-win32-x64-msvc",
23
- exe: "claude-rs.exe"
33
+ exe: "claude-rs.exe",
34
+ display: "win32:x64"
35
+ },
36
+ "win32:arm64": {
37
+ packageName: "@srothgan/claude-code-rust-win32-arm64-msvc",
38
+ exe: "claude-rs.exe",
39
+ display: "win32:arm64"
24
40
  }
25
41
  };
26
42
 
27
- function resolveInstall() {
28
- const key = `${process.platform}:${process.arch}`;
43
+ function detectLinuxLibc(processLike = process) {
44
+ if (processLike.platform !== "linux") {
45
+ return undefined;
46
+ }
47
+
48
+ try {
49
+ const report = processLike.report?.getReport?.();
50
+ const glibcVersion = report?.header?.glibcVersionRuntime;
51
+ if (typeof glibcVersion === "string" && glibcVersion.length > 0) {
52
+ return "glibc";
53
+ }
54
+ } catch {
55
+ // A missing or disabled process report should not be mistaken for glibc.
56
+ }
57
+
58
+ return "musl";
59
+ }
60
+
61
+ function supportedPlatformsText() {
62
+ return Object.values(TARGETS)
63
+ .map((target) => target.display)
64
+ .join(", ");
65
+ }
66
+
67
+ function selectTarget(processLike = process) {
68
+ const key = `${processLike.platform}:${processLike.arch}`;
29
69
  const info = TARGETS[key];
30
70
  if (!info) {
31
- return { error: `Unsupported platform/arch for claude-rs: ${key}` };
71
+ return {
72
+ error:
73
+ `Unsupported platform/arch for claude-rs: ${key}\n` +
74
+ `Supported platforms: ${supportedPlatformsText()}\n` +
75
+ "Use one of the supported npm platforms, or build claude-code-rust from source for this host."
76
+ };
32
77
  }
33
78
 
79
+ if (processLike.platform === "linux") {
80
+ const libc = detectLinuxLibc(processLike);
81
+ if (libc !== info.libc) {
82
+ return {
83
+ error:
84
+ `Unsupported Linux libc for claude-rs: linux/${processLike.arch} ${libc}\n` +
85
+ `linux/${processLike.arch} musl is not supported by the current npm packages.\n` +
86
+ "Linux npm packages currently require glibc. Build claude-code-rust from source for this host."
87
+ };
88
+ }
89
+ }
90
+
91
+ return { key, info };
92
+ }
93
+
94
+ function resolveInstall(options = {}) {
95
+ const processLike = options.processLike || process;
96
+ const requireResolve = options.requireResolve || require.resolve;
97
+ const existsSync = options.existsSync || fs.existsSync;
98
+ const dirname = options.dirname || __dirname;
99
+ const selected = selectTarget(processLike);
100
+ if (selected.error) {
101
+ return { error: selected.error };
102
+ }
103
+
104
+ const { key, info } = selected;
34
105
  let packageJsonPath;
35
106
  try {
36
- packageJsonPath = require.resolve(`${info.packageName}/package.json`);
107
+ packageJsonPath = requireResolve(`${info.packageName}/package.json`);
37
108
  } catch (error) {
38
109
  if (error && error.code === "MODULE_NOT_FOUND") {
39
110
  return {
40
111
  error:
41
112
  `Missing platform package ${info.packageName} for ${key}.\n` +
42
- "This usually means npm optional dependencies were omitted.\n" +
113
+ "This usually means npm optional dependencies were omitted, for example by `npm install --omit=optional`.\n" +
43
114
  "Check `npm config get omit`, then reinstall with:\n" +
44
115
  " npm install -g claude-code-rust"
45
116
  };
@@ -48,7 +119,7 @@ function resolveInstall() {
48
119
  }
49
120
 
50
121
  const binaryPath = path.join(path.dirname(packageJsonPath), "bin", info.exe);
51
- if (!fs.existsSync(binaryPath)) {
122
+ if (!existsSync(binaryPath)) {
52
123
  return {
53
124
  error:
54
125
  `Missing binary at ${binaryPath}\n` +
@@ -57,8 +128,8 @@ function resolveInstall() {
57
128
  };
58
129
  }
59
130
 
60
- const bundledBridgeScript = path.join(__dirname, "..", "agent-sdk", "dist", "bridge.js");
61
- if (!fs.existsSync(bundledBridgeScript)) {
131
+ const bundledBridgeScript = path.join(dirname, "..", "agent-sdk", "dist", "bridge.js");
132
+ if (!existsSync(bundledBridgeScript)) {
62
133
  return {
63
134
  error:
64
135
  `Missing bundled bridge at ${bundledBridgeScript}\n` +
@@ -70,30 +141,44 @@ function resolveInstall() {
70
141
  return { binaryPath, bundledBridgeScript };
71
142
  }
72
143
 
73
- const resolved = resolveInstall();
74
- if (resolved.error) {
75
- console.error(resolved.error);
76
- process.exit(1);
144
+ function main() {
145
+ const resolved = resolveInstall();
146
+ if (resolved.error) {
147
+ console.error(resolved.error);
148
+ process.exit(1);
149
+ }
150
+
151
+ const child = spawn(resolved.binaryPath, process.argv.slice(2), {
152
+ env: {
153
+ ...process.env,
154
+ CLAUDE_RS_AGENT_BRIDGE: process.env.CLAUDE_RS_AGENT_BRIDGE || resolved.bundledBridgeScript
155
+ },
156
+ stdio: "inherit",
157
+ windowsHide: true
158
+ });
159
+
160
+ child.on("error", (error) => {
161
+ console.error(`Failed to launch claude-rs: ${error.message}`);
162
+ process.exit(1);
163
+ });
164
+
165
+ child.on("exit", (code, signal) => {
166
+ if (signal) {
167
+ process.kill(process.pid, signal);
168
+ return;
169
+ }
170
+ process.exit(code ?? 1);
171
+ });
77
172
  }
78
173
 
79
- const child = spawn(resolved.binaryPath, process.argv.slice(2), {
80
- env: {
81
- ...process.env,
82
- CLAUDE_RS_AGENT_BRIDGE: process.env.CLAUDE_RS_AGENT_BRIDGE || resolved.bundledBridgeScript
83
- },
84
- stdio: "inherit",
85
- windowsHide: true
86
- });
87
-
88
- child.on("error", (error) => {
89
- console.error(`Failed to launch claude-rs: ${error.message}`);
90
- process.exit(1);
91
- });
92
-
93
- child.on("exit", (code, signal) => {
94
- if (signal) {
95
- process.kill(process.pid, signal);
96
- return;
97
- }
98
- process.exit(code ?? 1);
99
- });
174
+ if (require.main === module) {
175
+ main();
176
+ }
177
+
178
+ module.exports = {
179
+ TARGETS,
180
+ detectLinuxLibc,
181
+ resolveInstall,
182
+ selectTarget,
183
+ supportedPlatformsText
184
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-rust",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "description": "Claude Code Rust - native Rust terminal interface for Claude Code",
5
5
  "keywords": [
6
6
  "cli",
@@ -33,19 +33,18 @@
33
33
  "LICENSE"
34
34
  ],
35
35
  "dependencies": {
36
- "@anthropic-ai/claude-agent-sdk": "0.3.198",
37
- "@anthropic-ai/sdk": "0.106.0",
38
- "@modelcontextprotocol/sdk": "1.29.0",
39
- "zod": "4.4.3"
36
+ "@anthropic-ai/claude-agent-sdk": "0.3.198"
40
37
  },
41
38
  "optionalDependencies": {
42
- "@srothgan/claude-code-rust-darwin-arm64": "0.13.2",
43
- "@srothgan/claude-code-rust-darwin-x64": "0.13.2",
44
- "@srothgan/claude-code-rust-linux-x64-gnu": "0.13.2",
45
- "@srothgan/claude-code-rust-win32-x64-msvc": "0.13.2"
39
+ "@srothgan/claude-code-rust-darwin-arm64": "0.13.4",
40
+ "@srothgan/claude-code-rust-darwin-x64": "0.13.4",
41
+ "@srothgan/claude-code-rust-linux-x64-gnu": "0.13.4",
42
+ "@srothgan/claude-code-rust-linux-arm64-gnu": "0.13.4",
43
+ "@srothgan/claude-code-rust-win32-x64-msvc": "0.13.4",
44
+ "@srothgan/claude-code-rust-win32-arm64-msvc": "0.13.4"
46
45
  },
47
46
  "engines": {
48
- "node": ">=18"
47
+ "node": ">=24"
49
48
  },
50
49
  "publishConfig": {
51
50
  "access": "public"