claude-code-rust 0.13.4 → 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
@@ -16,28 +16,39 @@ A native Rust terminal interface for Claude Code. Drop-in replacement for Anthro
16
16
 
17
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.
18
18
 
19
- ## Requisites
19
+ ## Prerequisite
20
20
 
21
- - 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.
22
22
 
23
23
  ## Install
24
24
 
25
- ### 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:**
26
30
 
27
31
  ```bash
28
- npm install -g claude-code-rust
32
+ curl -fsSL https://raw.githubusercontent.com/srothgan/claude-code-rust/main/scripts/install/install.sh | sh
29
33
  ```
30
34
 
31
- 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:**
32
36
 
33
- 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:
34
44
 
35
45
  ```bash
36
- npm config get omit
37
46
  npm install -g claude-code-rust
38
47
  ```
39
48
 
40
- 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.
41
52
 
42
53
  ## Usage
43
54
 
@@ -66,7 +77,7 @@ Claude Code Rust addresses these with a native terminal UI that uses diffed, dir
66
77
 
67
78
  ## Documentation
68
79
 
69
- 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:
70
81
 
71
82
  - [Installation](https://srothgan.github.io/claude-code-rust/installation.html)
72
83
  - [Usage](https://srothgan.github.io/claude-code-rust/usage.html)
@@ -1,8 +1,26 @@
1
+ import { bridgeLogger, LOG_TARGETS } from "./logger.js";
1
2
  export async function dispatchCancelTurnCommand(command, deps) {
2
3
  const session = deps.sessionById(command.session_id);
3
4
  if (!session) {
4
5
  deps.slashError(command.session_id, `unknown session: ${command.session_id}`, deps.requestId);
5
6
  return;
6
7
  }
7
- await session.query.interrupt();
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
+ }
8
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);
@@ -542,6 +542,8 @@ function permissionModeFromSettingsValue(rawMode) {
542
542
  return undefined;
543
543
  }
544
544
  switch (rawMode) {
545
+ case "manual":
546
+ return "default";
545
547
  case "default":
546
548
  case "auto":
547
549
  case "acceptEdits":
@@ -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") {
@@ -130,7 +130,7 @@ export function emitAgentConfigOptionUpdate(sessionId, agent) {
130
130
  value: agent,
131
131
  });
132
132
  }
133
- const EXPECTED_AGENT_SDK_VERSION = "0.3.198";
133
+ const EXPECTED_AGENT_SDK_VERSION = "0.3.207";
134
134
  const require = createRequire(import.meta.url);
135
135
  export function resolveInstalledAgentSdkVersion() {
136
136
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-rust",
3
- "version": "0.13.4",
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,15 +33,15 @@
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.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"
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
47
  "node": ">=24"