Skip to content
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

* Added two new commands to the `btw` CLI: `btw app` to launch a `btw_app()` session in the current working directory and `btw skills install` to install skills from the terminal.

* Added `btw docs topics <pkg>` to the `btw` CLI for discovering a package's help topics and vignettes. Use `--only help` or `--only vignettes` to limit output to one section, or `--json` for machine-readable output (#195).

* Added `btw help` to the `btw` CLI, which prints the `r-btw-cli` skill — a usage guide designed for AI agents.

## Bug fixes
Expand Down
107 changes: 102 additions & 5 deletions exec/btw.R
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ btw_docs_help <- function(topic, package) {
)
}
btw_output(btw_this(btw:::as_btw_docs_topic(parts[1], parts[2])))
} else if (grepl("^\\{.+\\}$", topic)) {
pkg_name <- sub("^\\{(.+)\\}$", "\\1", topic)
btw_output(btw_this(btw:::as_btw_docs_package(pkg_name)))
} else if (has_value(package)) {
btw_output(btw_this(btw:::as_btw_docs_topic(package, topic)))
} else {
Expand All @@ -81,6 +78,91 @@ btw_docs_help <- function(topic, package) {
}
}

btw_docs_topics <- function(package, only, json = FALSE) {
if (!only %in% c("", "help", "vignettes")) {
stop("--only must be \"help\" or \"vignettes\"", call. = FALSE)
}

include_help <- only == "" || only == "help"
include_vignettes <- only == "" || only == "vignettes"

if (json) {
out <- list()

if (include_help) {
result <- btw:::btw_tool_docs_package_help_topics_impl(package)
df <- S7::prop(result, "extra")$data
out$help <- lapply(seq_len(nrow(df)), function(i) {
list(topic_id = df$topic_id[[i]], title = df$title[[i]], aliases = df$aliases[[i]])
})
}

if (include_vignettes) {
out$vignettes <- tryCatch(
{
result <- btw:::btw_tool_docs_available_vignettes_impl(package)
df <- S7::prop(result, "extra")$data
lapply(seq_len(nrow(df)), function(i) {
list(vignette = df$vignette[[i]], title = df$title[[i]])
})
},
error = function(e) list()
)
}

btw_json_output(out)
return(invisible(NULL))
}

if (include_help) {
result <- btw:::btw_tool_docs_package_help_topics_impl(package)
df <- S7::prop(result, "extra")$data
lines <- mapply(
function(topic_id, title, aliases) {
sprintf("* `%s` - %s", topic_id, title)
},
df$topic_id,
df$title,
df$aliases
)
cat(
"## Help topics\n\n",
lines,
sprintf(
"\nUse `btw docs help %s::<topic>` to read a help page.\n",
package
),
sep = "\n"
)
}

if (include_vignettes) {
if (include_help) {
cat("\n")
}
cat("## Vignettes\n\n")
tryCatch(
{
result <- btw:::btw_tool_docs_available_vignettes_impl(package)
df <- S7::prop(result, "extra")$data
lines <- sprintf("* `%s` - %s", df$vignette, df$title)
cat(
lines,
sprintf(
"\nUse `btw docs vignette %s --name <name>` to read a vignette.\n",
package
),
sep = "\n"
)
},
error = function(e) {
msg <- cli::ansi_strip(conditionMessage(e))
cat(msg, "\n")
}
)
}
}

btw_docs_vignette <- function(package, name, list) {
if (list) {
btw_output(btw_this(utils::vignette(package = package)))
Expand Down Expand Up @@ -243,9 +325,22 @@ switch(
switch(
docs_cmd <- "",

#| title: List help topics and vignettes for a package
topics = {
#| description: Package name.
package <- NULL
#| description: Limit output to "help" topics or "vignettes".
#| short: 'o'
only <- ""
#| description: Output as JSON with top-level keys "help" (array of {topic_id, title, aliases[]}) and "vignettes" (array of {vignette, title}).
json <- FALSE

tryCatch(btw_docs_topics(package, only, json), error = btw_error)
},

#| title: Show help for a topic or package
help = {
#| description: Help topic, package name, or {package} for package listing.
#| description: Help topic, or pkg::topic to scope to a specific package.
topic <- NULL
#| description: Package name to scope the help topic.
#| short: 'p'
Expand Down Expand Up @@ -417,7 +512,9 @@ switch(
#| title: Show btw CLI usage guide for AI agents
help = {
skill_path <- system.file(
"cli-skill", "r-btw-cli", "SKILL.md",
"cli-skill",
"r-btw-cli",
"SKILL.md",
package = "btw"
)
if (!nzchar(skill_path)) {
Expand Down
12 changes: 6 additions & 6 deletions inst/cli-skill/r-btw-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ description: "Use the `btw` CLI to access R documentation, manage R package deve
Use `btw docs` to read R help pages, vignettes, and package NEWS for locally installed packages.

```
btw docs help <topic> [-p <pkg>] Read an R help page (tries topic first, falls back to package listing)
btw docs help {<pkg>} List help topics for a package
btw docs help <pkg>::<topic> Read a specific help page (scoped)
btw docs vignette <pkg> [-n <name>] Read a vignette (--list to list available)
btw docs news <pkg> [-s <term>] Read package NEWS/changelog
btw docs topics <pkg> [--only help|vignettes] List help topics and vignettes for a package
btw docs help <topic> [-p <pkg>] Read an R help page
btw docs help <pkg>::<topic> Read a specific help page (scoped)
btw docs vignette <pkg> [-n <name>] Read a vignette (--list to list available)
btw docs news <pkg> [-s <term>] Read package NEWS/changelog
```

Use `btw pkg` to run development tasks on an R package under active development.
Expand All @@ -49,4 +49,4 @@ btw cran search <query> [-n <count>] [--json] Search CRAN for packages
btw cran info <pkg> [--json] CRAN package details
```

Run `btw --help`, `btw <group> --help`, or `btw <group> <cmd> --help` for full usage details.
Run `btw --help`, `btw <group> --help`, or `btw <group> <cmd> --help` for full usage details.
3 changes: 2 additions & 1 deletion tests/testthat/_snaps/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
Usage: btw docs [OPTIONS] <COMMAND>

Commands:
topics List help topics and vignettes for a package
help Show help for a topic or package
vignette Read a package vignette
news Show package NEWS
Expand Down Expand Up @@ -61,7 +62,7 @@
Enable with `--version`.

Arguments:
<TOPIC> Help topic, package name, or {package} for package listing.
<TOPIC> Help topic, or pkg::topic to scope to a specific package.

# btw pkg --help shows pkg group help

Expand Down
30 changes: 23 additions & 7 deletions tests/testthat/test-cli.R
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,6 @@ test_that("btw docs help <topic> resolves topic first", {
expect_equal(env$package, "")
})

test_that("btw docs help {package} lists help topics", {
env <- run_btw_quietly("docs", "help", "{stats}")
expect_true(is.environment(env))
expect_equal(env$topic, "{stats}")
expect_equal(env$package, "")
})

test_that("btw docs help <topic> -p <package> reads help page", {
local_skip_pandoc_convert_text()
Expand Down Expand Up @@ -126,6 +120,28 @@ test_that("btw docs help errors for unknown topic", {
expect_match(result$stderr, "completely_nonexistent_xyz", ignore.case = TRUE)
})


# docs topics ----------------------------------------------------------

test_that("btw docs topics <package> shows topics and vignettes", {
env <- run_btw_quietly("docs", "topics", "stats")
expect_equal(env$package, "stats")
expect_equal(env$only, "")
})

test_that("btw docs topics --only help shows only help topics", {
env <- run_btw_quietly("docs", "topics", "stats", "--only", "help")
expect_equal(env$package, "stats")
expect_equal(env$only, "help")
})

test_that("btw docs topics --only vignettes shows only vignettes", {
skip_if_not_installed("dplyr")
env <- run_btw_quietly("docs", "topics", "dplyr", "--only", "vignettes")
expect_equal(env$package, "dplyr")
expect_equal(env$only, "vignettes")
})

# docs vignette ----------------------------------------------------------

test_that("btw docs vignette <package> --list lists vignettes", {
Expand Down Expand Up @@ -458,4 +474,4 @@ test_that("btw pkg error exits with code 1 and message on stderr", {
expect_equal(result$status, 1)
expect_match(result$stderr, "nonexistent_pkg_xyz", ignore.case = TRUE)
expect_equal(trimws(result$stdout), "")
})
})
Loading