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:
+7
-6
@@ -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
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user