mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-08 00:54:49 +08:00
feat: b5-git-aware — batch 5 upstream parity
This commit is contained in:
@@ -86,6 +86,7 @@ const CLI_OPTION_SUGGESTIONS: &[&str] = &[
|
|||||||
"--allowed-tools",
|
"--allowed-tools",
|
||||||
"--resume",
|
"--resume",
|
||||||
"--print",
|
"--print",
|
||||||
|
"--compact",
|
||||||
"-p",
|
"-p",
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -156,8 +157,9 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
output_format,
|
output_format,
|
||||||
allowed_tools,
|
allowed_tools,
|
||||||
permission_mode,
|
permission_mode,
|
||||||
|
compact,
|
||||||
} => LiveCli::new(model, true, allowed_tools, permission_mode)?
|
} => LiveCli::new(model, true, allowed_tools, permission_mode)?
|
||||||
.run_turn_with_output(&prompt, output_format)?,
|
.run_turn_with_output(&prompt, output_format, compact)?,
|
||||||
CliAction::Login { output_format } => run_login(output_format)?,
|
CliAction::Login { output_format } => run_login(output_format)?,
|
||||||
CliAction::Logout { output_format } => run_logout(output_format)?,
|
CliAction::Logout { output_format } => run_logout(output_format)?,
|
||||||
CliAction::Doctor { output_format } => run_doctor(output_format)?,
|
CliAction::Doctor { output_format } => run_doctor(output_format)?,
|
||||||
@@ -225,6 +227,7 @@ enum CliAction {
|
|||||||
output_format: CliOutputFormat,
|
output_format: CliOutputFormat,
|
||||||
allowed_tools: Option<AllowedToolSet>,
|
allowed_tools: Option<AllowedToolSet>,
|
||||||
permission_mode: PermissionMode,
|
permission_mode: PermissionMode,
|
||||||
|
compact: bool,
|
||||||
},
|
},
|
||||||
Login {
|
Login {
|
||||||
output_format: CliOutputFormat,
|
output_format: CliOutputFormat,
|
||||||
@@ -283,6 +286,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
let mut wants_help = false;
|
let mut wants_help = false;
|
||||||
let mut wants_version = false;
|
let mut wants_version = false;
|
||||||
let mut allowed_tool_values = Vec::new();
|
let mut allowed_tool_values = Vec::new();
|
||||||
|
let mut compact = false;
|
||||||
let mut rest = Vec::new();
|
let mut rest = Vec::new();
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
|
|
||||||
@@ -333,6 +337,10 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
permission_mode_override = Some(PermissionMode::DangerFullAccess);
|
permission_mode_override = Some(PermissionMode::DangerFullAccess);
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
|
"--compact" => {
|
||||||
|
compact = true;
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
"-p" => {
|
"-p" => {
|
||||||
// Claw Code compat: -p "prompt" = one-shot prompt
|
// Claw Code compat: -p "prompt" = one-shot prompt
|
||||||
let prompt = args[index + 1..].join(" ");
|
let prompt = args[index + 1..].join(" ");
|
||||||
@@ -346,6 +354,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
allowed_tools: normalize_allowed_tools(&allowed_tool_values)?,
|
allowed_tools: normalize_allowed_tools(&allowed_tool_values)?,
|
||||||
permission_mode: permission_mode_override
|
permission_mode: permission_mode_override
|
||||||
.unwrap_or_else(default_permission_mode),
|
.unwrap_or_else(default_permission_mode),
|
||||||
|
compact,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
"--print" => {
|
"--print" => {
|
||||||
@@ -439,6 +448,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
output_format,
|
output_format,
|
||||||
allowed_tools,
|
allowed_tools,
|
||||||
permission_mode,
|
permission_mode,
|
||||||
|
compact,
|
||||||
}),
|
}),
|
||||||
SkillSlashDispatch::Local => Ok(CliAction::Skills {
|
SkillSlashDispatch::Local => Ok(CliAction::Skills {
|
||||||
args,
|
args,
|
||||||
@@ -461,6 +471,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
output_format,
|
output_format,
|
||||||
allowed_tools,
|
allowed_tools,
|
||||||
permission_mode,
|
permission_mode,
|
||||||
|
compact,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
other if other.starts_with('/') => parse_direct_slash_cli_action(
|
other if other.starts_with('/') => parse_direct_slash_cli_action(
|
||||||
@@ -469,6 +480,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
output_format,
|
output_format,
|
||||||
allowed_tools,
|
allowed_tools,
|
||||||
permission_mode,
|
permission_mode,
|
||||||
|
compact,
|
||||||
),
|
),
|
||||||
_other => Ok(CliAction::Prompt {
|
_other => Ok(CliAction::Prompt {
|
||||||
prompt: rest.join(" "),
|
prompt: rest.join(" "),
|
||||||
@@ -476,6 +488,7 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
|||||||
output_format,
|
output_format,
|
||||||
allowed_tools,
|
allowed_tools,
|
||||||
permission_mode,
|
permission_mode,
|
||||||
|
compact,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,6 +578,7 @@ fn parse_direct_slash_cli_action(
|
|||||||
output_format: CliOutputFormat,
|
output_format: CliOutputFormat,
|
||||||
allowed_tools: Option<AllowedToolSet>,
|
allowed_tools: Option<AllowedToolSet>,
|
||||||
permission_mode: PermissionMode,
|
permission_mode: PermissionMode,
|
||||||
|
compact: bool,
|
||||||
) -> Result<CliAction, String> {
|
) -> Result<CliAction, String> {
|
||||||
let raw = rest.join(" ");
|
let raw = rest.join(" ");
|
||||||
match SlashCommand::parse(&raw) {
|
match SlashCommand::parse(&raw) {
|
||||||
@@ -590,6 +604,7 @@ fn parse_direct_slash_cli_action(
|
|||||||
output_format,
|
output_format,
|
||||||
allowed_tools,
|
allowed_tools,
|
||||||
permission_mode,
|
permission_mode,
|
||||||
|
compact,
|
||||||
}),
|
}),
|
||||||
SkillSlashDispatch::Local => Ok(CliAction::Skills {
|
SkillSlashDispatch::Local => Ok(CliAction::Skills {
|
||||||
args,
|
args,
|
||||||
@@ -3204,13 +3219,28 @@ impl LiveCli {
|
|||||||
&mut self,
|
&mut self,
|
||||||
input: &str,
|
input: &str,
|
||||||
output_format: CliOutputFormat,
|
output_format: CliOutputFormat,
|
||||||
|
compact: bool,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
match output_format {
|
match output_format {
|
||||||
|
CliOutputFormat::Text if compact => self.run_prompt_compact(input),
|
||||||
CliOutputFormat::Text => self.run_turn(input),
|
CliOutputFormat::Text => self.run_turn(input),
|
||||||
CliOutputFormat::Json => self.run_prompt_json(input),
|
CliOutputFormat::Json => self.run_prompt_json(input),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_prompt_compact(&mut self, input: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let (mut runtime, hook_abort_monitor) = self.prepare_turn_runtime(false)?;
|
||||||
|
let mut permission_prompter = CliPermissionPrompter::new(self.permission_mode);
|
||||||
|
let result = runtime.run_turn(input, Some(&mut permission_prompter));
|
||||||
|
hook_abort_monitor.stop();
|
||||||
|
let summary = result?;
|
||||||
|
self.replace_runtime(runtime)?;
|
||||||
|
self.persist_session()?;
|
||||||
|
let final_text = final_assistant_text(&summary);
|
||||||
|
println!("{final_text}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn run_prompt_json(&mut self, input: &str) -> Result<(), Box<dyn std::error::Error>> {
|
fn run_prompt_json(&mut self, input: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let (mut runtime, hook_abort_monitor) = self.prepare_turn_runtime(false)?;
|
let (mut runtime, hook_abort_monitor) = self.prepare_turn_runtime(false)?;
|
||||||
let mut permission_prompter = CliPermissionPrompter::new(self.permission_mode);
|
let mut permission_prompter = CliPermissionPrompter::new(self.permission_mode);
|
||||||
@@ -7007,6 +7037,10 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> {
|
|||||||
out,
|
out,
|
||||||
" --output-format FORMAT Non-interactive output format: text or json"
|
" --output-format FORMAT Non-interactive output format: text or json"
|
||||||
)?;
|
)?;
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" --compact Strip tool call details; print only the final assistant text (text mode only; useful for piping)"
|
||||||
|
)?;
|
||||||
writeln!(
|
writeln!(
|
||||||
out,
|
out,
|
||||||
" --permission-mode MODE Set read-only, workspace-write, or danger-full-access"
|
" --permission-mode MODE Set read-only, workspace-write, or danger-full-access"
|
||||||
@@ -7053,6 +7087,10 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> {
|
|||||||
out,
|
out,
|
||||||
" claw --output-format json prompt \"explain src/main.rs\""
|
" claw --output-format json prompt \"explain src/main.rs\""
|
||||||
)?;
|
)?;
|
||||||
|
writeln!(
|
||||||
|
out,
|
||||||
|
" claw --compact \"summarize Cargo.toml\" | wc -l"
|
||||||
|
)?;
|
||||||
writeln!(
|
writeln!(
|
||||||
out,
|
out,
|
||||||
" claw --allowedTools read,glob \"summarize Cargo.toml\""
|
" claw --allowedTools read,glob \"summarize Cargo.toml\""
|
||||||
@@ -7595,6 +7633,7 @@ mod tests {
|
|||||||
output_format: CliOutputFormat::Text,
|
output_format: CliOutputFormat::Text,
|
||||||
allowed_tools: None,
|
allowed_tools: None,
|
||||||
permission_mode: PermissionMode::DangerFullAccess,
|
permission_mode: PermissionMode::DangerFullAccess,
|
||||||
|
compact: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -7618,10 +7657,56 @@ mod tests {
|
|||||||
output_format: CliOutputFormat::Json,
|
output_format: CliOutputFormat::Json,
|
||||||
allowed_tools: None,
|
allowed_tools: None,
|
||||||
permission_mode: PermissionMode::DangerFullAccess,
|
permission_mode: PermissionMode::DangerFullAccess,
|
||||||
|
compact: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_compact_flag_for_prompt_mode() {
|
||||||
|
// given a bare prompt invocation that includes the --compact flag
|
||||||
|
let _guard = env_lock();
|
||||||
|
std::env::remove_var("RUSTY_CLAUDE_PERMISSION_MODE");
|
||||||
|
let args = vec![
|
||||||
|
"--compact".to_string(),
|
||||||
|
"summarize".to_string(),
|
||||||
|
"this".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// when parse_args interprets the flag
|
||||||
|
let parsed = parse_args(&args).expect("args should parse");
|
||||||
|
|
||||||
|
// then compact mode is propagated and other defaults stay unchanged
|
||||||
|
assert_eq!(
|
||||||
|
parsed,
|
||||||
|
CliAction::Prompt {
|
||||||
|
prompt: "summarize this".to_string(),
|
||||||
|
model: DEFAULT_MODEL.to_string(),
|
||||||
|
output_format: CliOutputFormat::Text,
|
||||||
|
allowed_tools: None,
|
||||||
|
permission_mode: PermissionMode::DangerFullAccess,
|
||||||
|
compact: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prompt_subcommand_defaults_compact_to_false() {
|
||||||
|
// given a `prompt` subcommand invocation without --compact
|
||||||
|
let _guard = env_lock();
|
||||||
|
std::env::remove_var("RUSTY_CLAUDE_PERMISSION_MODE");
|
||||||
|
let args = vec!["prompt".to_string(), "hello".to_string()];
|
||||||
|
|
||||||
|
// when parse_args runs
|
||||||
|
let parsed = parse_args(&args).expect("args should parse");
|
||||||
|
|
||||||
|
// then compact stays false (opt-in flag)
|
||||||
|
match parsed {
|
||||||
|
CliAction::Prompt { compact, .. } => assert!(!compact),
|
||||||
|
other => panic!("expected Prompt action, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolves_model_aliases_in_args() {
|
fn resolves_model_aliases_in_args() {
|
||||||
let _guard = env_lock();
|
let _guard = env_lock();
|
||||||
@@ -7640,6 +7725,7 @@ mod tests {
|
|||||||
output_format: CliOutputFormat::Text,
|
output_format: CliOutputFormat::Text,
|
||||||
allowed_tools: None,
|
allowed_tools: None,
|
||||||
permission_mode: PermissionMode::DangerFullAccess,
|
permission_mode: PermissionMode::DangerFullAccess,
|
||||||
|
compact: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -7791,6 +7877,7 @@ mod tests {
|
|||||||
output_format: CliOutputFormat::Text,
|
output_format: CliOutputFormat::Text,
|
||||||
allowed_tools: None,
|
allowed_tools: None,
|
||||||
permission_mode: crate::default_permission_mode(),
|
permission_mode: crate::default_permission_mode(),
|
||||||
|
compact: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -7898,6 +7985,7 @@ mod tests {
|
|||||||
output_format: CliOutputFormat::Text,
|
output_format: CliOutputFormat::Text,
|
||||||
allowed_tools: None,
|
allowed_tools: None,
|
||||||
permission_mode: PermissionMode::DangerFullAccess,
|
permission_mode: PermissionMode::DangerFullAccess,
|
||||||
|
compact: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -7962,6 +8050,7 @@ mod tests {
|
|||||||
output_format: CliOutputFormat::Text,
|
output_format: CliOutputFormat::Text,
|
||||||
allowed_tools: None,
|
allowed_tools: None,
|
||||||
permission_mode: crate::default_permission_mode(),
|
permission_mode: crate::default_permission_mode(),
|
||||||
|
compact: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -7985,6 +8074,7 @@ mod tests {
|
|||||||
output_format: CliOutputFormat::Text,
|
output_format: CliOutputFormat::Text,
|
||||||
allowed_tools: None,
|
allowed_tools: None,
|
||||||
permission_mode: crate::default_permission_mode(),
|
permission_mode: crate::default_permission_mode(),
|
||||||
|
compact: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
let error = parse_args(&["/status".to_string()])
|
let error = parse_args(&["/status".to_string()])
|
||||||
|
|||||||
159
rust/crates/rusty-claude-cli/tests/compact_output.rs
Normal file
159
rust/crates/rusty-claude-cli/tests/compact_output.rs
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::{Command, Output};
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use mock_anthropic_service::{MockAnthropicService, SCENARIO_PREFIX};
|
||||||
|
|
||||||
|
static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compact_flag_prints_only_final_assistant_text_without_tool_call_details() {
|
||||||
|
// given a workspace pointed at the mock Anthropic service and a fixture file
|
||||||
|
// that the read_file_roundtrip scenario will fetch through a tool call
|
||||||
|
let runtime = tokio::runtime::Runtime::new().expect("tokio runtime should build");
|
||||||
|
let server = runtime
|
||||||
|
.block_on(MockAnthropicService::spawn())
|
||||||
|
.expect("mock service should start");
|
||||||
|
let base_url = server.base_url();
|
||||||
|
|
||||||
|
let workspace = unique_temp_dir("compact-read-file");
|
||||||
|
let config_home = workspace.join("config-home");
|
||||||
|
let home = workspace.join("home");
|
||||||
|
fs::create_dir_all(&workspace).expect("workspace should exist");
|
||||||
|
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||||
|
fs::create_dir_all(&home).expect("home should exist");
|
||||||
|
fs::write(workspace.join("fixture.txt"), "alpha parity line\n").expect("fixture should write");
|
||||||
|
|
||||||
|
// when we run claw in compact text mode against a tool-using scenario
|
||||||
|
let prompt = format!("{SCENARIO_PREFIX}read_file_roundtrip");
|
||||||
|
let output = run_claw(
|
||||||
|
&workspace,
|
||||||
|
&config_home,
|
||||||
|
&home,
|
||||||
|
&base_url,
|
||||||
|
&[
|
||||||
|
"--model",
|
||||||
|
"sonnet",
|
||||||
|
"--permission-mode",
|
||||||
|
"read-only",
|
||||||
|
"--allowedTools",
|
||||||
|
"read_file",
|
||||||
|
"--compact",
|
||||||
|
&prompt,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// then the command exits successfully and stdout contains exactly the final
|
||||||
|
// assistant text with no tool call IDs, JSON envelopes, or spinner output
|
||||||
|
assert!(
|
||||||
|
output.status.success(),
|
||||||
|
"compact run should succeed\nstdout:\n{}\n\nstderr:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr),
|
||||||
|
);
|
||||||
|
let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
|
||||||
|
let trimmed = stdout.trim_end_matches('\n');
|
||||||
|
assert_eq!(
|
||||||
|
trimmed, "read_file roundtrip complete: alpha parity line",
|
||||||
|
"compact stdout should contain only the final assistant text"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!stdout.contains("toolu_"),
|
||||||
|
"compact stdout must not leak tool_use_id ({stdout:?})"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!stdout.contains("\"tool_uses\""),
|
||||||
|
"compact stdout must not leak json envelopes ({stdout:?})"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!stdout.contains("Thinking"),
|
||||||
|
"compact stdout must not include the spinner banner ({stdout:?})"
|
||||||
|
);
|
||||||
|
|
||||||
|
fs::remove_dir_all(&workspace).expect("workspace cleanup should succeed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compact_flag_streaming_text_only_emits_final_message_text() {
|
||||||
|
// given a workspace pointed at the mock Anthropic service running the
|
||||||
|
// streaming_text scenario which only emits a single assistant text block
|
||||||
|
let runtime = tokio::runtime::Runtime::new().expect("tokio runtime should build");
|
||||||
|
let server = runtime
|
||||||
|
.block_on(MockAnthropicService::spawn())
|
||||||
|
.expect("mock service should start");
|
||||||
|
let base_url = server.base_url();
|
||||||
|
|
||||||
|
let workspace = unique_temp_dir("compact-streaming-text");
|
||||||
|
let config_home = workspace.join("config-home");
|
||||||
|
let home = workspace.join("home");
|
||||||
|
fs::create_dir_all(&workspace).expect("workspace should exist");
|
||||||
|
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||||
|
fs::create_dir_all(&home).expect("home should exist");
|
||||||
|
|
||||||
|
// when we invoke claw with --compact for the streaming text scenario
|
||||||
|
let prompt = format!("{SCENARIO_PREFIX}streaming_text");
|
||||||
|
let output = run_claw(
|
||||||
|
&workspace,
|
||||||
|
&config_home,
|
||||||
|
&home,
|
||||||
|
&base_url,
|
||||||
|
&[
|
||||||
|
"--model",
|
||||||
|
"sonnet",
|
||||||
|
"--permission-mode",
|
||||||
|
"read-only",
|
||||||
|
"--compact",
|
||||||
|
&prompt,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// then stdout should be exactly the assistant text followed by a newline
|
||||||
|
assert!(
|
||||||
|
output.status.success(),
|
||||||
|
"compact streaming run should succeed\nstdout:\n{}\n\nstderr:\n{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
String::from_utf8_lossy(&output.stderr),
|
||||||
|
);
|
||||||
|
let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
|
||||||
|
assert_eq!(
|
||||||
|
stdout, "Mock streaming says hello from the parity harness.\n",
|
||||||
|
"compact streaming stdout should contain only the final assistant text"
|
||||||
|
);
|
||||||
|
|
||||||
|
fs::remove_dir_all(&workspace).expect("workspace cleanup should succeed");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_claw(
|
||||||
|
cwd: &std::path::Path,
|
||||||
|
config_home: &std::path::Path,
|
||||||
|
home: &std::path::Path,
|
||||||
|
base_url: &str,
|
||||||
|
args: &[&str],
|
||||||
|
) -> Output {
|
||||||
|
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
|
||||||
|
command
|
||||||
|
.current_dir(cwd)
|
||||||
|
.env_clear()
|
||||||
|
.env("ANTHROPIC_API_KEY", "test-compact-key")
|
||||||
|
.env("ANTHROPIC_BASE_URL", base_url)
|
||||||
|
.env("CLAW_CONFIG_HOME", config_home)
|
||||||
|
.env("HOME", home)
|
||||||
|
.env("NO_COLOR", "1")
|
||||||
|
.env("PATH", "/usr/bin:/bin")
|
||||||
|
.args(args);
|
||||||
|
command.output().expect("claw should launch")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique_temp_dir(label: &str) -> PathBuf {
|
||||||
|
let millis = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("clock should be after epoch")
|
||||||
|
.as_millis();
|
||||||
|
let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
std::env::temp_dir().join(format!(
|
||||||
|
"claw-compact-{label}-{}-{millis}-{counter}",
|
||||||
|
std::process::id()
|
||||||
|
))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user