pub(crate) fn help_text() -> &'static str {
"/connect <profile> /connections /include <profile> /exclude <profile>\n/provider [name] /model [name] /privacy [on|off]\n/approvals [ask|read-only|never] /schema [refresh] /sql <query> /export <path>\n/explain [sql] /clear /history /sessions /resume <id> /doctor /help /exit\n/contracts [table] /remember <table> <kind> <value…> /forget <id> /queue [limit]\n/confirm <prefix> /reject <prefix> — act on a claim shown this turn by its short id prefix"
}
pub(crate) fn command_help(name: &str) -> Option<&'static str> {
let clean_name = name.trim_start_matches('/').to_lowercase();
match clean_name.as_str() {
"connect" => {
Some("connect <profile> — set the active database profile. Example: /connect prod")
}
"connections" => Some(
"connections — list configured database connection profiles. Example: /connections",
),
"include" => Some(
"include <profile> — include an additional database profile. Example: /include staging",
),
"exclude" => {
Some("exclude <profile> — exclude a database profile. Example: /exclude staging")
}
"provider" => {
Some("provider [name] — view or set the AI provider. Example: /provider anthropic")
}
"model" => Some("model [name] — view or set the AI model. Example: /model gpt-4o"),
"privacy" => {
Some("privacy [on|off] — view or toggle cloud data sharing. Example: /privacy off")
}
"approvals" => Some(
"approvals [ask|read-only|never] — view or set tool execution approval policy. Example: /approvals ask",
),
"schema" => Some(
"schema [refresh] — display or refresh database schema context. Example: /schema refresh",
),
"sql" => Some(
"sql <query> — execute a raw SQL query directly. Example: /sql SELECT * FROM users LIMIT 10;",
),
"export" => Some(
"export <path> — write the last query's rows to a .csv or .json file. Example: /export results.csv",
),
"chart" => Some(
"chart [type] [path] — render the last query as an interactive HTML chart and open it. type: bar|line|area|pie|doughnut|scatter (default auto)",
),
"explain" => Some(
"explain [sql] — show the query plan (EXPLAIN) for the given SQL, or the last query if omitted",
),
"clear" => Some("clear — clear the conversation and context. Example: /clear"),
"history" => Some("history — list saved sessions as text. Example: /history"),
"sessions" => {
Some("sessions — browse saved sessions; opens a picker in the TUI. Example: /sessions")
}
"doctor" => Some(
"doctor — diagnose configuration: secrets resolve? provider endpoint? Example: /doctor",
),
"resume" => Some("resume <id> — resume a previous session by ID. Example: /resume 12345"),
"contracts" => Some(
"contracts [catalog.schema.object] — list every recalled contract for the active profile, or show one object's contract when you name it. Example: /contracts or /contracts analytics.public.orders",
),
"contract" => Some(
"contract [catalog.schema.object] — alias for /contracts: list every recalled contract, or show one object's contract when you name it. Example: /contract analytics.public.orders",
),
"remember" => Some(
"remember <catalog.schema.object> <kind> <value…> [because <reason…>] — store a confirmed claim. Kinds: description, alias, grain, time-column, column-description <column> <value…>, column-role <column> <role>. The optional `because <reason…>` (directive kinds only) records why the claim holds. Example: /remember analytics.public.orders time-column created_at because orders complete on return",
),
"forget" => Some(
"forget <claim-id> — tombstone a claim so recall excludes it. Example: /forget abc-123",
),
"queue" => {
Some("queue [limit] — list candidate claims awaiting review. Example: /queue 20")
}
"confirm" => Some(
"confirm <claim-id-prefix> — confirm the claim named by its short id prefix (the ki-xxxx form /contracts shows). Example: /confirm ki-a86a3f",
),
"reject" => Some(
"reject <claim-id-prefix> — reject the claim named by its short id prefix. Example: /reject ki-a86a3f",
),
"help" => Some(
"help [command] — display general help or detailed usage for a command. Example: /help connect",
),
"exit" | "quit" => Some("exit — exit the interactive CLI session. Example: /exit"),
_ => None,
}
}
pub(crate) fn help_for(topic: Option<&str>) -> String {
match topic {
Some(name) => {
let clean = name.trim_start_matches('/');
match command_help(clean) {
Some(help) => help.to_string(),
None => format!("No help for /{clean}. Type /help for the full list."),
}
}
None => help_text().to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn history_and_sessions_help_describe_their_real_surfaces() {
let history = command_help("history").expect("history has help");
let sessions = command_help("sessions").expect("sessions has help");
assert!(
!history.contains("alias") && !sessions.contains("alias"),
"neither may claim aliasing — the TUI routes them differently: {history} / {sessions}"
);
assert!(
sessions.contains("picker"),
"`/sessions` help must mention the picker it opens in the TUI, got: {sessions}"
);
assert!(
history.contains("saved") && sessions.contains("saved"),
"both must name saved sessions, got: {history} / {sessions}"
);
}
#[test]
fn clear_help_describes_the_conversation_not_history() {
let clear = command_help("clear").expect("clear has help");
assert!(
clear.contains("conversation") && clear.contains("context"),
"/clear help should describe the conversation and context, got: {clear}"
);
assert!(
!clear.contains("history"),
"/clear help must not reuse the overloaded 'history' word (now = saved sessions), got: {clear}"
);
}
#[test]
fn contracts_help_covers_both_forms_and_marks_the_optional_argument() {
let summary = help_text();
assert!(
summary.contains("/contracts [table]"),
"summary must show the merged /contracts [table] form, got: {summary}"
);
assert!(
!summary.contains("/contract <table>"),
"summary must not keep the old separate /contract <table> entry, got: {summary}"
);
let contracts = command_help("contracts").expect("contracts has help");
assert!(
contracts.contains("list every recalled contract"),
"/contracts help must name the list form, got: {contracts}"
);
assert!(
contracts.contains("show one object's contract"),
"/contracts help must name the show form, got: {contracts}"
);
assert!(
contracts.contains("[catalog.schema.object]"),
"/contracts help must mark the optional argument with brackets, got: {contracts}"
);
assert!(
contracts.contains("/contracts") && contracts.contains("analytics.public.orders"),
"/contracts help must show a no-arg and a with-arg example, got: {contracts}"
);
let contract = command_help("contract").expect("contract still has help");
assert!(
contract.contains("alias"),
"/contract help must name itself an alias of /contracts, got: {contract}"
);
assert!(
contract.contains("show one object's contract"),
"/contract help must describe the merged show form, got: {contract}"
);
}
}