mirror of
https://github.com/instructkr/claude-code.git
synced 2026-06-05 12:06:43 +00:00
fix: add structured help JSON and provider BASE_URL validation
#683-#692: help topic JSON now includes usage, purpose, formats, related, local_only, requires_credentials, and aliases extracted from help prose. Export and doctor keep their custom structured responses. #466: new check_base_url_health() validates ANTHROPIC_BASE_URL, OPENAI_BASE_URL, XAI_BASE_URL, and DASHSCOPE_BASE_URL for basic HTTP(S) URL format. Non-http schemes and empty values produce a warn-level diagnostic. Generated with https://github.com/Yeachan-Heo/gajae-code Co-authored-by: Gajae Code <dev@gajae-code.com>
This commit is contained in:
@@ -3625,6 +3625,7 @@ fn render_doctor_report(
|
||||
Ok(DoctorReport {
|
||||
checks: vec![
|
||||
check_auth_health(),
|
||||
check_base_url_health(),
|
||||
check_config_health(&config_loader, config.as_ref()),
|
||||
check_mcp_validation_health(&mcp_validation),
|
||||
check_hook_validation_health(&hook_validation),
|
||||
@@ -3865,6 +3866,50 @@ fn check_auth_health() -> DiagnosticCheck {
|
||||
}
|
||||
}
|
||||
|
||||
/// #466: validate provider BASE_URL env vars
|
||||
fn check_base_url_health() -> DiagnosticCheck {
|
||||
let base_url_vars = [
|
||||
("ANTHROPIC_BASE_URL", "https://api.anthropic.com"),
|
||||
("OPENAI_BASE_URL", "https://api.openai.com"),
|
||||
("XAI_BASE_URL", "https://api.x.ai"),
|
||||
("DASHSCOPE_BASE_URL", "https://dashscope.aliyuncs.com"),
|
||||
];
|
||||
let mut issues: Vec<String> = Vec::new();
|
||||
let mut details: Vec<String> = Vec::new();
|
||||
for (var_name, default_url) in &base_url_vars {
|
||||
if let Ok(value) = env::var(var_name) {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
issues.push(format!("{var_name} is empty"));
|
||||
details.push(format!(
|
||||
"{var_name} empty (will use default: {default_url})"
|
||||
));
|
||||
} else if !trimmed.starts_with("http://") && !trimmed.starts_with("https://") {
|
||||
issues.push(format!("{var_name}={trimmed} is not a valid HTTP(S) URL"));
|
||||
details.push(format!("{var_name} invalid ({trimmed})"));
|
||||
} else {
|
||||
details.push(format!("{var_name} {trimmed}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if issues.is_empty() {
|
||||
DiagnosticCheck::new(
|
||||
"Base URLs",
|
||||
DiagnosticLevel::Ok,
|
||||
"provider base URL env vars are valid or unset",
|
||||
)
|
||||
.with_details(details)
|
||||
} else {
|
||||
DiagnosticCheck::new(
|
||||
"Base URLs",
|
||||
DiagnosticLevel::Warn,
|
||||
format!("{} base URL issue(s) found", issues.len()),
|
||||
)
|
||||
.with_details(details)
|
||||
.with_hint("Fix the reported BASE_URL env vars or unset them to use provider defaults.")
|
||||
}
|
||||
}
|
||||
|
||||
fn check_config_health(
|
||||
config_loader: &ConfigLoader,
|
||||
config: Result<&runtime::RuntimeConfig, &runtime::ConfigError>,
|
||||
@@ -10011,6 +10056,82 @@ fn render_doctor_help_json() -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
/// #683-#692: extract structured metadata from help prose
|
||||
fn extract_help_metadata(
|
||||
topic: LocalHelpTopic,
|
||||
) -> (
|
||||
Option<String>, // usage
|
||||
Option<String>, // purpose
|
||||
Option<String>, // output description
|
||||
Option<Vec<String>>, // formats
|
||||
Option<Vec<String>>, // related
|
||||
Option<Vec<String>>, // aliases
|
||||
bool, // local_only
|
||||
bool, // requires_credentials
|
||||
) {
|
||||
let text = render_help_topic(topic);
|
||||
let mut usage = None;
|
||||
let mut purpose = None;
|
||||
let mut output_desc = None;
|
||||
let formats = Some(vec!["text".to_string(), "json".to_string()]);
|
||||
let mut related = None;
|
||||
let mut aliases = None;
|
||||
let local_only = matches!(
|
||||
topic,
|
||||
LocalHelpTopic::Status
|
||||
| LocalHelpTopic::Sandbox
|
||||
| LocalHelpTopic::Doctor
|
||||
| LocalHelpTopic::Version
|
||||
| LocalHelpTopic::State
|
||||
| LocalHelpTopic::Init
|
||||
| LocalHelpTopic::Export
|
||||
| LocalHelpTopic::SystemPrompt
|
||||
| LocalHelpTopic::DumpManifests
|
||||
| LocalHelpTopic::BootstrapPlan
|
||||
);
|
||||
for line in text.lines() {
|
||||
let trimmed = line.trim();
|
||||
if let Some(rest) = trimmed.strip_prefix("Usage") {
|
||||
let value = rest.trim();
|
||||
if !value.is_empty() {
|
||||
usage = Some(value.to_string());
|
||||
}
|
||||
} else if let Some(rest) = trimmed.strip_prefix("Purpose") {
|
||||
purpose = Some(rest.trim().to_string());
|
||||
} else if let Some(rest) = trimmed.strip_prefix("Output") {
|
||||
output_desc = Some(rest.trim().to_string());
|
||||
} else if let Some(rest) = trimmed.strip_prefix("Aliases") {
|
||||
let parts: Vec<String> = rest
|
||||
.split('·')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
if !parts.is_empty() {
|
||||
aliases = Some(parts);
|
||||
}
|
||||
} else if let Some(rest) = trimmed.strip_prefix("Related") {
|
||||
let parts: Vec<String> = rest
|
||||
.split('·')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
if !parts.is_empty() {
|
||||
related = Some(parts);
|
||||
}
|
||||
}
|
||||
}
|
||||
(
|
||||
usage,
|
||||
purpose,
|
||||
output_desc,
|
||||
formats,
|
||||
related,
|
||||
aliases,
|
||||
local_only,
|
||||
!local_only,
|
||||
)
|
||||
}
|
||||
|
||||
fn render_help_topic_json(topic: LocalHelpTopic) -> serde_json::Value {
|
||||
if topic == LocalHelpTopic::Export {
|
||||
return render_export_help_json();
|
||||
@@ -10019,14 +10140,30 @@ fn render_help_topic_json(topic: LocalHelpTopic) -> serde_json::Value {
|
||||
return render_doctor_help_json();
|
||||
}
|
||||
|
||||
json!({
|
||||
// #683-#692: extract structured metadata from help prose for machine consumption
|
||||
let (usage, purpose, output_desc, formats, related, aliases, local_only, requires_credentials) =
|
||||
extract_help_metadata(topic);
|
||||
let mut obj = serde_json::json!({
|
||||
"kind": "help",
|
||||
"action": "help",
|
||||
"status": "ok",
|
||||
"topic": local_help_topic_command(topic),
|
||||
"command": local_help_topic_command(topic),
|
||||
"message": render_help_topic(topic),
|
||||
})
|
||||
"usage": usage,
|
||||
"purpose": purpose,
|
||||
"formats": formats,
|
||||
"related": related,
|
||||
"local_only": local_only,
|
||||
"requires_credentials": requires_credentials,
|
||||
});
|
||||
if let Some(desc) = output_desc {
|
||||
obj["output_fields"] = serde_json::Value::String(desc);
|
||||
}
|
||||
if let Some(a) = aliases {
|
||||
obj["aliases"] = serde_json::json!(a);
|
||||
}
|
||||
obj
|
||||
}
|
||||
|
||||
fn print_help_topic(
|
||||
|
||||
Reference in New Issue
Block a user