Merge remote-tracking branch 'origin/main' into feat/glm-provider

Resolved conflicts in:
- Cargo.toml: kept both `ring` (JWT auth) and `prost` (protobuf) dependencies
- src/onboard/wizard.rs: accepted main branch version
- src/providers/mod.rs: accepted main branch version
- Cargo.lock: accepted main branch version

Note: The custom `glm::GlmProvider` from this PR was replaced with
main's OpenAiCompatibleProvider approach for GLM, which uses base URLs.
The main purpose of this PR is Windows daemon support via Task Scheduler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
argenis de la rosa 2026-02-17 13:27:58 -05:00
commit 34af6a223a
269 changed files with 68574 additions and 2541 deletions

View file

@ -453,9 +453,9 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> Result<()> {
/// Handle the `skills` CLI command
#[allow(clippy::too_many_lines)]
pub fn handle_command(command: super::SkillCommands, workspace_dir: &Path) -> Result<()> {
pub fn handle_command(command: crate::SkillCommands, workspace_dir: &Path) -> Result<()> {
match command {
super::SkillCommands::List => {
crate::SkillCommands::List => {
let skills = load_skills(workspace_dir);
if skills.is_empty() {
println!("No skills installed.");
@ -493,13 +493,13 @@ pub fn handle_command(command: super::SkillCommands, workspace_dir: &Path) -> Re
println!();
Ok(())
}
super::SkillCommands::Install { source } => {
crate::SkillCommands::Install { source } => {
println!("Installing skill from: {source}");
let skills_path = skills_dir(workspace_dir);
std::fs::create_dir_all(&skills_path)?;
if source.starts_with("http") || source.contains("github.com") {
if source.starts_with("https://") || source.starts_with("http://") {
// Git clone
let output = std::process::Command::new("git")
.args(["clone", "--depth", "1", &source])
@ -584,8 +584,24 @@ pub fn handle_command(command: super::SkillCommands, workspace_dir: &Path) -> Re
Ok(())
}
super::SkillCommands::Remove { name } => {
crate::SkillCommands::Remove { name } => {
// Reject path traversal attempts
if name.contains("..") || name.contains('/') || name.contains('\\') {
anyhow::bail!("Invalid skill name: {name}");
}
let skill_path = skills_dir(workspace_dir).join(&name);
// Verify the resolved path is actually inside the skills directory
let canonical_skills = skills_dir(workspace_dir)
.canonicalize()
.unwrap_or_else(|_| skills_dir(workspace_dir));
if let Ok(canonical_skill) = skill_path.canonicalize() {
if !canonical_skill.starts_with(&canonical_skills) {
anyhow::bail!("Skill path escapes skills directory: {name}");
}
}
if !skill_path.exists() {
anyhow::bail!("Skill not found: {name}");
}