From b26e09d0d6530f02f52d59063659addcb518616f Mon Sep 17 00:00:00 2001 From: jlaportebot Date: Sat, 27 Jun 2026 23:16:43 -0400 Subject: [PATCH 1/4] fix(npm): add 15 missing npm subcommands to prevent incorrect run injection (fixes #2663) --- src/cmds/js/npm_cmd.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/cmds/js/npm_cmd.rs b/src/cmds/js/npm_cmd.rs index 6066f104fe..c1e730e1c6 100644 --- a/src/cmds/js/npm_cmd.rs +++ b/src/cmds/js/npm_cmd.rs @@ -6,6 +6,7 @@ use anyhow::Result; /// Known npm subcommands that should NOT get "run" injected. /// Shared between production code and tests to avoid drift. +/// Updated to include all 15 missing subcommands from npm 10.8.2 (issue #2663). const NPM_SUBCOMMANDS: &[&str] = &[ "install", "i", @@ -71,6 +72,22 @@ const NPM_SUBCOMMANDS: &[&str] = &[ "start", "stop", "restart", + // Added in fix for #2663 - 15 missing official npm subcommands + "completion", + "edit", + "explore", + "find-dupes", + "help-search", + "hook", + "install-ci-test", + "install-test", + "ll", + "org", + "query", + "run-script", + "sbom", + "shrinkwrap", + "unstar", ]; pub fn run(args: &[String], verbose: u8, skip_env: bool) -> Result { From 487116921affb63cb89cf6b786d41da4452646e8 Mon Sep 17 00:00:00 2001 From: jlaportebot Date: Sat, 27 Jun 2026 23:21:47 -0400 Subject: [PATCH 2/4] fix(ruff): add RUFF_SUBCOMMANDS allowlist to prevent incorrect check injection (fixes #2669) --- src/cmds/python/ruff_cmd.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/cmds/python/ruff_cmd.rs b/src/cmds/python/ruff_cmd.rs index f8a5120356..a945504fa6 100644 --- a/src/cmds/python/ruff_cmd.rs +++ b/src/cmds/python/ruff_cmd.rs @@ -31,10 +31,30 @@ struct RuffDiagnostic { fix: Option, } +/// Known ruff subcommands that should NOT get "check --output-format=json" injected. +/// Based on `ruff --help` output. Only "check" and "format" need special handling; +/// all other subcommands pass through unmodified. +const RUFF_SUBCOMMANDS: &[&str] = &[ + "check", + "format", + "version", + "rule", + "config", + "linter", + "clean", + "server", + "analyze", + "generate-shell-completion", + "help", +]; + pub fn run(args: &[String], verbose: u8) -> Result { + // Determine if this is a "check" invocation (needs JSON output for filtering) + // Only "check" subcommand or path-like arguments trigger the check filter. let is_check = args.is_empty() || args[0] == "check" - || (!args[0].starts_with('-') && args[0] != "format" && args[0] != "version"); + || (!args[0].starts_with('-') + && !RUFF_SUBCOMMANDS.contains(&args[0].as_str())); let is_format = args.iter().any(|a| a == "format"); From ee54a4be9c6d7bf75da022c9f2b862fd0d86544d Mon Sep 17 00:00:00 2001 From: jlaportebot Date: Sun, 28 Jun 2026 12:27:32 -0400 Subject: [PATCH 3/4] fix(git): handle -n combined form in git log (fixes #2665) - Update has_limit_flag to detect -nN form (e.g., -n20) - Update parse_user_limit to parse -nN form - Add tests for -nN form (single and multi-digit) - All existing tests pass Fixes: #2665 --- src/cmds/git/git.rs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/cmds/git/git.rs b/src/cmds/git/git.rs index 0b99f62d05..d2a6827d02 100644 --- a/src/cmds/git/git.rs +++ b/src/cmds/git/git.rs @@ -436,10 +436,15 @@ fn run_log( arg.starts_with("--oneline") || arg.starts_with("--pretty") || arg.starts_with("--format") }); - // Check if user provided limit flag (-N, -n N, --max-count=N, --max-count N) + // Check if user provided limit flag (-N, -n N, -nN, --max-count=N, --max-count N) let has_limit_flag = args.iter().any(|arg| { - (arg.starts_with('-') && arg.chars().nth(1).is_some_and(|c| c.is_ascii_digit())) + // -N form (e.g., -20) + (arg.starts_with('-') && arg.len() > 1 && arg.chars().nth(1).is_some_and(|c| c.is_ascii_digit())) + // -nN form (e.g., -n20) + || (arg.starts_with("-n") && arg.len() > 2 && arg.chars().nth(2).is_some_and(|c| c.is_ascii_digit())) + // -n as standalone token || arg == "-n" + // --max-count forms || arg.starts_with("--max-count") }); @@ -507,7 +512,7 @@ fn run_log( /// Filter git log output: truncate long messages, cap lines /// Parse the user-specified limit from git log args. -/// Handles: -20, -n 20, --max-count=20, --max-count 20 +/// Handles: -20, -n 20, -n, -n20, --max-count=20, --max-count 20 fn parse_user_limit(args: &[String]) -> Option { let mut iter = args.iter(); while let Some(arg) = iter.next() { @@ -520,6 +525,12 @@ fn parse_user_limit(args: &[String]) -> Option { return Some(n); } } + // -n20 (combined -nN form) + if arg.starts_with("-n") && arg.len() > 2 && arg.chars().nth(2).is_some_and(|c| c.is_ascii_digit()) { + if let Ok(n) = arg[2..].parse::() { + return Some(n); + } + } // -n 20 (two-token form) if arg == "-n" { if let Some(next) = iter.next() { @@ -2372,6 +2383,18 @@ A added.rs assert_eq!(parse_user_limit(&args), Some(25)); } + #[test] + fn test_parse_user_limit_n_combined() { + let args: Vec = vec!["-n20".into()]; + assert_eq!(parse_user_limit(&args), Some(20)); + } + + #[test] + fn test_parse_user_limit_n_combined_single_digit() { + let args: Vec = vec!["-n5".into()]; + assert_eq!(parse_user_limit(&args), Some(5)); + } + #[test] fn test_parse_user_limit_none() { let args: Vec = vec!["--oneline".into()]; From 00c4fb287f7011ca4b856b2d1a96a870ac4a2c02 Mon Sep 17 00:00:00 2001 From: jlaportebot Date: Mon, 29 Jun 2026 09:16:13 -0400 Subject: [PATCH 4/4] fix(windows): add PowerShell compatibility fallbacks for ls, wc, proxy, discover, session Addresses issue #1248: RTK Windows PowerShell Compatibility Gaps Changes: - ls.rs: On Windows, detect when 'ls' is not a real executable (PowerShell alias) and suggest 'rtk tree .' as native alternative - wc_cmd.rs: On Windows, detect when 'wc' is not available and provide PowerShell/Git Bash/WSL alternatives - main.rs (proxy): On Windows, warn about PowerShell aliases (ls, dir, echo, pwd, cp, mv, rm, cat, man, which) and built-ins (cd, set, exit, cls, history, alias, function) that aren't real executables - discover/mod.rs: On Windows, when no Claude sessions found, provide helpful info about session storage location (%USERPROFILE%\.claude\projects\) - analytics/session_cmd.rs: Same as above for session command All changes are conditional compilation (cfg(target_os = "windows")) so they only affect Windows users. --- src/analytics/session_cmd.rs | 9 +++++++++ src/cmds/system/ls.rs | 12 +++++++++++- src/cmds/system/wc_cmd.rs | 14 +++++++++++++- src/discover/mod.rs | 7 +++++++ src/main.rs | 25 +++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/analytics/session_cmd.rs b/src/analytics/session_cmd.rs index c36a42c25a..0f6b6f5ca0 100644 --- a/src/analytics/session_cmd.rs +++ b/src/analytics/session_cmd.rs @@ -65,6 +65,15 @@ pub fn run(_verbose: u8) -> Result<()> { if sessions.is_empty() { println!("No Claude Code sessions found in the last 30 days."); println!("Make sure Claude Code has been used at least once."); + + // On Windows, the session directory may not exist or be in a different location + if cfg!(target_os = "windows") { + println!("\n[rtk] Note: On Windows, Claude Code sessions are stored in:"); + println!(" %USERPROFILE%\\.claude\\projects\\"); + println!("If this directory doesn't exist, Claude Code may not have been run yet,"); + println!("or the sessions may be stored in a different location."); + } + return Ok(()); } diff --git a/src/cmds/system/ls.rs b/src/cmds/system/ls.rs index 98eb364479..edb6264451 100644 --- a/src/cmds/system/ls.rs +++ b/src/cmds/system/ls.rs @@ -3,7 +3,7 @@ use super::constants::NOISE_DIRS; use crate::core::runner::{self, RunOptions}; use crate::core::truncate::{reduced, CAP_WARNINGS}; -use crate::core::utils::resolved_command; +use crate::core::utils::{resolved_command, tool_exists}; use anyhow::Result; use lazy_static::lazy_static; use regex::Regex; @@ -48,6 +48,16 @@ pub fn run(args: &[String], verbose: u8) -> Result { .map(|s| s.as_str()) .collect(); + // On Windows, `ls` is a PowerShell alias, not an executable on PATH. + // Suggest `rtk tree` as a native alternative. + if cfg!(target_os = "windows") && !tool_exists("ls") { + eprintln!( + "[rtk] Note: 'ls' is a PowerShell alias on Windows, not a native executable.\n\ + For directory listings on Windows, use 'rtk tree .' instead." + ); + // Fall through to let resolved_command fail with its own message + } + let mut cmd = resolved_command("ls"); cmd.env("LC_ALL", "C"); cmd.arg("-la"); diff --git a/src/cmds/system/wc_cmd.rs b/src/cmds/system/wc_cmd.rs index 78c3924411..d927bf120a 100644 --- a/src/cmds/system/wc_cmd.rs +++ b/src/cmds/system/wc_cmd.rs @@ -7,10 +7,22 @@ /// - `wc -c file.py` → `978` /// - `wc -l *.py` → table with common path prefix stripped use crate::core::runner::{self, RunOptions}; -use crate::core::utils::resolved_command; +use crate::core::utils::{resolved_command, tool_exists}; use anyhow::Result; pub fn run(args: &[String], verbose: u8) -> Result { + // On Windows, `wc` is not a standard executable. Provide a helpful message. + if cfg!(target_os = "windows") && !tool_exists("wc") { + eprintln!( + "[rtk] Note: 'wc' is not available as a standard Windows executable.\n\ + For line/word/char counts on Windows, consider:\n\ + - PowerShell: Get-Content file.txt | Measure-Object -Line -Word -Character\n\ + - Git Bash/WSL: wc (available in Unix-like environments)\n\ + - Native alternative: find /c /v \"\" file.txt (line count only)" + ); + // Fall through to let resolved_command fail with its own message + } + let mut cmd = resolved_command("wc"); for arg in args { cmd.arg(arg); diff --git a/src/discover/mod.rs b/src/discover/mod.rs index 98d3eae765..71ee1da0c1 100644 --- a/src/discover/mod.rs +++ b/src/discover/mod.rs @@ -72,6 +72,13 @@ pub fn run( } } + // On Windows, if no sessions found, provide helpful info about session location + if cfg!(target_os = "windows") && sessions.is_empty() { + eprintln!( + "\n[rtk] Note: On Windows, Claude Code sessions are stored in:\n %USERPROFILE%\\.claude\\projects\\\nIf this directory doesn't exist, Claude Code may not have been run yet,\nor sessions may be in a different location." + ); + } + let mut total_commands: usize = 0; let mut already_rtk: usize = 0; let mut parse_errors: usize = 0; diff --git a/src/main.rs b/src/main.rs index d13bb448cf..ebf896201c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2370,6 +2370,31 @@ fn run_cli() -> Result { ) }; + // On Windows, warn about PowerShell aliases and built-ins that aren't real executables. + // These commands will fail because RTK resolves executables on PATH, not shell aliases/built-ins. + if cfg!(target_os = "windows") { + let powershell_aliases = [ + "ls", "dir", "echo", "pwd", "cp", "mv", "rm", "cat", "man", "which", + ]; + let powershell_builtins = + ["cd", "set", "exit", "cls", "history", "alias", "function"]; + + if powershell_aliases.contains(&cmd_name.as_str()) { + eprintln!( + "[rtk] Note: '{}' is a PowerShell alias, not a native executable on PATH.\n\ + rtk proxy only works with real executables. Try running the command directly in PowerShell,\n\ + or use the native Windows equivalent.", + cmd_name + ); + } else if powershell_builtins.contains(&cmd_name.as_str()) { + eprintln!( + "[rtk] Note: '{}' is a PowerShell built-in command, not an executable on PATH.\n\ + rtk proxy cannot execute shell built-ins. Run it directly in PowerShell instead.", + cmd_name + ); + } + } + if cli.verbose > 0 { eprintln!("Proxy mode: {} {}", cmd_name, cmd_args.join(" ")); }