feat(skills): rename "agent-browser" skill to "core"; make CLI-served main skill actually useful (#1253)

Before this change, the main skill served by the CLI (`agent-browser
skills get agent-browser`) was a ~40-line discovery stub whose content
was essentially "run `agent-browser skills get <name>` before doing
anything." Agents already inside the CLI got no signal from it — the
content they needed to actually use the tool lived only in the `--full`
references.

Split the two jobs apart:

- **`skill-data/core/`** (new) — the runtime usage guide. 420-line
  `SKILL.md` covering the snapshot-and-ref loop, common workflows
  (login, extract, screenshot, multi-tab, sessions, iframes, dialogs),
  waiting strategies, element selection strategies, troubleshooting,
  and when to load a specialized skill. Supplementary `references/` and
  `templates/` (moved from `skills/agent-browser/`) provide the full
  command reference under `--full`.
- **`skills/agent-browser/SKILL.md`** — still the discovery stub that
  `npx skills add` installs, now marked `hidden: true` so it stays out
  of `skills list` inside the CLI. Body is a clean pointer to
  `agent-browser skills get core` and the specialized skills.

The `hidden: true` frontmatter flag is a new, general mechanism: skills
marked hidden are omitted from `skills list` and `skills get --all` but
can still be fetched by explicit name. This keeps the stub reachable
for anyone who installed via `npx skills add` without polluting the
CLI-side skill listing.

## Behavior

```
$ agent-browser skills list
  agentcore       Run agent-browser on AWS Bedrock AgentCore cloud browsers...
  core            Core agent-browser usage guide. Read this before running...
  dogfood         Systematically explore and test a web application...
  electron        Automate Electron desktop apps (VS Code, Slack, Discord...)
  slack           Interact with Slack workspaces using browser automation...
  vercel-sandbox  Run agent-browser + Chrome inside Vercel Sandbox microVMs...

$ agent-browser skills get core          # the actual usage guide
# ~420 lines of workflows, patterns, troubleshooting

$ agent-browser skills get agent-browser # still works if called explicitly
# the thin stub, now pointing at `core`
```

External `npx skills add vercel-labs/agent-browser` behavior is
unchanged: it finds and installs the thin `agent-browser` stub, which
tells the agent to run `agent-browser skills get core` for real
content. Version drift protection is preserved — the stub is the only
thing that gets copied; the real content is always runtime-fetched.

## Updated

- `cli/src/skills.rs` — `SkillInfo.hidden: bool`, parsed from
  frontmatter; `run_list` and `run_get --all` filter it. 3 new unit
  tests for the frontmatter parser.
- `cli/src/output.rs` — top-level `--help` and `skills` subcommand help
  reference `skills get core` / `skills get core --full`.
- `AGENTS.md` — "update these files for user-facing features" now
  points at `skill-data/core/` instead of the stub, with a note that
  the stub is not the right place for feature content.
- `README.md`, `docs/src/app/skills/page.mdx` — describe the new
  split and `skills get core --full` as the recommended entry point.
- `evals/cases/{command-usage,skill-selection}.ts` — expect
  `skills get core` in agent output instead of `skills get
  agent-browser`. Eval lib still reads `skills/agent-browser/SKILL.md`
  (simulating what an agent sees after `npx skills add`).

All 11 skills unit tests pass. `cargo clippy -- -D warnings` and
`cargo fmt --check` clean. Verified end-to-end: `skills list` shows
`core` + specialized (no stub), `skills get core` returns the new
content, `skills get agent-browser` still returns the stub on explicit
request.
This commit is contained in:
Chris Tate
2026-04-16 14:36:59 -05:00
committed by GitHub
parent 1afcaa0e84
commit 4cc6ca40b7
19 changed files with 513 additions and 39 deletions
+7 -6
View File
@@ -2829,10 +2829,11 @@ rather than relying on cached copies.
Examples:
agent-browser skills
agent-browser skills list
agent-browser skills get agent-browser
agent-browser skills get core
agent-browser skills get core --full
agent-browser skills get electron --full
agent-browser skills get --all
agent-browser skills path agent-browser
agent-browser skills path core
agent-browser skills list --json
Environment:
@@ -2854,7 +2855,7 @@ agent-browser - fast browser automation CLI for AI agents
Usage: agent-browser <command> [args] [options]
Start here (for AI agents):
agent-browser skills get agent-browser --full
agent-browser skills get core --full
Skills ship with the CLI (always version-matched) and include workflow
patterns, ref/selector usage, and copy-paste examples. Prefer this over
@@ -2862,9 +2863,9 @@ Start here (for AI agents):
apps, Slack, exploratory testing, and cloud browser providers.
skills [list] List available skills
skills get <name> Get a skill's core content (overview + examples)
skills get <name> --full Include references and templates
skills get --all Get every skill
skills get core Core usage guide (overview + common patterns)
skills get core --full Include full command reference and templates
skills get <name> Load a specialized skill (electron, slack, ...)
skills path [name] Print skill directory path
Core Commands:
+45 -10
View File
@@ -10,11 +10,21 @@ struct SkillInfo {
name: String,
description: String,
dir: PathBuf,
/// When true, the skill is omitted from `skills list` and `skills get --all`
/// but can still be fetched by name via `skills get <name>`. Used for
/// bootstrap stubs that exist for external tooling (e.g. `npx skills add`)
/// but aren't the intended entry point for agents already inside the CLI.
hidden: bool,
}
/// Skill content is split across two directories:
/// - `skills/` — the bootstrap skill (discoverable by npx skills add)
/// - `skill-data/` — specialized skills (only served by the CLI)
///
/// - `skills/` — discovery stubs (picked up by `npx skills add`). Carry
/// `hidden: true` so they don't show up in `skills list` or `skills get
/// --all` inside the CLI, since they exist only to redirect external
/// agents to `skills get core`.
/// - `skill-data/` — runtime skill content served by the CLI (`core`,
/// `electron`, `slack`, `dogfood`, etc.).
///
/// Both are shipped in the npm package and searched by `discover_skills`.
const SKILL_DIRS: &[&str] = &["skills", "skill-data"];
@@ -74,8 +84,8 @@ fn find_skills_dirs() -> Vec<PathBuf> {
.collect()
}
/// Parse YAML frontmatter from a SKILL.md file. Returns (name, description).
fn parse_frontmatter(content: &str) -> Option<(String, String)> {
/// Parse YAML frontmatter from a SKILL.md file. Returns (name, description, hidden).
fn parse_frontmatter(content: &str) -> Option<(String, String, bool)> {
let content = content.trim_start();
if !content.starts_with("---") {
return None;
@@ -86,6 +96,7 @@ fn parse_frontmatter(content: &str) -> Option<(String, String)> {
let mut name = None;
let mut description = None;
let mut hidden = false;
let lines: Vec<&str> = frontmatter.lines().collect();
let mut i = 0;
@@ -104,11 +115,13 @@ fn parse_frontmatter(content: &str) -> Option<(String, String)> {
desc.push_str(lines[i].trim());
}
description = Some(desc);
} else if let Some(val) = line.strip_prefix("hidden:") {
hidden = matches!(val.trim(), "true" | "yes");
}
i += 1;
}
Some((name?, description.unwrap_or_default()))
Some((name?, description.unwrap_or_default(), hidden))
}
/// Discover all skills across the given directories.
@@ -134,11 +147,12 @@ fn discover_skills(dirs: &[PathBuf]) -> Vec<SkillInfo> {
Ok(c) => c,
Err(_) => continue,
};
if let Some((name, description)) = parse_frontmatter(&content) {
if let Some((name, description, hidden)) = parse_frontmatter(&content) {
skills.push(SkillInfo {
name,
description,
dir: path,
hidden,
});
}
}
@@ -198,7 +212,10 @@ fn collect_supplementary_files(skill_dir: &Path) -> Vec<(String, String)> {
}
fn run_list(skills_dirs: &[PathBuf], json_mode: bool) {
let skills = discover_skills(skills_dirs);
let skills: Vec<SkillInfo> = discover_skills(skills_dirs)
.into_iter()
.filter(|s| !s.hidden)
.collect();
if skills.is_empty() {
if json_mode {
println!(
@@ -242,7 +259,7 @@ fn run_get(skills_dirs: &[PathBuf], names: &[String], get_all: bool, full: bool,
let all_skills = discover_skills(skills_dirs);
let targets: Vec<&SkillInfo> = if get_all {
all_skills.iter().collect()
all_skills.iter().filter(|s| !s.hidden).collect()
} else {
let mut targets = Vec::new();
for name in names {
@@ -490,18 +507,36 @@ mod tests {
#[test]
fn test_parse_frontmatter_basic() {
let content = "---\nname: test-skill\ndescription: A test skill.\n---\n\n# Test\n";
let (name, desc) = parse_frontmatter(content).unwrap();
let (name, desc, hidden) = parse_frontmatter(content).unwrap();
assert_eq!(name, "test-skill");
assert_eq!(desc, "A test skill.");
assert!(!hidden);
}
#[test]
fn test_parse_frontmatter_multiline_description() {
let content =
"---\nname: test\ndescription: First line\n continued here\n and here\n---\n";
let (name, desc) = parse_frontmatter(content).unwrap();
let (name, desc, hidden) = parse_frontmatter(content).unwrap();
assert_eq!(name, "test");
assert_eq!(desc, "First line continued here and here");
assert!(!hidden);
}
#[test]
fn test_parse_frontmatter_hidden_true() {
let content = "---\nname: stub\ndescription: A bootstrap stub.\nhidden: true\n---\n";
let (name, desc, hidden) = parse_frontmatter(content).unwrap();
assert_eq!(name, "stub");
assert_eq!(desc, "A bootstrap stub.");
assert!(hidden);
}
#[test]
fn test_parse_frontmatter_hidden_false() {
let content = "---\nname: visible\ndescription: Visible.\nhidden: false\n---\n";
let (_, _, hidden) = parse_frontmatter(content).unwrap();
assert!(!hidden);
}
#[test]