mirror of
https://github.com/instructkr/claw-code.git
synced 2026-11-02 00:00:10 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a1c4b29c7 | ||
|
|
0663c80ba1 | ||
|
|
e97ddc767f | ||
|
|
a6c32edcf6 |
No files matched your search
@@ -25,6 +25,8 @@ on:
|
|||||||
- tests/test_roadmap_helpers.py
|
- tests/test_roadmap_helpers.py
|
||||||
- docs/**
|
- docs/**
|
||||||
- rust/**
|
- rust/**
|
||||||
|
- flake.nix
|
||||||
|
- .envrc
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -47,6 +49,8 @@ on:
|
|||||||
- tests/test_roadmap_helpers.py
|
- tests/test_roadmap_helpers.py
|
||||||
- docs/**
|
- docs/**
|
||||||
- rust/**
|
- rust/**
|
||||||
|
- flake.nix
|
||||||
|
- .envrc
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -15,3 +15,7 @@ status-help.txt
|
|||||||
.port_sessions/
|
.port_sessions/
|
||||||
# Local agent session state
|
# Local agent session state
|
||||||
.gjc/
|
.gjc/
|
||||||
|
# Nix build outputs and direnv
|
||||||
|
.envrc.local
|
||||||
|
result
|
||||||
|
result-*
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
# Nix flake development environment
|
||||||
|
|
||||||
|
This repository includes a **`flake.nix`** for reproducible development environments on NixOS and systems with Nix installed.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Nix** (version 2.4 or later with flakes support enabled)
|
||||||
|
- **direnv** (optional, for automatic environment loading via `.envrc`)
|
||||||
|
|
||||||
|
To verify Nix is installed and flakes are enabled:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix --version
|
||||||
|
nix flake --help
|
||||||
|
```
|
||||||
|
|
||||||
|
If flakes are not enabled, add this to your `~/.config/nix/nix.conf`:
|
||||||
|
|
||||||
|
```
|
||||||
|
experimental-features = nix-command flakes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
### Enter the development environment
|
||||||
|
|
||||||
|
**Option 1: Using `nix flake`** (without direnv)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix flake update # Update inputs (optional, pins nixpkgs)
|
||||||
|
nix develop # Enter default dev shell
|
||||||
|
nix develop .#full # Enter full dev shell with extra tools
|
||||||
|
exit # Leave the shell
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Using direnv** (automatic on directory entry)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
direnv allow # Trust the .envrc file (one-time setup)
|
||||||
|
cd . # Direnv reloads the flake automatically
|
||||||
|
```
|
||||||
|
|
||||||
|
When you leave the repository directory, the environment is automatically unloaded.
|
||||||
|
|
||||||
|
## Development shells
|
||||||
|
|
||||||
|
The flake provides two development shells:
|
||||||
|
|
||||||
|
### `default` (minimal)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix develop
|
||||||
|
```
|
||||||
|
|
||||||
|
Includes:
|
||||||
|
- Rust (stable, latest)
|
||||||
|
- Rust source code (`rust-src`)
|
||||||
|
- `cargo-edit` (dependency management helpers)
|
||||||
|
- `cargo-expand` (macro expansion tool)
|
||||||
|
- `cargo-watch` (file watcher for incremental builds)
|
||||||
|
- Build tools: `pkg-config`, `openssl`, `git`, `ca-certificates`
|
||||||
|
|
||||||
|
### `full` (with extra tooling)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix develop .#full
|
||||||
|
```
|
||||||
|
|
||||||
|
Includes everything from `default` plus:
|
||||||
|
- `clippy` (linter)
|
||||||
|
- `rustfmt` (code formatter)
|
||||||
|
- `mold` (fast linker)
|
||||||
|
- `lldb` (debugger)
|
||||||
|
|
||||||
|
## Building the claw binary
|
||||||
|
|
||||||
|
### With the dev environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix develop
|
||||||
|
cd rust
|
||||||
|
cargo build -p rusty-claude-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### Directly with `nix build`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix build .#claw # Build the claw binary
|
||||||
|
nix build # Same as .#claw (default package)
|
||||||
|
```
|
||||||
|
|
||||||
|
The compiled binary is symlinked to `./result/bin/claw`.
|
||||||
|
|
||||||
|
## Running common tasks
|
||||||
|
|
||||||
|
Once in the dev shell:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Format code
|
||||||
|
cd rust
|
||||||
|
cargo fmt
|
||||||
|
|
||||||
|
# Run clippy linter
|
||||||
|
cargo clippy --workspace --all-targets -- -D warnings
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
cargo test --workspace
|
||||||
|
|
||||||
|
# Run tests for a specific crate
|
||||||
|
cargo test -p rusty-claude-cli
|
||||||
|
|
||||||
|
# Build with optimizations
|
||||||
|
cargo build --release -p rusty-claude-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
The flake automatically sets:
|
||||||
|
|
||||||
|
- `RUSTUP_TOOLCHAIN=stable` — uses stable Rust from nixpkgs
|
||||||
|
- Standard Rust build flags are inherited from the environment
|
||||||
|
|
||||||
|
For development, the minimal dev shell includes a helpful message on entry:
|
||||||
|
|
||||||
|
```
|
||||||
|
Claw Code development environment
|
||||||
|
Run 'cargo build -p rusty-claude-cli' to build the claw binary
|
||||||
|
Run 'cargo test --workspace' to run tests
|
||||||
|
Run 'scripts/fmt.sh' to format code
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### `command not found: nix`
|
||||||
|
|
||||||
|
Nix is not installed. Install it from [nixos.org/download.html](https://nixos.org/download.html).
|
||||||
|
|
||||||
|
### `error: experimental Nix feature 'flakes' is disabled`
|
||||||
|
|
||||||
|
Enable flakes in your Nix configuration (`~/.config/nix/nix.conf` or `/etc/nix/nix.conf`):
|
||||||
|
|
||||||
|
```
|
||||||
|
experimental-features = nix-command flakes
|
||||||
|
```
|
||||||
|
|
||||||
|
### `error: ... does not provide attribute 'claw'`
|
||||||
|
|
||||||
|
The flake structure may have changed. Try updating:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix flake update
|
||||||
|
nix flake check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rebuilds are slow after updating dependencies
|
||||||
|
|
||||||
|
Cargo will recompile dependencies if `Cargo.lock` changes. This is expected:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix build --rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
### `cache lock file needs to be updated`
|
||||||
|
|
||||||
|
The Cargo.lock in `rust/` needs to be committed. Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd rust
|
||||||
|
cargo generate-lockfile
|
||||||
|
git add Cargo.lock
|
||||||
|
git commit -m "update: Cargo.lock"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with other tools
|
||||||
|
|
||||||
|
### IDE integration (VS Code, IntelliJ, etc.)
|
||||||
|
|
||||||
|
Many IDEs can detect and use Nix environments. Direnv integration is recommended:
|
||||||
|
|
||||||
|
1. Install the direnv extension
|
||||||
|
2. Enable `direnv` shell integration in your IDE
|
||||||
|
3. Run `direnv allow` in the repository
|
||||||
|
|
||||||
|
Your IDE will automatically use the correct Rust toolchain and dependencies from the flake.
|
||||||
|
|
||||||
|
### CI/CD
|
||||||
|
|
||||||
|
For GitHub Actions, consider using [cachix/install-nix-action](https://github.com/cachix/install-nix-action) to set up Nix on the runner:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: cachix/install-nix-action@v18
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
extra_nix_config: "experimental-features = nix-command flakes"
|
||||||
|
|
||||||
|
- run: nix develop -c cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
## More information
|
||||||
|
|
||||||
|
- [NixOS flakes documentation](https://nixos.wiki/wiki/Flakes)
|
||||||
|
- [Direnv documentation](https://direnv.net/)
|
||||||
|
- [Rust on Nix](https://github.com/oxalica/rust-overlay)
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
{
|
||||||
|
description = "claw-code: Claude Code CLI agent harness in Rust";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
rust-overlay = {
|
||||||
|
url = "github:oxalica/rust-overlay";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
overlays = [ (import rust-overlay) ];
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system overlays;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Build inputs needed for Rust and the project
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
openssl
|
||||||
|
ca-certificates
|
||||||
|
git
|
||||||
|
];
|
||||||
|
|
||||||
|
# Runtime inputs for the dev environment
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
(rust-bin.stable.latest.default.override {
|
||||||
|
extensions = [ "rust-src" ];
|
||||||
|
targets = [ ];
|
||||||
|
})
|
||||||
|
cargo-edit
|
||||||
|
cargo-expand
|
||||||
|
cargo-watch
|
||||||
|
];
|
||||||
|
|
||||||
|
# Derivation to build the claw binary
|
||||||
|
clawBinary = pkgs.rustPlatform.buildRustPackage {
|
||||||
|
pname = "claw";
|
||||||
|
version = (builtins.fromTOML (builtins.readFile ./rust/Cargo.toml)).workspace.package.version;
|
||||||
|
|
||||||
|
src = ./rust;
|
||||||
|
|
||||||
|
# Hash of all the dependencies in Cargo.lock
|
||||||
|
# This needs to be updated when dependencies change
|
||||||
|
# Run: nix flake update
|
||||||
|
cargoLock = {
|
||||||
|
lockFile = ./rust/Cargo.lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
cargoBuildFlags = [ "-p" "rusty-claude-cli" ];
|
||||||
|
|
||||||
|
buildInputs = buildInputs ++ (with pkgs; [
|
||||||
|
openssl
|
||||||
|
]);
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = with pkgs.lib; {
|
||||||
|
description = "Claude Code CLI: agent harness with Rust implementation";
|
||||||
|
homepage = "https://github.com/ultraworkers/claw-code";
|
||||||
|
license = licenses.mit;
|
||||||
|
platforms = platforms.unix;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Provide the claw binary as a package
|
||||||
|
packages.claw = clawBinary;
|
||||||
|
packages.default = clawBinary;
|
||||||
|
|
||||||
|
# Development shell for contributors
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
name = "claw-dev";
|
||||||
|
inherit buildInputs nativeBuildInputs;
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
echo "Claw Code development environment"
|
||||||
|
echo "Run 'cargo build -p rusty-claude-cli' to build the claw binary"
|
||||||
|
echo "Run 'cargo test --workspace' to run tests"
|
||||||
|
echo "Run 'scripts/fmt.sh' to format code"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Make sure we have the right Rust toolchain
|
||||||
|
RUSTUP_TOOLCHAIN = "stable";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Alternative shell with additional development tools
|
||||||
|
devShells.full = pkgs.mkShell {
|
||||||
|
name = "claw-dev-full";
|
||||||
|
inherit buildInputs nativeBuildInputs;
|
||||||
|
|
||||||
|
nativeBuildInputs = nativeBuildInputs ++ (with pkgs; [
|
||||||
|
clippy
|
||||||
|
rustfmt
|
||||||
|
mold
|
||||||
|
lldb
|
||||||
|
]);
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
echo "Claw Code full development environment (with extra tools)"
|
||||||
|
echo "Available tools: clippy, rustfmt, mold (linker), lldb (debugger)"
|
||||||
|
'';
|
||||||
|
|
||||||
|
RUSTUP_TOOLCHAIN = "stable";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1131,6 +1131,9 @@ pub enum SlashCommand {
|
|||||||
SecurityReview,
|
SecurityReview,
|
||||||
Keybindings,
|
Keybindings,
|
||||||
PrivacySettings,
|
PrivacySettings,
|
||||||
|
Workspace {
|
||||||
|
path: Option<String>,
|
||||||
|
},
|
||||||
Plan {
|
Plan {
|
||||||
mode: Option<String>,
|
mode: Option<String>,
|
||||||
},
|
},
|
||||||
@@ -1272,6 +1275,7 @@ impl SlashCommand {
|
|||||||
Self::SecurityReview => "/security-review",
|
Self::SecurityReview => "/security-review",
|
||||||
Self::Keybindings => "/keybindings",
|
Self::Keybindings => "/keybindings",
|
||||||
Self::PrivacySettings => "/privacy-settings",
|
Self::PrivacySettings => "/privacy-settings",
|
||||||
|
Self::Workspace { .. } => "/workspace",
|
||||||
Self::Plan { .. } => "/plan",
|
Self::Plan { .. } => "/plan",
|
||||||
Self::Review { .. } => "/review",
|
Self::Review { .. } => "/review",
|
||||||
Self::Tasks { .. } => "/tasks",
|
Self::Tasks { .. } => "/tasks",
|
||||||
@@ -1405,6 +1409,9 @@ pub fn validate_slash_command_input(
|
|||||||
validate_no_args(command, &args)?;
|
validate_no_args(command, &args)?;
|
||||||
SlashCommand::Setup
|
SlashCommand::Setup
|
||||||
}
|
}
|
||||||
|
"workspace" | "cwd" => SlashCommand::Workspace {
|
||||||
|
path: optional_single_arg(command, &args, "[path]")?,
|
||||||
|
},
|
||||||
"login" | "logout" => {
|
"login" | "logout" => {
|
||||||
return Err(command_error(
|
return Err(command_error(
|
||||||
"This auth flow was removed. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN instead.",
|
"This auth flow was removed. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN instead.",
|
||||||
@@ -5342,6 +5349,7 @@ pub fn handle_slash_command(
|
|||||||
| SlashCommand::Cost
|
| SlashCommand::Cost
|
||||||
| SlashCommand::Resume { .. }
|
| SlashCommand::Resume { .. }
|
||||||
| SlashCommand::Config { .. }
|
| SlashCommand::Config { .. }
|
||||||
|
| SlashCommand::Workspace { .. }
|
||||||
| SlashCommand::Mcp { .. }
|
| SlashCommand::Mcp { .. }
|
||||||
| SlashCommand::Memory
|
| SlashCommand::Memory
|
||||||
| SlashCommand::Init
|
| SlashCommand::Init
|
||||||
@@ -6119,6 +6127,24 @@ mod tests {
|
|||||||
assert_eq!(suggest_slash_commands("zzz", 3), Vec::<String>::new());
|
assert_eq!(suggest_slash_commands("zzz", 3), Vec::<String>::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_workspace_slash_command_and_alias() {
|
||||||
|
let workspace = validate_slash_command_input("/workspace")
|
||||||
|
.expect("workspace should parse")
|
||||||
|
.expect("workspace should be a slash command");
|
||||||
|
assert_eq!(workspace, SlashCommand::Workspace { path: None });
|
||||||
|
|
||||||
|
let cwd = validate_slash_command_input("/cwd src")
|
||||||
|
.expect("cwd alias should parse")
|
||||||
|
.expect("cwd alias should be a slash command");
|
||||||
|
assert_eq!(
|
||||||
|
cwd,
|
||||||
|
SlashCommand::Workspace {
|
||||||
|
path: Some("src".to_string()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compacts_sessions_via_slash_command() {
|
fn compacts_sessions_via_slash_command() {
|
||||||
let mut session = Session::new();
|
let mut session = Session::new();
|
||||||
|
|||||||
@@ -6715,6 +6715,49 @@ fn run_resume_command(
|
|||||||
message: Some(render_memory_report()?),
|
message: Some(render_memory_report()?),
|
||||||
json: Some(render_memory_json()?),
|
json: Some(render_memory_json()?),
|
||||||
}),
|
}),
|
||||||
|
SlashCommand::Workspace { path } => {
|
||||||
|
let cwd_before = env::current_dir()?;
|
||||||
|
let workspace_root = session
|
||||||
|
.workspace_root()
|
||||||
|
.map(Path::to_path_buf)
|
||||||
|
.unwrap_or_else(|| cwd_before.clone());
|
||||||
|
let workspace_root = canonicalize_or_clone(&workspace_root);
|
||||||
|
let changed = if let Some(path) = path.as_deref() {
|
||||||
|
let requested_path = Path::new(path);
|
||||||
|
let resolved_path = if requested_path.is_absolute() {
|
||||||
|
requested_path.to_path_buf()
|
||||||
|
} else {
|
||||||
|
cwd_before.join(requested_path)
|
||||||
|
};
|
||||||
|
let resolved_path = canonicalize_or_clone(&resolved_path);
|
||||||
|
if !resolved_path.starts_with(&workspace_root) {
|
||||||
|
return Err(format!(
|
||||||
|
"workspace_change_outside_root: `{}` is outside the current workspace root `{}`.\nUse `claw --cwd {}` to start a new session there.",
|
||||||
|
resolved_path.display(),
|
||||||
|
workspace_root.display(),
|
||||||
|
resolved_path.display(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
env::set_current_dir(&resolved_path)?;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let cwd_after = env::current_dir()?;
|
||||||
|
let message =
|
||||||
|
render_workspace_report(&cwd_after, &workspace_root, &session.session_id, changed);
|
||||||
|
Ok(ResumeCommandOutcome {
|
||||||
|
session: session.clone(),
|
||||||
|
message: Some(message),
|
||||||
|
json: Some(workspace_report_json(
|
||||||
|
&cwd_after,
|
||||||
|
&workspace_root,
|
||||||
|
&session.session_id,
|
||||||
|
changed,
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
SlashCommand::Init => {
|
SlashCommand::Init => {
|
||||||
// #142: run the init once, then render both text + structured JSON
|
// #142: run the init once, then render both text + structured JSON
|
||||||
// from the same InitReport so both surfaces stay in sync.
|
// from the same InitReport so both surfaces stay in sync.
|
||||||
@@ -8216,6 +8259,7 @@ impl LiveCli {
|
|||||||
Self::print_config(section.as_deref())?;
|
Self::print_config(section.as_deref())?;
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
SlashCommand::Workspace { path } => self.handle_workspace_command(path.as_deref())?,
|
||||||
SlashCommand::Mcp { action, target } => {
|
SlashCommand::Mcp { action, target } => {
|
||||||
let args = match (action.as_deref(), target.as_deref()) {
|
let args = match (action.as_deref(), target.as_deref()) {
|
||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
@@ -8374,6 +8418,51 @@ impl LiveCli {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_workspace_command(
|
||||||
|
&mut self,
|
||||||
|
target: Option<&str>,
|
||||||
|
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
let current_dir = env::current_dir()?;
|
||||||
|
let workspace_root = self
|
||||||
|
.runtime
|
||||||
|
.session()
|
||||||
|
.workspace_root()
|
||||||
|
.map(Path::to_path_buf)
|
||||||
|
.unwrap_or_else(|| current_dir.clone());
|
||||||
|
let workspace_root = canonicalize_or_clone(&workspace_root);
|
||||||
|
|
||||||
|
if let Some(target) = target {
|
||||||
|
let requested_path = Path::new(target);
|
||||||
|
let resolved_path = if requested_path.is_absolute() {
|
||||||
|
requested_path.to_path_buf()
|
||||||
|
} else {
|
||||||
|
current_dir.join(requested_path)
|
||||||
|
};
|
||||||
|
let resolved_path = canonicalize_or_clone(&resolved_path);
|
||||||
|
if !resolved_path.starts_with(&workspace_root) {
|
||||||
|
return Err(format!(
|
||||||
|
"workspace_change_outside_root: `{}` is outside the current workspace root `{}`.\nUse `claw --cwd {}` to start a new session there.",
|
||||||
|
resolved_path.display(),
|
||||||
|
workspace_root.display(),
|
||||||
|
resolved_path.display(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
env::set_current_dir(&resolved_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
render_workspace_report(
|
||||||
|
&env::current_dir()?,
|
||||||
|
&workspace_root,
|
||||||
|
&self.session.id,
|
||||||
|
target.is_some(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Ok(target.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
fn record_prompt_history(&mut self, prompt: &str) {
|
fn record_prompt_history(&mut self, prompt: &str) {
|
||||||
let timestamp_ms = std::time::SystemTime::now()
|
let timestamp_ms = std::time::SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
@@ -9214,6 +9303,41 @@ fn new_cli_session() -> Result<Session, Box<dyn std::error::Error>> {
|
|||||||
Ok(Session::new().with_workspace_root(env::current_dir()?))
|
Ok(Session::new().with_workspace_root(env::current_dir()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn canonicalize_or_clone(path: &Path) -> PathBuf {
|
||||||
|
fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_workspace_report(
|
||||||
|
cwd: &Path,
|
||||||
|
workspace_root: &Path,
|
||||||
|
session_id: &str,
|
||||||
|
changed: bool,
|
||||||
|
) -> String {
|
||||||
|
let action = if changed { "change" } else { "show" };
|
||||||
|
format!(
|
||||||
|
"Workspace\n Action {action}\n Session {session_id}\n Workspace root {}\n Current directory {}",
|
||||||
|
workspace_root.display(),
|
||||||
|
cwd.display(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn workspace_report_json(
|
||||||
|
cwd: &Path,
|
||||||
|
workspace_root: &Path,
|
||||||
|
session_id: &str,
|
||||||
|
changed: bool,
|
||||||
|
) -> Value {
|
||||||
|
serde_json::json!({
|
||||||
|
"kind": "workspace",
|
||||||
|
"action": if changed { "change" } else { "show" },
|
||||||
|
"status": "ok",
|
||||||
|
"session_id": session_id,
|
||||||
|
"workspace_root": workspace_root.display().to_string(),
|
||||||
|
"current_directory": cwd.display().to_string(),
|
||||||
|
"changed": changed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn create_managed_session_handle(
|
fn create_managed_session_handle(
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
) -> Result<SessionHandle, Box<dyn std::error::Error>> {
|
) -> Result<SessionHandle, Box<dyn std::error::Error>> {
|
||||||
|
|||||||
@@ -441,6 +441,9 @@ fn status_and_sandbox_emit_json_when_requested() {
|
|||||||
|
|
||||||
// #831: direct resume-safe slash commands should use the same local CliAction
|
// #831: direct resume-safe slash commands should use the same local CliAction
|
||||||
// JSON surfaces as their bare subcommands, not interactive_only guidance.
|
// JSON surfaces as their bare subcommands, not interactive_only guidance.
|
||||||
|
// Note: /sandbox status depends on kernel support for user namespaces.
|
||||||
|
// On systems with working user namespaces the status is "ok".
|
||||||
|
// On systems without user namespaces the status is "warn" (#731).
|
||||||
#[test]
|
#[test]
|
||||||
fn direct_resume_safe_slash_commands_route_to_local_json_actions_831() {
|
fn direct_resume_safe_slash_commands_route_to_local_json_actions_831() {
|
||||||
let root = unique_temp_dir("direct-resume-safe-slash-831");
|
let root = unique_temp_dir("direct-resume-safe-slash-831");
|
||||||
@@ -451,11 +454,11 @@ fn direct_resume_safe_slash_commands_route_to_local_json_actions_831() {
|
|||||||
.output()
|
.output()
|
||||||
.expect("git init should launch");
|
.expect("git init should launch");
|
||||||
|
|
||||||
for (command, expected_kind, expected_status) in [
|
for (command, expected_kind, expected_statuses) in [
|
||||||
("/version", "version", "ok"),
|
("/version", "version", &["ok"][..]),
|
||||||
("/sandbox", "sandbox", "warn"),
|
("/sandbox", "sandbox", &["ok", "warn"][..]),
|
||||||
("/diff", "diff", "ok"),
|
("/diff", "diff", &["ok"][..]),
|
||||||
("/status", "status", "ok"),
|
("/status", "status", &["ok"][..]),
|
||||||
] {
|
] {
|
||||||
let output = run_claw(&root, &["--output-format", "json", command], &[]);
|
let output = run_claw(&root, &["--output-format", "json", command], &[]);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -470,9 +473,9 @@ fn direct_resume_safe_slash_commands_route_to_local_json_actions_831() {
|
|||||||
.unwrap_or_else(|_| panic!("{command} must emit JSON (#831), got: {stdout:?}"));
|
.unwrap_or_else(|_| panic!("{command} must emit JSON (#831), got: {stdout:?}"));
|
||||||
|
|
||||||
assert_eq!(parsed["kind"], expected_kind, "{command} kind: {parsed}");
|
assert_eq!(parsed["kind"], expected_kind, "{command} kind: {parsed}");
|
||||||
assert_eq!(
|
assert!(
|
||||||
parsed["status"], expected_status,
|
expected_statuses.contains(&parsed["status"].as_str().unwrap_or("")),
|
||||||
"{command} status: {parsed}"
|
"{command} status must be one of {expected_statuses:?}: {parsed}"
|
||||||
);
|
);
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
parsed["error_kind"], "interactive_only",
|
parsed["error_kind"], "interactive_only",
|
||||||
|
|||||||
Reference in new issue
Block a user