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 +21 -12
- package/agent-sdk/dist/bridge/command_dispatch.js +26 -0
- package/agent-sdk/dist/bridge/commands.js +4 -0
- package/agent-sdk/dist/bridge/message_handlers.js +15 -1
- package/agent-sdk/dist/bridge/session_lifecycle.js +12 -5
- package/agent-sdk/dist/bridge/tasks.js +40 -0
- package/agent-sdk/dist/bridge/tool_calls.js +6 -0
- package/agent-sdk/dist/bridge/tooling.js +0 -1
- package/agent-sdk/dist/bridge.js +4 -8
- package/bin/claude-rs.js +116 -39
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -7,7 +7,6 @@ A native Rust terminal interface for Claude Code. Drop-in replacement for Anthro
|
|
|
7
7
|
[](https://github.com/srothgan/claude-code-rust/actions/workflows/pr.yml)
|
|
8
8
|
[](https://srothgan.github.io/claude-code-rust/)
|
|
9
9
|
[](https://www.apache.org/licenses/LICENSE-2.0)
|
|
10
|
-
[](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
|
-
##
|
|
19
|
+
## Prerequisite
|
|
21
20
|
|
|
22
|
-
-
|
|
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
|
-
###
|
|
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
|
-
|
|
32
|
+
curl -fsSL https://raw.githubusercontent.com/srothgan/claude-code-rust/main/scripts/install/install.sh | sh
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
**Windows PowerShell:**
|
|
34
36
|
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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-
|
|
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:
|
|
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: "
|
|
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
|
-
|
|
632
|
-
const
|
|
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") {
|
package/agent-sdk/dist/bridge.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
36
|
-
|
|
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 {
|
|
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 =
|
|
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
|
|
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 (!
|
|
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(
|
|
69
|
-
if (!
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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.
|
|
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.
|
|
36
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.207"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@srothgan/claude-code-rust-darwin-arm64": "0.
|
|
40
|
-
"@srothgan/claude-code-rust-darwin-x64": "0.
|
|
41
|
-
"@srothgan/claude-code-rust-linux-x64-gnu": "0.
|
|
42
|
-
"@srothgan/claude-code-rust-linux-arm64-gnu": "0.
|
|
43
|
-
"@srothgan/claude-code-rust-win32-x64-msvc": "0.
|
|
44
|
-
"@srothgan/claude-code-rust-win32-arm64-msvc": "0.
|
|
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": ">=
|
|
47
|
+
"node": ">=24"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|