Skip to content

Commit d394518

Browse files
committed
chore: release
1 parent d6c6c74 commit d394518

13 files changed

Lines changed: 199 additions & 83 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name: Release CLI
44
on:
55
push:
66
tags:
7-
- '**' # match all tags, e.g. 1.0.0, release-1, beta, etc.
7+
- '*.*.*' # semver-ish tags like 0.9.0 (repo uses non-v tags)
88
workflow_dispatch:
99

1010
env:

CHANGELOG.md

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
### Changed
13+
14+
### Fixed
15+
16+
## [0.9.0] - 2025-12-12
17+
18+
### Added
19+
20+
- **`token:get` command**
21+
- Retrieve a machine token without running the authorization flow
22+
1023
### Changed
1124

1225
- **SDK Update: runbeam-sdk 0.8.0 → 0.9.1**
@@ -16,9 +29,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1629
- Use `JwtValidationOptions::default()` for standard validation behavior
1730
- See runbeam-sdk 0.9.0 release notes for migration details
1831

19-
### Harmony DSL 1.9.0 Alignment
20-
- Updated all JWT token validation calls to use new `JwtValidationOptions` API
21-
- Full compatibility with runbeam-sdk 0.9.1 (includes harmony-dsl 1.9.0)
32+
- **Harmony DSL 1.9.0 alignment**
33+
- Updated all JWT token validation calls to use new `JwtValidationOptions` API
34+
- Full compatibility with runbeam-sdk 0.9.1 (includes harmony-dsl 1.9.0)
2235

2336
## [0.8.0] - 2025-11-20
2437

@@ -248,7 +261,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
248261
- Harmony commands (`harmony:add`, `harmony:list`, `harmony:remove`)
249262
- Harmony management API integration
250263

251-
[0.4.0]: https://github.com/aurabx/runbeam-cli/compare/v0.3.0...v0.4.0
252-
[0.3.0]: https://github.com/aurabx/runbeam-cli/compare/v0.2.0...v0.3.0
253-
[0.2.0]: https://github.com/aurabx/runbeam-cli/compare/v0.1.0...v0.2.0
254-
[0.1.0]: https://github.com/aurabx/runbeam-cli/releases/tag/v0.1.0
264+
[0.9.0]: https://github.com/aurabx/runbeam-cli/compare/0.8.0...0.9.0
265+
[0.8.0]: https://github.com/aurabx/runbeam-cli/compare/0.7.8...0.8.0
266+
[0.4.0]: https://github.com/aurabx/runbeam-cli/compare/0.3.0...0.4.0
267+
[0.3.0]: https://github.com/aurabx/runbeam-cli/compare/0.2.0...0.3.0
268+
[0.2.0]: https://github.com/aurabx/runbeam-cli/compare/0.1.0...0.2.0
269+
[0.1.0]: https://github.com/aurabx/runbeam-cli/releases/tag/0.1.0

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "runbeam-cli"
3-
version = "0.9.0-dev"
3+
version = "0.9.0"
44
edition = "2024"
55
authors = ["Aurabox <hello@aurabox.cloud>"]
66
description = "CLI for managing Runbeam and Harmony"

binstall.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
pkg_fmt = "tgz"
22
bin_dir = "runbeam-{ target }/{ bin }"
3-
pkg_url = "{ repo }/releases/download/v{ version }/runbeam-{ target }.tar.gz"
3+
# Tags in this repo are version-only (e.g. "0.9.0"), not "v0.9.0"
4+
pkg_url = "{ repo }/releases/download/{ version }/runbeam-{ target }.tar.gz"
45

56
[target.x86_64-pc-windows-msvc]
67
pkg_fmt = "zip"
7-
pkg_url = "{ repo }/releases/download/v{ version }/runbeam-{ target }.zip"
8+
pkg_url = "{ repo }/releases/download/{ version }/runbeam-{ target }.zip"
89
bin_dir = "runbeam-{ target }/{ bin }.exe"

src/commands/auth.rs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use anyhow::{Context, Result};
2-
use runbeam_sdk::{RunbeamClient, UserInfo, validate_jwt_token as sdk_validate_jwt, JwtValidationOptions};
2+
use runbeam_sdk::{
3+
JwtValidationOptions, RunbeamClient, UserInfo, validate_jwt_token as sdk_validate_jwt,
4+
};
35
use serde::{Deserialize, Serialize};
46
use std::thread;
57
use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -49,7 +51,10 @@ pub fn login() -> Result<()> {
4951
// Verify the token is still valid
5052
let validation_result = tokio::runtime::Runtime::new()
5153
.expect("Failed to create Tokio runtime")
52-
.block_on(sdk_validate_jwt(&existing_auth.token, &JwtValidationOptions::default()));
54+
.block_on(sdk_validate_jwt(
55+
&existing_auth.token,
56+
&JwtValidationOptions::default(),
57+
));
5358

5459
if validation_result.is_ok() {
5560
println!("✓ Already logged in with a valid token.");
@@ -207,7 +212,10 @@ pub fn login() -> Result<()> {
207212
// Verify the token using SDK (RS256 with JWKS)
208213
let validation_result = tokio::runtime::Runtime::new()
209214
.expect("Failed to create Tokio runtime")
210-
.block_on(sdk_validate_jwt(&token_clone, &JwtValidationOptions::default()));
215+
.block_on(sdk_validate_jwt(
216+
&token_clone,
217+
&JwtValidationOptions::default(),
218+
));
211219

212220
match validation_result {
213221
Ok(jwt_claims) => {
@@ -291,7 +299,10 @@ pub fn authorize_harmony(
291299
debug!("Validating JWT token before authorization...");
292300
let validation_result = tokio::runtime::Runtime::new()
293301
.expect("Failed to create Tokio runtime")
294-
.block_on(sdk_validate_jwt(&auth.token, &JwtValidationOptions::default()));
302+
.block_on(sdk_validate_jwt(
303+
&auth.token,
304+
&JwtValidationOptions::default(),
305+
));
295306

296307
match validation_result {
297308
Ok(claims) => {
@@ -303,7 +314,10 @@ pub fn authorize_harmony(
303314
.as_secs() as i64;
304315
let time_remaining = claims.exp - now;
305316
if time_remaining < 3600 {
306-
println!("⚠️ Warning: Your token expires in {} minutes.", time_remaining / 60);
317+
println!(
318+
"⚠️ Warning: Your token expires in {} minutes.",
319+
time_remaining / 60
320+
);
307321
println!(" Consider running `runbeam login` to refresh your token.");
308322
println!();
309323
}
@@ -399,7 +413,10 @@ pub fn authorize_harmony(
399413
if let Some(stored_instance) = instances.iter_mut().find(|i| i.id == instance_id) {
400414
stored_instance.gateway_id = Some(auth_response.gateway.id.clone());
401415
storage::save_harmony_instances(&instances)?;
402-
debug!("Stored gateway_id {} for instance {}", auth_response.gateway.id, instance_id);
416+
debug!(
417+
"Stored gateway_id {} for instance {}",
418+
auth_response.gateway.id, instance_id
419+
);
403420
}
404421

405422
// Send machine token to Harmony proxy instance
@@ -461,7 +478,9 @@ pub fn authorize_harmony(
461478
} else {
462479
// Interactive prompt
463480
println!("📡 Upload local configuration to Runbeam Cloud?");
464-
println!(" This will overwrite any configuration changes made on Runbeam Cloud.");
481+
println!(
482+
" This will overwrite any configuration changes made on Runbeam Cloud."
483+
);
465484
print!(" Upload configuration? [y/N]: ");
466485
std::io::Write::flush(&mut std::io::stdout()).ok();
467486

@@ -481,7 +500,9 @@ pub fn authorize_harmony(
481500
Err(e) => {
482501
warn!("Configuration upload failed: {}", e);
483502
println!("⚠️ Configuration upload failed: {}", e);
484-
println!(" Authorization is still valid. You can manually upload config with:");
503+
println!(
504+
" Authorization is still valid. You can manually upload config with:"
505+
);
485506
println!(" runbeam harmony:update --id {}", instance_id);
486507
}
487508
}
@@ -583,7 +604,10 @@ pub fn verify_token() -> Result<()> {
583604
// Validate the token using SDK (async)
584605
let validation_result = tokio::runtime::Runtime::new()
585606
.expect("Failed to create Tokio runtime")
586-
.block_on(sdk_validate_jwt(&auth.token, &JwtValidationOptions::default()));
607+
.block_on(sdk_validate_jwt(
608+
&auth.token,
609+
&JwtValidationOptions::default(),
610+
));
587611

588612
match validation_result {
589613
Ok(claims) => {
@@ -702,7 +726,10 @@ pub fn get_token(gateway_code: &str, raw: bool) -> Result<()> {
702726
// Output with context
703727
println!("✅ Machine token retrieved successfully!");
704728
println!();
705-
println!("Gateway: {} ({})", auth_response.gateway.name, auth_response.gateway.code);
729+
println!(
730+
"Gateway: {} ({})",
731+
auth_response.gateway.name, auth_response.gateway.code
732+
);
706733
println!("Gateway ID: {}", auth_response.gateway.id);
707734
println!("Expires at: {}", auth_response.expires_at);
708735
let expires_in_days = (auth_response.expires_in / 86400.0).round() as i64;
@@ -715,7 +742,10 @@ pub fn get_token(gateway_code: &str, raw: bool) -> Result<()> {
715742
println!("{}", auth_response.machine_token);
716743
println!();
717744
println!("Usage:");
718-
println!(" export RUNBEAM_MACHINE_TOKEN=$(runbeam token:get -g {} --raw)", gateway_code);
745+
println!(
746+
" export RUNBEAM_MACHINE_TOKEN=$(runbeam token:get -g {} --raw)",
747+
gateway_code
748+
);
719749
}
720750

721751
info!("Token retrieved for gateway: {}", auth_response.gateway.id);

src/commands/harmony/harmony.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub fn harmony_add(
2828
port,
2929
label: final_label.clone(),
3030
path_prefix: path_prefix.to_string(),
31-
gateway_id: None, // Will be set after authorization
31+
gateway_id: None, // Will be set after authorization
3232
};
3333
crate::storage::add_harmony_instance(instance.clone())?;
3434

src/commands/harmony/install.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use anyhow::{Context, Result, anyhow};
2-
use reqwest::blocking::Client;
32
use directories::BaseDirs;
3+
use flate2::read::GzDecoder;
4+
use reqwest::blocking::Client;
45
use std::env::consts::{ARCH, OS};
56
use std::fs::{self};
6-
use std::io::{Cursor};
7+
use std::io::Cursor;
78
#[cfg(unix)]
89
use std::os::unix::fs::PermissionsExt;
9-
use std::path::{PathBuf};
10-
use flate2::read::GzDecoder;
10+
use std::path::PathBuf;
1111
use tar::Archive;
1212
use tracing::info;
1313

@@ -16,7 +16,7 @@ fn get_default_install_dir() -> Result<PathBuf> {
1616
if let Some(exe_dir) = base_dirs.executable_dir() {
1717
return Ok(exe_dir.to_path_buf());
1818
}
19-
19+
2020
// Fallback for platforms where executable_dir() is None (e.g. macOS, Windows)
2121
#[cfg(target_os = "macos")]
2222
{
@@ -34,8 +34,10 @@ fn get_default_install_dir() -> Result<PathBuf> {
3434
return Ok(base_dirs.home_dir().join(".local").join("bin"));
3535
}
3636
}
37-
38-
Err(anyhow!("Could not determine default installation directory"))
37+
38+
Err(anyhow!(
39+
"Could not determine default installation directory"
40+
))
3941
}
4042

4143
pub fn install(version: Option<&str>, output_dir: Option<PathBuf>) -> Result<()> {
@@ -53,7 +55,7 @@ pub fn install(version: Option<&str>, output_dir: Option<PathBuf>) -> Result<()>
5355
let filename = format!("harmony-{}.tar.gz", target);
5456
let url = if let Some(v) = version {
5557
if !v.starts_with('v') {
56-
return Err(anyhow!("Version must start with 'v' (e.g. v0.7.0)"));
58+
return Err(anyhow!("Version must start with 'v' (e.g. v0.7.0)"));
5759
}
5860
format!(
5961
"https://github.com/aurabx/harmony/releases/download/{}/{}",
@@ -91,12 +93,12 @@ pub fn install(version: Option<&str>, output_dir: Option<PathBuf>) -> Result<()>
9193
Some(d) => d,
9294
None => get_default_install_dir()?,
9395
};
94-
96+
9597
if !output_path.exists() {
9698
println!("Creating directory: {}", output_path.display());
9799
fs::create_dir_all(&output_path).context("Failed to create output directory")?;
98100
}
99-
101+
100102
println!("Extracting to {}", output_path.display());
101103

102104
let decoder = GzDecoder::new(Cursor::new(&content));
@@ -109,25 +111,36 @@ pub fn install(version: Option<&str>, output_dir: Option<PathBuf>) -> Result<()>
109111
// So it likely contains the `harmony` binary at the root of the archive.
110112

111113
// Let's extract to the target directory.
112-
archive.unpack(&output_path).context("Failed to unpack archive")?;
114+
archive
115+
.unpack(&output_path)
116+
.context("Failed to unpack archive")?;
113117

114118
// 6. Make executable (Unix only)
115119
#[cfg(unix)]
116120
{
117121
let binary_name = "harmony";
118122
let binary_path = output_path.join(binary_name);
119123
if binary_path.exists() {
120-
println!("Setting executable permissions for {}", binary_path.display());
124+
println!(
125+
"Setting executable permissions for {}",
126+
binary_path.display()
127+
);
121128
let mut perms = fs::metadata(&binary_path)?.permissions();
122129
perms.set_mode(0o755);
123130
fs::set_permissions(&binary_path, perms)?;
124131
} else {
125132
// It might be in a subdirectory if the tarball structure changed, but assuming README is correct.
126133
// If not found, we just warn.
127-
println!("Warning: '{}' not found in extraction output. You may need to find the binary manually.", binary_name);
134+
println!(
135+
"Warning: '{}' not found in extraction output. You may need to find the binary manually.",
136+
binary_name
137+
);
128138
}
129139
}
130140

131-
println!("✓ Harmony installed successfully to {}", output_path.display());
141+
println!(
142+
"✓ Harmony installed successfully to {}",
143+
output_path.display()
144+
);
132145
Ok(())
133146
}

src/commands/harmony/management.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,7 @@ pub fn reload(id: Option<&str>, label: Option<&str>) -> Result<()> {
263263

264264
pub fn update(id: Option<&str>, label: Option<&str>) -> Result<()> {
265265
let inst = resolve_instance(id, label)?;
266-
let url = format!(
267-
"{}/update",
268-
base_url(&inst)
269-
);
266+
let url = format!("{}/update", base_url(&inst));
270267
let client = Client::new();
271268
let resp = client
272269
.post(&url)
@@ -277,13 +274,18 @@ pub fn update(id: Option<&str>, label: Option<&str>) -> Result<()> {
277274
let json: Value = resp.json().context("parsing JSON response")?;
278275

279276
if status.is_success() {
280-
let config_size = json.get("config_size")
277+
let config_size = json
278+
.get("config_size")
281279
.and_then(|v| v.as_u64())
282280
.unwrap_or(0);
283-
println!("✓ Configuration uploaded successfully ({} bytes)", config_size);
281+
println!(
282+
"✓ Configuration uploaded successfully ({} bytes)",
283+
config_size
284+
);
284285
Ok(())
285286
} else {
286-
let message = json.get("message")
287+
let message = json
288+
.get("message")
287289
.and_then(|v| v.as_str())
288290
.unwrap_or("Unknown error");
289291
Err(anyhow!("Failed to update configuration: {}", message))

src/main.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,12 @@ fn main() -> Result<()> {
8787
Some(cli::Command::HarmonyReload { id, label }) => {
8888
harmony::management::reload(id.as_deref(), label.as_deref())?;
8989
}
90-
Some(cli::Command::HarmonyAuthorize { id, label, update, yes }) => {
90+
Some(cli::Command::HarmonyAuthorize {
91+
id,
92+
label,
93+
update,
94+
yes,
95+
}) => {
9196
auth::authorize_harmony(id.as_deref(), label.as_deref(), update, yes)?;
9297
}
9398
Some(cli::Command::HarmonySetKey { id, encryption_key }) => {
@@ -102,7 +107,10 @@ fn main() -> Result<()> {
102107
Some(cli::Command::HarmonyUpdate { id, label }) => {
103108
harmony::management::update(id.as_deref(), label.as_deref())?;
104109
}
105-
Some(cli::Command::HarmonyInstall { version, output_dir }) => {
110+
Some(cli::Command::HarmonyInstall {
111+
version,
112+
output_dir,
113+
}) => {
106114
harmony::install::install(version.as_deref(), output_dir)?;
107115
}
108116
Some(cli::Command::TestBrowser) => {

0 commit comments

Comments
 (0)