use anyhow::{bail, Context, Result};
use std::process::{Command, Stdio};
pub fn run(
description: Option<&str>,
issue: Option<i64>,
gh_issue: Option<i64>,
continue_slug: Option<&str>,
) -> Result<()> {
if std::env::var("CLAUDE_CODE").is_ok() || std::env::var("CLAUDECODE").is_ok() {
eprintln!("Already inside Claude Code \u{2014} use /design instead.");
std::process::exit(1);
}
let claude_available = Command::new("which")
.arg("claude")
.output()
.is_ok_and(|o| o.status.success());
if !claude_available {
bail!(
"`claude` CLI not found. Install it:\n\n \
npm install -g @anthropic-ai/claude-code\n\n \
Or: brew install claude-code"
);
}
let mut args_parts = Vec::new();
if let Some(slug) = continue_slug {
args_parts.push(format!("--continue {slug}"));
} else if let Some(desc) = description {
args_parts.push(format!("\"{desc}\""));
}
if let Some(id) = issue {
args_parts.push(format!("--issue {id}"));
}
if let Some(id) = gh_issue {
args_parts.push(format!("--gh-issue {id}"));
}
let arguments = args_parts.join(" ");
let skill_prompt = include_str!("../../resources/claude/commands/design.md");
let prompt_body = strip_frontmatter(skill_prompt);
let full_prompt = if arguments.is_empty() {
prompt_body.to_string()
} else {
format!("ARGUMENTS: {arguments}\n\n{prompt_body}")
};
let status = Command::new("claude")
.arg("--prompt")
.arg(&full_prompt)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("Failed to launch claude session")?;
if !status.success() {
let code = status.code().unwrap_or(1);
std::process::exit(code);
}
Ok(())
}
fn strip_frontmatter(content: &str) -> &str {
if !content.starts_with("---") {
return content;
}
content[3..].find("\n---").map_or(content, |end| {
let after_frontmatter = &content[3 + end + 4..];
after_frontmatter.trim_start_matches('\n')
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strip_frontmatter_with_frontmatter() {
let input = "---\nallowed-tools: Read\ndescription: test\n---\n\n## Context\nBody here";
let result = strip_frontmatter(input);
assert!(result.starts_with("## Context"));
}
#[test]
fn test_strip_frontmatter_without_frontmatter() {
let input = "## Context\nBody here";
let result = strip_frontmatter(input);
assert_eq!(result, input);
}
#[test]
fn test_strip_frontmatter_empty() {
let result = strip_frontmatter("");
assert_eq!(result, "");
}
}