claude-code-rust 0.13.3 → 0.14.0

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">
@@ -17,29 +16,39 @@ A native Rust terminal interface for Claude Code. Drop-in replacement for Anthro
17
16
 
18
17
  Claude Code Rust replaces the stock Claude Code terminal interface with a native Rust binary built on [Ratatui](https://ratatui.rs/). It connects to the same Claude API through a local Agent SDK bridge. Core Claude Code functionality - tool calls, file editing, terminal commands, and permissions - works unchanged.
19
18
 
20
- ## Requisites
19
+ ## Prerequisite
21
20
 
22
- - Node.js 18+ (for the Agent SDK bridge)
23
- - Existing Claude Code authentication (`~/.claude/config.json`)
21
+ - Install the Claude Code CLI. You do not need to authenticate before installing or starting `claude-rs`; sign in later with `/login` or `claude auth login` when needed.
24
22
 
25
23
  ## Install
26
24
 
27
- ### npm (global, recommended)
25
+ ### Install script (recommended, v0.14.0+)
26
+
27
+ The install script downloads a complete GitHub Release archive. It does not require Rust, Node.js, npm, or Bun on the user's machine.
28
+
29
+ **macOS/Linux:**
28
30
 
29
31
  ```bash
30
- npm install -g claude-code-rust
32
+ curl -fsSL https://raw.githubusercontent.com/srothgan/claude-code-rust/main/scripts/install/install.sh | sh
31
33
  ```
32
34
 
33
- The npm package installs a small launcher plus a platform-specific optional dependency containing the prebuilt Rust binary for your OS and architecture.
35
+ **Windows PowerShell:**
34
36
 
35
- If `claude-rs` reports a missing platform package, check whether optional dependencies were omitted:
37
+ ```powershell
38
+ powershell -NoProfile -ExecutionPolicy Bypass -Command "irm 'https://raw.githubusercontent.com/srothgan/claude-code-rust/main/scripts/install/install.ps1' | iex"
39
+ ```
40
+
41
+ ### npm (global)
42
+
43
+ npm remains supported for users who prefer package-manager ownership of the global command:
36
44
 
37
45
  ```bash
38
- npm config get omit
39
46
  npm install -g claude-code-rust
40
47
  ```
41
48
 
42
- If `claude-rs` resolves to an older global shim, ensure your npm global bin directory comes first on `PATH` or remove the stale shim before retrying.
49
+ This option requires Node.js 24 and npm. The npm package installs a small launcher plus a platform-specific optional dependency containing the prebuilt Rust binary, Agent SDK bridge, and bundled private Bun runtime for your OS and architecture. A separate Rust toolchain or Bun installation is not required.
50
+
51
+ See the [installation guide](https://srothgan.github.io/claude-code-rust/installation.html) for release pinning, custom locations, switching install methods, and troubleshooting.
43
52
 
44
53
  ## Usage
45
54
 
@@ -64,11 +73,11 @@ The stock Claude Code TUI runs on Node.js with React Ink, which renders by redra
64
73
  - **Scrollback**: Hijacks the terminal's native scrollback, erasing history you can no longer scroll back to
65
74
  - **Paste**: Large pastes can flood stdout and freeze the terminal
66
75
 
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.
76
+ 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
77
 
69
78
  ## Documentation
70
79
 
71
- The manual covers installation from npm and source, help, slash commands, keyboard shortcuts, settings, diagnostics, architecture, and the changelog:
80
+ The manual covers installation with scripts, npm, and source builds, plus help, slash commands, keyboard shortcuts, settings, diagnostics, architecture, and the changelog:
72
81
 
73
82
  - [Installation](https://srothgan.github.io/claude-code-rust/installation.html)
74
83
  - [Usage](https://srothgan.github.io/claude-code-rust/usage.html)
@@ -0,0 +1,26 @@
1
+ import { bridgeLogger, LOG_TARGETS } from "./logger.js";
2
+ export async function dispatchCancelTurnCommand(command, deps) {
3
+ const session = deps.sessionById(command.session_id);
4
+ if (!session) {
5
+ deps.slashError(command.session_id, `unknown session: ${command.session_id}`, deps.requestId);
6
+ return;
7
+ }
8
+ const receipt = await session.query.interrupt();
9
+ const stillQueued = Array.isArray(receipt?.still_queued)
10
+ ? receipt.still_queued.filter((entry) => typeof entry === "string")
11
+ : [];
12
+ if (stillQueued.length > 0) {
13
+ bridgeLogger.info({
14
+ target: LOG_TARGETS.APP_SESSION,
15
+ eventName: "interrupt_receipt_still_queued",
16
+ message: "interrupt receipt reported queued async messages",
17
+ outcome: "success",
18
+ sessionId: command.session_id,
19
+ ...(deps.requestId ? { requestId: deps.requestId } : {}),
20
+ fields: {
21
+ still_queued_count: stillQueued.length,
22
+ still_queued: stillQueued,
23
+ },
24
+ });
25
+ }
26
+ }
@@ -307,6 +307,7 @@ export function parseCommandEnvelope(line) {
307
307
  session_id: expectString(raw, "session_id", "reload_plugins"),
308
308
  };
309
309
  case "mcp_status":
310
+ // Rust historically sends `get_mcp_snapshot`; normalize it to the bridge-internal command.
310
311
  case "get_mcp_snapshot":
311
312
  return {
312
313
  command: "mcp_status",
@@ -450,6 +451,9 @@ function parseQuestionAnnotation(value) {
450
451
  };
451
452
  }
452
453
  export function toPermissionMode(mode) {
454
+ if (mode === "manual") {
455
+ return "default";
456
+ }
453
457
  if (mode === "default" ||
454
458
  mode === "auto" ||
455
459
  mode === "acceptEdits" ||
@@ -3,7 +3,7 @@ import { toPermissionMode, buildModeState, refreshSupportedModesForSession } fro
3
3
  import { writeEvent, emitSessionUpdate, emitConnectEvent, emitSessionReplacedEvent, } from "./events.js";
4
4
  import { TOOL_RESULT_TYPES, isToolSearchToolName, isToolSearchToolResultType, unwrapToolUseResult, } from "./tooling.js";
5
5
  import { emitToolCall, emitToolCallUpdate, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, defersTaskNotificationCompletion, toolAcceptsTaskLifecycle, taskProgressText, taskUpdatedFields, } from "./tool_calls.js";
6
- import { applyTaskLifecycleState } from "./tasks.js";
6
+ import { applyBackgroundTasksChanged, applyTaskLifecycleState } from "./tasks.js";
7
7
  import { linkTaskToolUse, unlinkTaskToolUse } from "./task_links.js";
8
8
  import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdateIfChanged } from "./error_classification.js";
9
9
  import { mapAvailableAgentsFromNames, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
@@ -39,6 +39,7 @@ function sdkCorrelationMetadata(msg) {
39
39
  requestId: typeof msg.request_id === "string" ? msg.request_id : undefined,
40
40
  subagentType: typeof msg.subagent_type === "string" ? msg.subagent_type : undefined,
41
41
  taskDescription: typeof msg.task_description === "string" ? msg.task_description : undefined,
42
+ parentAgentId: typeof msg.parent_agent_id === "string" ? msg.parent_agent_id : undefined,
42
43
  };
43
44
  }
44
45
  function sdkTaskMetadata(msg) {
@@ -53,12 +54,14 @@ function sdkTaskMetadata(msg) {
53
54
  ...(metadata.requestId ? { request_id: metadata.requestId } : {}),
54
55
  ...(metadata.subagentType ? { subagent_type: metadata.subagentType } : {}),
55
56
  ...(metadata.taskDescription ? { task_description: metadata.taskDescription } : {}),
57
+ ...(metadata.parentAgentId ? { parent_agent_id: metadata.parentAgentId } : {}),
56
58
  ...(taskType ? { task_type: taskType } : {}),
57
59
  ...(workflowName ? { workflow_name: workflowName } : {}),
58
60
  ...(prompt ? { prompt } : {}),
59
61
  ...(outputFile ? { output_file: outputFile } : {}),
60
62
  ...(summary ? { summary } : {}),
61
63
  ...(status ? { terminal_status: status } : {}),
64
+ ...(typeof msg.blocked === "boolean" ? { blocked: msg.blocked } : {}),
62
65
  };
63
66
  return Object.keys(taskMetadata).length > 0 ? taskMetadata : undefined;
64
67
  }
@@ -822,6 +825,13 @@ function terminalReasonFromValue(value) {
822
825
  case "hook_stopped":
823
826
  case "tool_deferred":
824
827
  case "max_turns":
828
+ case "background_requested":
829
+ case "api_error":
830
+ case "malformed_tool_use_exhausted":
831
+ case "budget_exhausted":
832
+ case "structured_output_retry_exhausted":
833
+ case "tool_deferred_unavailable":
834
+ case "turn_setup_failed":
825
835
  case "completed":
826
836
  return value;
827
837
  default:
@@ -848,6 +858,10 @@ export function handleSdkMessage(session, message) {
848
858
  updateAvailableCommands(session, "commands_changed", mapSdkSlashCommands(msg.commands));
849
859
  return;
850
860
  }
861
+ if (subtype === "background_tasks_changed") {
862
+ applyBackgroundTasksChanged(session, msg);
863
+ return;
864
+ }
851
865
  if (subtype === "notification") {
852
866
  const text = typeof msg.text === "string" ? msg.text : "";
853
867
  emitSystemNoticeUpdate(session, notificationSeverity(msg.priority), text);
@@ -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,
@@ -539,6 +542,8 @@ function permissionModeFromSettingsValue(rawMode) {
539
542
  return undefined;
540
543
  }
541
544
  switch (rawMode) {
545
+ case "manual":
546
+ return "default";
542
547
  case "default":
543
548
  case "auto":
544
549
  case "acceptEdits":
@@ -607,7 +612,7 @@ export function buildQueryOptions(params) {
607
612
  includePartialMessages: true,
608
613
  promptSuggestions: true,
609
614
  enableFileCheckpointing: true,
610
- executable: "node",
615
+ executable: "bun",
611
616
  ...(params.resume ? {} : { sessionId: params.provisionalSessionId }),
612
617
  ...(settings ? { settings } : {}),
613
618
  ...modelOption,
@@ -628,8 +633,10 @@ export function buildQueryOptions(params) {
628
633
  }
629
634
  },
630
635
  spawnClaudeCodeProcess: (options) => {
631
- logSdkProcessSpawnStarted(options, params.enableSpawnDebug);
632
- const child = spawnChild(options.command, options.args, {
636
+ const command = resolveClaudeCodeSpawnCommand(options.command);
637
+ const spawnOptions = { ...options, command };
638
+ logSdkProcessSpawnStarted(spawnOptions, params.enableSpawnDebug);
639
+ const child = spawnChild(command, options.args, {
633
640
  cwd: options.cwd,
634
641
  env: options.env,
635
642
  signal: options.signal,
@@ -9,6 +9,7 @@ const TASK_TOOL_NAMES = new Set([
9
9
  "TaskOutput",
10
10
  "TaskStop",
11
11
  ]);
12
+ const BACKGROUND_TASK_METADATA_KEY = "sdk_background_task";
12
13
  export function isTaskToolName(name) {
13
14
  return TASK_TOOL_NAMES.has(name);
14
15
  }
@@ -806,6 +807,8 @@ function lifecycleMetadata(msg) {
806
807
  "summary",
807
808
  "end_time",
808
809
  "total_paused_ms",
810
+ "blocked",
811
+ "parent_agent_id",
809
812
  ]) {
810
813
  copyValue(msg, key);
811
814
  copyValue(patch, key);
@@ -857,6 +860,43 @@ export function applyTaskLifecycleState(session, subtype, msg) {
857
860
  }),
858
861
  ]);
859
862
  }
863
+ export function applyBackgroundTasksChanged(session, msg) {
864
+ const rawTasks = Array.isArray(msg.tasks) ? msg.tasks : [];
865
+ const nextTasks = rawTasks
866
+ .map((entry) => {
867
+ const task = asRecordOrNull(entry);
868
+ const taskId = nonEmptyString(task?.task_id);
869
+ if (!task || !taskId) {
870
+ return undefined;
871
+ }
872
+ const taskType = nonEmptyString(task.task_type);
873
+ const description = nonEmptyString(task.description);
874
+ return {
875
+ task_id: taskId,
876
+ subject: description ?? taskType ?? taskId,
877
+ description,
878
+ status: "in_progress",
879
+ blocks: [],
880
+ blocked_by: [],
881
+ metadata: {
882
+ [BACKGROUND_TASK_METADATA_KEY]: true,
883
+ ...(taskType ? { task_type: taskType } : {}),
884
+ },
885
+ };
886
+ })
887
+ .filter((task) => Boolean(task));
888
+ const nextIds = new Set(nextTasks.map((task) => task.task_id));
889
+ const removedTaskIds = session.taskOrder.filter((taskId) => {
890
+ if (nextIds.has(taskId)) {
891
+ return false;
892
+ }
893
+ const metadata = asRecordOrNull(session.tasksById.get(taskId)?.metadata);
894
+ return metadata?.[BACKGROUND_TASK_METADATA_KEY] === true;
895
+ });
896
+ const tasks = nextTasks.map((task) => upsertTask(session, task));
897
+ const removed = removeTasks(session, removedTaskIds);
898
+ emitTaskStateUpdate(session, "background_tasks", tasks, removed, true);
899
+ }
860
900
  export function currentTaskSnapshot(session) {
861
901
  return orderedTasks(session);
862
902
  }
@@ -423,6 +423,12 @@ function buildTaskMetadata(patch) {
423
423
  if (typeof patch.status === "string" && ["completed", "failed", "killed"].includes(patch.status)) {
424
424
  taskMetadata.terminal_status = patch.status;
425
425
  }
426
+ if (typeof patch.blocked === "boolean") {
427
+ taskMetadata.blocked = patch.blocked;
428
+ }
429
+ if (typeof patch.parent_agent_id === "string" && patch.parent_agent_id.length > 0) {
430
+ taskMetadata.parent_agent_id = patch.parent_agent_id;
431
+ }
426
432
  return Object.keys(taskMetadata).length > 0 ? taskMetadata : undefined;
427
433
  }
428
434
  export function taskUpdatedFields(msg) {
@@ -261,7 +261,6 @@ export function toolTitle(name, input, context = {}) {
261
261
  return label ? `${ARTIFACT_TOOL_NAME}: ${label}` : ARTIFACT_TOOL_NAME;
262
262
  }
263
263
  if (name === SHOW_ONBOARDING_ROLE_PICKER_TOOL_NAME) {
264
- // TODO: The TUI accepts this SDK tool call but does not implement an onboarding role flow yet.
265
264
  return SHOW_ONBOARDING_ROLE_PICKER_TOOL_NAME;
266
265
  }
267
266
  if (name === "EnterWorktree") {
@@ -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";
@@ -129,7 +130,7 @@ export function emitAgentConfigOptionUpdate(sessionId, agent) {
129
130
  value: agent,
130
131
  });
131
132
  }
132
- const EXPECTED_AGENT_SDK_VERSION = "0.3.198";
133
+ const EXPECTED_AGENT_SDK_VERSION = "0.3.207";
133
134
  const require = createRequire(import.meta.url);
134
135
  export function resolveInstalledAgentSdkVersion() {
135
136
  try {
@@ -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,46 +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"
20
24
  },
21
25
  "linux:arm64": {
22
26
  packageName: "@srothgan/claude-code-rust-linux-arm64-gnu",
23
- exe: "claude-rs"
27
+ exe: "claude-rs",
28
+ libc: "glibc",
29
+ display: "linux:arm64 glibc"
24
30
  },
25
31
  "win32:x64": {
26
32
  packageName: "@srothgan/claude-code-rust-win32-x64-msvc",
27
- exe: "claude-rs.exe"
33
+ exe: "claude-rs.exe",
34
+ display: "win32:x64"
28
35
  },
29
36
  "win32:arm64": {
30
37
  packageName: "@srothgan/claude-code-rust-win32-arm64-msvc",
31
- exe: "claude-rs.exe"
38
+ exe: "claude-rs.exe",
39
+ display: "win32:arm64"
32
40
  }
33
41
  };
34
42
 
35
- function resolveInstall() {
36
- 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}`;
37
69
  const info = TARGETS[key];
38
70
  if (!info) {
39
- 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
+ };
40
77
  }
41
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;
42
105
  let packageJsonPath;
43
106
  try {
44
- packageJsonPath = require.resolve(`${info.packageName}/package.json`);
107
+ packageJsonPath = requireResolve(`${info.packageName}/package.json`);
45
108
  } catch (error) {
46
109
  if (error && error.code === "MODULE_NOT_FOUND") {
47
110
  return {
48
111
  error:
49
112
  `Missing platform package ${info.packageName} for ${key}.\n` +
50
- "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" +
51
114
  "Check `npm config get omit`, then reinstall with:\n" +
52
115
  " npm install -g claude-code-rust"
53
116
  };
@@ -56,7 +119,7 @@ function resolveInstall() {
56
119
  }
57
120
 
58
121
  const binaryPath = path.join(path.dirname(packageJsonPath), "bin", info.exe);
59
- if (!fs.existsSync(binaryPath)) {
122
+ if (!existsSync(binaryPath)) {
60
123
  return {
61
124
  error:
62
125
  `Missing binary at ${binaryPath}\n` +
@@ -65,8 +128,8 @@ function resolveInstall() {
65
128
  };
66
129
  }
67
130
 
68
- const bundledBridgeScript = path.join(__dirname, "..", "agent-sdk", "dist", "bridge.js");
69
- if (!fs.existsSync(bundledBridgeScript)) {
131
+ const bundledBridgeScript = path.join(dirname, "..", "agent-sdk", "dist", "bridge.js");
132
+ if (!existsSync(bundledBridgeScript)) {
70
133
  return {
71
134
  error:
72
135
  `Missing bundled bridge at ${bundledBridgeScript}\n` +
@@ -78,30 +141,44 @@ function resolveInstall() {
78
141
  return { binaryPath, bundledBridgeScript };
79
142
  }
80
143
 
81
- const resolved = resolveInstall();
82
- if (resolved.error) {
83
- console.error(resolved.error);
84
- 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
+ });
172
+ }
173
+
174
+ if (require.main === module) {
175
+ main();
85
176
  }
86
177
 
87
- const child = spawn(resolved.binaryPath, process.argv.slice(2), {
88
- env: {
89
- ...process.env,
90
- CLAUDE_RS_AGENT_BRIDGE: process.env.CLAUDE_RS_AGENT_BRIDGE || resolved.bundledBridgeScript
91
- },
92
- stdio: "inherit",
93
- windowsHide: true
94
- });
95
-
96
- child.on("error", (error) => {
97
- console.error(`Failed to launch claude-rs: ${error.message}`);
98
- process.exit(1);
99
- });
100
-
101
- child.on("exit", (code, signal) => {
102
- if (signal) {
103
- process.kill(process.pid, signal);
104
- return;
105
- }
106
- process.exit(code ?? 1);
107
- });
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.3",
3
+ "version": "0.14.0",
4
4
  "description": "Claude Code Rust - native Rust terminal interface for Claude Code",
5
5
  "keywords": [
6
6
  "cli",
@@ -33,18 +33,18 @@
33
33
  "LICENSE"
34
34
  ],
35
35
  "dependencies": {
36
- "@anthropic-ai/claude-agent-sdk": "0.3.198"
36
+ "@anthropic-ai/claude-agent-sdk": "0.3.207"
37
37
  },
38
38
  "optionalDependencies": {
39
- "@srothgan/claude-code-rust-darwin-arm64": "0.13.3",
40
- "@srothgan/claude-code-rust-darwin-x64": "0.13.3",
41
- "@srothgan/claude-code-rust-linux-x64-gnu": "0.13.3",
42
- "@srothgan/claude-code-rust-linux-arm64-gnu": "0.13.3",
43
- "@srothgan/claude-code-rust-win32-x64-msvc": "0.13.3",
44
- "@srothgan/claude-code-rust-win32-arm64-msvc": "0.13.3"
39
+ "@srothgan/claude-code-rust-darwin-arm64": "0.14.0",
40
+ "@srothgan/claude-code-rust-darwin-x64": "0.14.0",
41
+ "@srothgan/claude-code-rust-linux-x64-gnu": "0.14.0",
42
+ "@srothgan/claude-code-rust-linux-arm64-gnu": "0.14.0",
43
+ "@srothgan/claude-code-rust-win32-x64-msvc": "0.14.0",
44
+ "@srothgan/claude-code-rust-win32-arm64-msvc": "0.14.0"
45
45
  },
46
46
  "engines": {
47
- "node": ">=18"
47
+ "node": ">=24"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"