Skip to content

Commit a45337e

Browse files
rammieclaude
andcommitted
Bump version to 0.1.1, add pre-commit hook, apply cargo fmt
Add git_hooks/pre-commit running cargo fmt --check and cargo test. Apply cargo fmt formatting across the codebase. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 4f684c5 commit a45337e

13 files changed

Lines changed: 266 additions & 294 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rsh"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
edition = "2024"
55
description = "Restricted shell for AI agents — safe, auditable command execution"
66

git_hooks/pre-commit

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
set -e
3+
4+
echo "Running cargo fmt --check..."
5+
cargo fmt --check
6+
7+
echo "Running cargo test..."
8+
cargo test --quiet

src/allowlist.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ pub struct Allowlist {
3737
impl Allowlist {
3838
/// Build the allowlist from the pinned defaults.
3939
pub fn new() -> Self {
40-
let commands: HashSet<String> =
41-
DEFAULT_ALLOWLIST.iter().map(|s| s.to_string()).collect();
40+
let commands: HashSet<String> = DEFAULT_ALLOWLIST.iter().map(|s| s.to_string()).collect();
4241
Self { commands }
4342
}
4443

src/executor.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use std::fs::{File, OpenOptions};
44
use std::io::Write;
55
use std::process::{Command, Stdio};
66

7+
use brush_parser::ParserOptions;
78
use brush_parser::ast::*;
89
use brush_parser::word::{self, Parameter, ParameterExpr, WordPiece};
9-
use brush_parser::ParserOptions;
1010

1111
use crate::allowlist::{self, Allowlist};
1212
use crate::glob as rsh_glob;
@@ -988,9 +988,10 @@ impl Executor {
988988
Ok(local_vars.get(name.as_str()).cloned().unwrap_or_default())
989989
}
990990
Parameter::Special(sp) => match sp {
991-
word::SpecialParameter::LastExitStatus => {
992-
Ok(local_vars.get("?").cloned().unwrap_or_else(|| "0".to_string()))
993-
}
991+
word::SpecialParameter::LastExitStatus => Ok(local_vars
992+
.get("?")
993+
.cloned()
994+
.unwrap_or_else(|| "0".to_string())),
994995
word::SpecialParameter::ProcessId => Ok(std::process::id().to_string()),
995996
_ => Ok(String::new()),
996997
},

src/install.rs

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// Handle `rsh --install claude`: register MCP server and install SessionStart hook.
2-
use serde_json::{json, Value};
2+
use serde_json::{Value, json};
33

44
fn read_json_file(path: &std::path::Path) -> Value {
55
match std::fs::read_to_string(path) {
@@ -23,11 +23,7 @@ fn write_json_file(path: &std::path::Path, value: &Value) {
2323
}
2424
}
2525

26-
fn install_mcp_server(
27-
project_root: &std::path::Path,
28-
allow_redirects: bool,
29-
inherit_env: bool,
30-
) {
26+
fn install_mcp_server(project_root: &std::path::Path, allow_redirects: bool, inherit_env: bool) {
3127
let mcp_path = project_root.join(".mcp.json");
3228

3329
let mut args = vec!["--mcp".to_string()];
@@ -51,7 +47,10 @@ fn install_mcp_server(
5147
};
5248
let servers = root.entry("mcpServers").or_insert_with(|| json!({}));
5349
let Some(servers) = servers.as_object_mut() else {
54-
eprintln!("error: mcpServers in {} must be a JSON object", mcp_path.display());
50+
eprintln!(
51+
"error: mcpServers in {} must be a JSON object",
52+
mcp_path.display()
53+
);
5554
std::process::exit(1);
5655
};
5756
servers.insert("rsh".to_string(), rsh_entry);
@@ -71,29 +70,41 @@ fn install_session_hook(project_root: &std::path::Path) {
7170
let mut settings = read_json_file(&settings_path);
7271

7372
let Some(root) = settings.as_object_mut() else {
74-
eprintln!("error: {} must contain a JSON object", settings_path.display());
73+
eprintln!(
74+
"error: {} must contain a JSON object",
75+
settings_path.display()
76+
);
7577
std::process::exit(1);
7678
};
7779
let hooks = root.entry("hooks").or_insert_with(|| json!({}));
7880
let Some(hooks) = hooks.as_object_mut() else {
79-
eprintln!("error: hooks in {} must be a JSON object", settings_path.display());
81+
eprintln!(
82+
"error: hooks in {} must be a JSON object",
83+
settings_path.display()
84+
);
8085
std::process::exit(1);
8186
};
8287
let session_start = hooks.entry("SessionStart").or_insert_with(|| json!([]));
8388
let Some(arr) = session_start.as_array_mut() else {
84-
eprintln!("error: SessionStart in {} must be an array", settings_path.display());
89+
eprintln!(
90+
"error: SessionStart in {} must be an array",
91+
settings_path.display()
92+
);
8593
std::process::exit(1);
8694
};
8795

8896
// Dedup: don't add a second hook if one already exists
8997
let already_exists = arr.iter().any(|entry| {
90-
entry.get("hooks").and_then(|h| h.as_array()).is_some_and(|hooks_arr| {
91-
hooks_arr.iter().any(|hook| {
92-
hook.get("command")
93-
.and_then(|c| c.as_str())
94-
.is_some_and(|c| c.contains("rsh --prime"))
98+
entry
99+
.get("hooks")
100+
.and_then(|h| h.as_array())
101+
.is_some_and(|hooks_arr| {
102+
hooks_arr.iter().any(|hook| {
103+
hook.get("command")
104+
.and_then(|c| c.as_str())
105+
.is_some_and(|c| c.contains("rsh --prime"))
106+
})
95107
})
96-
})
97108
});
98109

99110
if !already_exists {
@@ -107,11 +118,7 @@ fn install_session_hook(project_root: &std::path::Path) {
107118
eprintln!(" SessionStart hook: {}", settings_path.display());
108119
}
109120

110-
pub fn install_claude(
111-
working_dir: Option<&str>,
112-
allow_redirects: bool,
113-
inherit_env: bool,
114-
) {
121+
pub fn install_claude(working_dir: Option<&str>, allow_redirects: bool, inherit_env: bool) {
115122
let start_dir = crate::resolve_working_dir(working_dir);
116123
let project_root = crate::find_git_root(&start_dir).unwrap_or(start_dir);
117124

src/main.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,25 @@ Patterns for multi-step reads:\n"
9494
);
9595

9696
if has_rg {
97-
s.push_str(" rg \"pattern\" -t rust -C 3 # search by content + file type\n");
97+
s.push_str(
98+
" rg \"pattern\" -t rust -C 3 # search by content + file type\n",
99+
);
98100
s.push_str(" rg \"pattern\" -g \"*.ts\" . # search with glob filter (NOT --include)\n");
99101
s.push_str(" rg \"pattern\" -l . # list matching files only\n");
100102
}
101-
s.push_str(" grep -rn \"pattern\" --include=\"*.rs\" . # search by content + file type\n");
103+
s.push_str(
104+
" grep -rn \"pattern\" --include=\"*.rs\" . # search by content + file type\n",
105+
);
102106
if has_fd {
103107
s.push_str(" fd -e rs | head -20 # find files by extension\n");
104108
}
105-
s.push_str(" tree -L 2 . # overview of directory structure\n");
109+
s.push_str(
110+
" tree -L 2 . # overview of directory structure\n",
111+
);
106112
s.push_str(" grep pattern $(find . -name \"*.rs\") # find by name, then search\n");
107-
s.push_str(" for f in $(find . -name \"*.toml\"); do head -20 \"$f\"; done # find, then inspect\n");
113+
s.push_str(
114+
" for f in $(find . -name \"*.toml\"); do head -20 \"$f\"; done # find, then inspect\n",
115+
);
108116

109117
s.push_str(
110118
"\
@@ -166,7 +174,13 @@ pub fn parse_and_execute(
166174
return Output::error("empty input".to_string());
167175
}
168176

169-
let executor = Executor::new(allowlist, working_dir, allow_redirects, max_output, inherit_env);
177+
let executor = Executor::new(
178+
allowlist,
179+
working_dir,
180+
allow_redirects,
181+
max_output,
182+
inherit_env,
183+
);
170184
executor.execute(&program)
171185
}
172186

@@ -290,9 +304,14 @@ fn main() {
290304
}
291305

292306
match install_target.as_deref() {
293-
Some("claude") => install::install_claude(working_dir.as_deref(), allow_redirects, inherit_env),
307+
Some("claude") => {
308+
install::install_claude(working_dir.as_deref(), allow_redirects, inherit_env)
309+
}
294310
Some(target) => {
295-
eprintln!("error: unknown install target '{}' (supported: claude)", target);
311+
eprintln!(
312+
"error: unknown install target '{}' (supported: claude)",
313+
target
314+
);
296315
std::process::exit(2);
297316
}
298317
None => {}

src/mcp.rs

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/// that runs commands through the parse→validate→execute pipeline.
55
use std::io::{BufRead, Write};
66

7-
use serde_json::{json, Value};
7+
use serde_json::{Value, json};
88

99
use crate::allowlist::Allowlist;
1010

@@ -17,10 +17,13 @@ fn rpc_error(id: &Option<Value>, code: i32, message: &str) -> Value {
1717
}
1818

1919
fn tool_response(id: &Option<Value>, text: String, is_error: bool) -> Value {
20-
rpc_result(id, json!({
21-
"content": [{ "type": "text", "text": text }],
22-
"isError": is_error
23-
}))
20+
rpc_result(
21+
id,
22+
json!({
23+
"content": [{ "type": "text", "text": text }],
24+
"isError": is_error
25+
}),
26+
)
2427
}
2528

2629
/// Run the MCP stdio server. Reads JSON-RPC from stdin, writes responses to stdout.
@@ -68,30 +71,36 @@ pub fn run_server(
6871
}
6972

7073
let response = match method {
71-
"initialize" => rpc_result(&id, json!({
72-
"protocolVersion": "2024-11-05",
73-
"capabilities": { "tools": {} },
74-
"serverInfo": {
75-
"name": "rsh",
76-
"version": env!("CARGO_PKG_VERSION")
77-
}
78-
})),
79-
"tools/list" => rpc_result(&id, json!({
80-
"tools": [{
81-
"name": "rsh",
82-
"description": tool_description,
83-
"inputSchema": {
84-
"type": "object",
85-
"properties": {
86-
"command": {
87-
"type": "string",
88-
"description": "The shell command to run"
89-
}
90-
},
91-
"required": ["command"]
74+
"initialize" => rpc_result(
75+
&id,
76+
json!({
77+
"protocolVersion": "2024-11-05",
78+
"capabilities": { "tools": {} },
79+
"serverInfo": {
80+
"name": "rsh",
81+
"version": env!("CARGO_PKG_VERSION")
9282
}
93-
}]
94-
})),
83+
}),
84+
),
85+
"tools/list" => rpc_result(
86+
&id,
87+
json!({
88+
"tools": [{
89+
"name": "rsh",
90+
"description": tool_description,
91+
"inputSchema": {
92+
"type": "object",
93+
"properties": {
94+
"command": {
95+
"type": "string",
96+
"description": "The shell command to run"
97+
}
98+
},
99+
"required": ["command"]
100+
}
101+
}]
102+
}),
103+
),
95104
"tools/call" => handle_tools_call(
96105
&id,
97106
msg.get("params"),

0 commit comments

Comments
 (0)