Skip to content

Commit 84095c0

Browse files
eddietejedaclaude
andauthored
feat(update): auto-install and update skills during hotdata update (#105)
After the binary is atomically swapped, download and install the matching skills tarball for the new version. Uses the target version URL rather than CURRENT_VERSION (old binary is still running at call time). Skills are installed even if never previously set up. Failures print a warning without rolling back the binary update. Refactors download_and_extract() into a download_and_extract_from_url() helper so the existing and new versioned paths share one implementation. Co-authored-by: Eddie A Tejeda <669988+eddietejeda@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b059ae4 commit 84095c0

2 files changed

Lines changed: 35 additions & 3 deletions

File tree

src/skill.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,19 @@ fn is_managed_by_skills_agent() -> bool {
195195
}
196196

197197
fn download_and_extract() -> Result<(), String> {
198-
let url = download_url();
198+
download_and_extract_from_url(&download_url())
199+
}
200+
201+
fn download_and_extract_from_url(url: &str) -> Result<(), String> {
199202
eprintln!("Downloading skill...");
200203

201204
// Binary download — can't route through `send_debug` (which calls
202205
// `resp.text()` and would corrupt the gzip stream). Log the
203206
// request line manually so `--debug` still shows the URL.
204-
crate::util::debug_request("GET", &url, &[], None);
207+
crate::util::debug_request("GET", url, &[], None);
205208
let client = reqwest::blocking::Client::new();
206209
let resp = client
207-
.get(&url)
210+
.get(url)
208211
.send()
209212
.map_err(|e| format!("error downloading skill: {e}"))?;
210213

@@ -250,6 +253,29 @@ fn download_and_extract() -> Result<(), String> {
250253
Ok(())
251254
}
252255

256+
/// Download and install skills for `version`. Called from `run_update()` after
257+
/// the binary has been atomically swapped so that skills match the new CLI on
258+
/// first use. Uses the release tarball URL for `version` (not `CURRENT_VERSION`,
259+
/// which is still the old binary at call time). Skips silently when managed by
260+
/// a skills agent. Prints a warning on failure so the binary update is not
261+
/// rolled back.
262+
pub fn install_for_version(version: &Version) {
263+
if is_managed_by_skills_agent() {
264+
return;
265+
}
266+
let url = format!("https://github.com/{REPO}/releases/download/v{version}/skills.tar.gz");
267+
if let Err(e) = download_and_extract_from_url(&url) {
268+
eprintln!(
269+
"{}",
270+
format!("warning: could not update agent skills: {e}").yellow()
271+
);
272+
return;
273+
}
274+
let _symlinks = ensure_symlinks();
275+
clear_skill_auto_update_suppression();
276+
println!("{}", format!("Agent skills updated to v{version}.").green());
277+
}
278+
253279
fn copy_dir_recursive(src: &PathBuf, dst: &PathBuf) -> Result<(), String> {
254280
fs::create_dir_all(dst).map_err(|e| format!("error creating directory: {e}"))?;
255281
for entry in fs::read_dir(src).map_err(|e| format!("error reading directory: {e}"))? {

src/update.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ pub fn run_update() {
199199
}
200200
println!("{}", format!("Updated to v{latest}.").green());
201201

202+
// Install/update skills to match the new binary. The tarball URL is built
203+
// from `latest` (not CURRENT_VERSION) because the old binary is still
204+
// running at this point — we want the skills for the version we just
205+
// downloaded, not the one we replaced.
206+
crate::skill::install_for_version(&latest);
207+
202208
// Bust the cache so the notice clears on the next run.
203209
write_cache(&UpdateCheckCache {
204210
checked_at: now_secs(),

0 commit comments

Comments
 (0)