feat(cli): ship the agent skill inside the binary

A `cargo install` leaves a binary and nothing else, so there is no file on
disk for a coding agent to read. `skills install` writes an embedded SKILL.md
into every agent found on the machine — Claude Code and opencode today, any
other via --dir — and installing over an older copy is what updating means.

The skill carries what --help cannot: the JSON keys of every record, what each
exit code means, and the rule that an agent always passes --json. A unit test
walks the clap command tree and holds SKILL.md to a documented entry for every
command, so a new subcommand fails the build until it is written up.
This commit is contained in:
Michel de Graaf
2026-07-25 02:17:11 +02:00
parent 03e8924a6b
commit 95bc18672d
7 changed files with 543 additions and 3 deletions
+77
View File
@@ -94,6 +94,7 @@ fn help_lists_every_scriptable_command() {
let help = stdout(&output);
for command in [
"search", "download", "get", "browse", "room", "message", "portmap",
"skills",
] {
assert!(help.contains(command), "--help should mention {command}");
}
@@ -256,6 +257,82 @@ fn quiet_keeps_progress_off_stderr() {
);
}
/// What the first record says happened, parsed rather than matched as text.
fn action(output: &Output) -> String {
let record: serde_json::Value =
serde_json::from_str(records(output).first().expect("one record"))
.expect("valid JSON");
record["action"]
.as_str()
.expect("a record carries an action")
.to_string()
}
#[test]
fn installing_the_skill_is_repeatable_and_reversible() {
let target = Scratch::new("skills");
let dir = target.display();
let installed = target.path().join("soulseek-rs").join("SKILL.md");
let first = run(&["--json", "skills", "install", "--dir", &dir]);
assert_eq!(code(&first), EXIT_OK, "stderr: {}", stderr(&first));
assert_eq!(action(&first), "installed");
let written = std::fs::read_to_string(&installed).expect("a skill file");
assert!(written.contains("name: soulseek-rs"));
assert!(written.contains("--json"));
let second = run(&["--json", "skills", "install", "--dir", &dir]);
assert_eq!(
action(&second),
"unchanged",
"reinstalling is how you update"
);
// A neighbour in the same folder proves the sweep removes our file rather
// than the directory it happened to be sitting in.
let neighbour = installed.with_file_name("notes.md");
std::fs::write(&neighbour, "mine").expect("a neighbouring file");
let removed = run(&["--json", "skills", "uninstall", "--dir", &dir]);
assert_eq!(action(&removed), "removed");
assert!(!installed.exists());
assert!(neighbour.exists(), "a file we did not write must survive");
let again = run(&["--json", "skills", "uninstall", "--dir", &dir]);
assert_eq!(code(&again), EXIT_OK, "removing nothing is not a failure");
assert_eq!(action(&again), "absent");
}
/// A mistyped `--dir` names somebody else's directory far more often than it
/// names one of ours, so uninstall has to leave it standing.
#[test]
fn uninstall_leaves_a_skill_it_did_not_write() {
let target = Scratch::new("skills-foreign");
let foreign = target.path().join("soulseek-rs").join("SKILL.md");
std::fs::create_dir_all(foreign.parent().expect("a parent"))
.expect("mkdir");
std::fs::write(&foreign, "---\nname: someone-else\n---\n").expect("write");
let output =
run(&["--json", "skills", "uninstall", "--dir", &target.display()]);
assert_eq!(code(&output), EXIT_OK);
assert_eq!(action(&output), "absent");
assert!(foreign.exists(), "a foreign skill must survive");
}
#[test]
fn listing_skills_reports_without_writing_anything() {
let target = Scratch::new("skills-list");
let output = run(&["--json", "skills", "list", "--dir", &target.display()]);
assert_eq!(code(&output), EXIT_OK);
assert_eq!(action(&output), "absent");
assert!(
!target.path().join("soulseek-rs").exists(),
"list must not create anything"
);
}
// ---------------------------------------------------------------------------
// Server required (soulfind)
// ---------------------------------------------------------------------------