@smbcloud / cli / commits / 371948f

wip

keypair34 committed May 2, 2026 at 20:26 UTC 371948f8e291031420b24a327a3ca40de68527d7
11 files changed +49 -80
crates/cli/README.md
+6 -6
@@ -22,7 +22,7 @@
22
23 ## Installation
24
25 -One can install this program in different ways.
25 +You can install the CLI a few different ways.
26
27 ### With Cargo
28
@@ -51,7 +51,7 @@ pip install smbcloud-cli
51
52 ## Update
53
54 -Simply rerun the installation command.
54 +To update the CLI, run the same install command again.
55
56 ## Uninstall
57
@@ -78,16 +78,16 @@ smb --help
78
79 ## Contribution
80
81 -- Setup your Rust tooling.
81 +- Set up your Rust tooling.
82 - Clone the repo.
83 -- Provide the environment variables in the .env.local.
83 +- Add the required environment variables to `.env.local`.
84 - Run `cargo run`.
85
86 ## Credits
87
88 -This repo is inspired by [Sugar](https://github.com/metaplex-foundation/sugar).
88 +This repo draws inspiration from [Sugar](https://github.com/metaplex-foundation/sugar).
89
90 -This repo tries to follow [the 12 factor CLI app](https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46) principles by
90 +It also borrows ideas from [the 12 factor CLI app](https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46).
91
92 > Explore more on the [smbCloud Services](https://smbcloud.xyz/services) page.
93
crates/cli/src/account/cli.rs
+4 -4
@@ -2,12 +2,12 @@ use clap::Subcommand;
2
3 #[derive(Subcommand)]
4 pub enum Commands {
5 - #[clap(about = "Create an account. Use your email as your username.")]
5 + #[clap(about = "Create an account with your email address.")]
6 Signup {},
7 - #[clap(about = "Login to your account. To create an account, use smb signup.")]
7 + #[clap(about = "Log in to your account. If you need one first, run `smb signup`.")]
8 Login {},
9 - #[clap(about = "Logout all session.")]
9 + #[clap(about = "Log out of your current session.")]
10 Logout {},
11 - #[clap(about = "Forgot email? Use this command to reset your password.")]
11 + #[clap(about = "Start the password reset flow.")]
12 Forgot {},
13 }
crates/cli/src/account/forgot/mod.rs
+1 -1
@@ -13,7 +13,7 @@ use smbcloud_utils::email_validation;
13 use spinners::Spinner;
14
15 pub async fn process_forgot(env: Environment) -> Result<CommandResult> {
16 - println!("Provide your login credentials.");
16 + println!("Enter your email address.");
17 let email = Input::<String>::with_theme(&ColorfulTheme::default())
18 .with_prompt("Email")
19 .validate_with(|email: &String| email_validation(email))
crates/cli/src/account/lib.rs
+6 -16
@@ -25,9 +25,7 @@ use {
25 };
26
27 pub async fn authorize_github(env: &Environment) -> Result<SmbAuthorization> {
28 - // Spin up a simple localhost server to listen for the GitHub OAuth callback
29 - // setup_oauth_callback_server();
30 - // Open the GitHub OAuth URL in the user's browser
28 + // Listen for the GitHub OAuth callback on localhost, then open the browser.
29 let mut spinner = Spinner::new(
30 spinners::Spinners::BouncingBall,
31 style("🚀 Getting your GitHub information...")
@@ -61,7 +59,6 @@ pub async fn authorize_github(env: &Environment) -> Result<SmbAuthorization> {
59 match rx.recv() {
60 Ok(code) => {
61 debug!("Got code from channel: {:#?}", &code);
64 - //Err(anyhow!("Failed to get code from channel."))
62 process_connect_github(*env, code).await
63 }
64 Err(e) => {
@@ -144,7 +141,6 @@ fn handle_connection(mut stream: TcpStream, tx: Sender<String>) {
141 stream.flush().unwrap();
142 }
143
147 -// Get access token
144 pub async fn process_connect_github(env: Environment, code: String) -> Result<SmbAuthorization> {
145 let response = Client::new()
146 .post(build_authorize_smb_url(env))
@@ -160,10 +156,8 @@ pub async fn process_connect_github(env: Environment, code: String) -> Result<Sm
156 .bold()
157 .to_string(),
158 );
163 - // println!("Response: {:#?}", &response);
159 match response.status() {
160 StatusCode::OK => {
166 - // Account authorized and token received
161 spinner.stop_and_persist("✅", "You are logged in with your GitHub account!".into());
162 save_token(env, &response).await?;
163 let result = response.json().await?;
@@ -171,21 +165,16 @@ pub async fn process_connect_github(env: Environment, code: String) -> Result<Sm
165 Ok(result)
166 }
167 StatusCode::NOT_FOUND => {
174 - // Account not found and we show signup option
175 - spinner.stop_and_persist("🥲", "Account not found. Please signup!".into());
168 + spinner.stop_and_persist("🥲", "Account not found. Please sign up.".into());
169 let result = response.json().await?;
177 - // println!("Result: {:#?}", &result);
170 Ok(result)
171 }
172 StatusCode::UNPROCESSABLE_ENTITY => {
181 - // Account found but email not verified
182 - spinner.stop_and_persist("🥹", "Unverified email!".into());
173 + spinner.stop_and_persist("🥹", "Please verify your email address.".into());
174 let result = response.json().await?;
184 - // println!("Result: {:#?}", &result);
175 Ok(result)
176 }
177 _ => {
188 - // Other errors
178 let error = anyhow!("Error while authorizing with GitHub.");
179 Err(error)
180 }
@@ -222,13 +211,14 @@ fn github_base_url_builder() -> URLBuilder {
211
212 pub async fn save_token(env: Environment, response: &Response) -> Result<()> {
213 let headers = response.headers();
225 - // println!("Headers: {:#?}", &headers);
214 match headers.get("Authorization") {
215 Some(token) => {
216 debug!("{}", token.to_str()?);
217 store_token(env, token.to_str()?.to_string()).await
218 }
231 - None => Err(anyhow!("Failed to get token. Probably a backend issue.")),
219 + None => Err(anyhow!(
220 + "Failed to get the access token from the server response."
221 + )),
222 }
223 }
224
crates/cli/src/account/login/process.rs
+13 -27
@@ -78,8 +78,6 @@ pub async fn process_login(env: Environment, is_logged_in: Option<bool>) -> Resu
78 }
79 }
80
81 -// Private functions
82 -
81 async fn login_with_github(env: Environment) -> Result<CommandResult> {
82 match authorize_github(&env).await {
83 Ok(result) => process_authorization(env, result).await,
@@ -91,15 +89,13 @@ async fn login_with_github(env: Environment) -> Result<CommandResult> {
89 }
90
91 async fn process_authorization(env: Environment, auth: SmbAuthorization) -> Result<CommandResult> {
94 - // What to do if not logged in with GitHub?
95 - // Check error_code first
92 + // Handle the account state returned by the OAuth flow before treating it as a login.
93 if let Some(error_code) = auth.error_code {
94 debug!("{}", error_code);
95 match error_code {
96 EmailNotFound => return create_new_account(env, auth.user_email, auth.user_info).await,
97 EmailUnverified => return send_email_verification(env, auth.user).await,
98 PasswordNotSet => {
102 - // Only for email and password login
99 let error = anyhow!("Password not set.");
100 return Err(error);
101 }
@@ -107,14 +103,12 @@ async fn process_authorization(env: Environment, auth: SmbAuthorization) -> Resu
103 }
104 }
105
110 - // Logged in with GitHub!
111 - // Token handling is in the lib.rs account module.
106 + // Token handling lives in the account module.
107 if let Some(user) = auth.user {
108 let spinner = Spinner::new(
109 spinners::Spinners::SimpleDotsScrolling,
110 style("Logging you in...").green().bold().to_string(),
111 );
117 - // We're logged in with GitHub.
112 return Ok(CommandResult {
113 spinner,
114 symbol: "✅".to_owned(),
@@ -142,7 +136,6 @@ async fn create_new_account(
136 }
137 };
138
145 - // Create account if user confirms
139 if !confirm {
140 let spinner = Spinner::new(
141 spinners::Spinners::SimpleDotsScrolling,
@@ -169,11 +162,10 @@ async fn create_new_account(
162 return do_signup(env, &params).await;
163 }
164
172 - Err(anyhow!("Shouldn't be here."))
165 + Err(anyhow!("GitHub returned incomplete account details."))
166 }
167
168 async fn send_email_verification(env: Environment, user: Option<User>) -> Result<CommandResult> {
176 - // Return early if user is null
169 if let Some(user) = user {
170 let confirm = match Confirm::with_theme(&ColorfulTheme::default())
171 .with_prompt("Do you want to send a new verification email?")
@@ -186,16 +178,15 @@ async fn send_email_verification(env: Environment, user: Option<User>) -> Result
178 }
179 };
180
189 - // Send verification email if user confirms
181 if !confirm {
182 let spinner = Spinner::new(
183 spinners::Spinners::SimpleDotsScrolling,
193 - style("Cancel operation.").green().bold().to_string(),
184 + style("Cancelled.").green().bold().to_string(),
185 );
186 return Ok(CommandResult {
187 spinner,
188 symbol: succeed_symbol(),
198 - msg: succeed_message("Doing nothing."),
189 + msg: succeed_message("Cancelled."),
190 });
191 }
192 resend_email_verification(env, user).await
@@ -238,16 +229,15 @@ async fn connect_github_account(env: Environment, auth: SmbAuthorization) -> Res
229 }
230 };
231
241 - // Link GitHub account if user confirms
232 if !confirm {
233 let spinner = Spinner::new(
234 spinners::Spinners::SimpleDotsScrolling,
245 - succeed_message("Cancel operation."),
235 + succeed_message("Cancelled."),
236 );
237 return Ok(CommandResult {
238 spinner,
239 symbol: succeed_symbol(),
250 - msg: succeed_message("Doing nothing."),
240 + msg: succeed_message("Cancelled."),
241 });
242 }
243
@@ -278,7 +268,7 @@ async fn connect_github_account(env: Environment, auth: SmbAuthorization) -> Res
268 }
269
270 async fn login_with_email(env: Environment) -> Result<CommandResult> {
281 - println!("Provide your login credentials.");
271 + println!("Enter your login details.");
272 let username = match Input::<String>::with_theme(&ColorfulTheme::default())
273 .with_prompt("Email")
274 .validate_with(|email: &String| email_validation(email))
@@ -293,9 +283,7 @@ async fn login_with_email(env: Environment) -> Result<CommandResult> {
283
284 match check_email(env, client(), &username).await {
285 Ok(auth) => {
296 - // Only continue with password input if email is found and confirmed.
286 if auth.error_code.is_some() {
298 - // Check if email is in the database, unconfirmed. Only presents password input if email is found and confirmed.
287 let spinner = Spinner::new(
288 spinners::Spinners::SimpleDotsScrolling,
289 succeed_message("Checking email"),
@@ -382,11 +370,11 @@ async fn after_checking_email_step(
370 }
371 _ => {
372 spinner.stop_and_persist(&fail_symbol(), fail_message("An error occurred."));
385 - Err(anyhow!("Idk what happened."))
373 + Err(anyhow!("The server returned an unexpected account state."))
374 }
375 }
376 }
389 - None => Err(anyhow!("Shouldn't be here.")),
377 + None => Err(anyhow!("The server did not return an account state.")),
378 }
379 }
380
@@ -421,13 +409,12 @@ async fn action_on_account_status(
409 }
410 _ => {
411 spinner.stop_and_persist(&fail_symbol(), fail_message("An error occurred."));
424 - Err(anyhow!("Idk what happened."))
412 + Err(anyhow!("The server returned an unexpected account state."))
413 }
414 }
415 }
416
417 async fn send_reset_password(env: Environment, user: Option<User>) -> Result<CommandResult> {
430 - // Return early if user is null
418 if let Some(user) = user {
419 let confirm = match Confirm::with_theme(&ColorfulTheme::default())
420 .with_prompt("Do you want to reset your password?")
@@ -440,16 +427,15 @@ async fn send_reset_password(env: Environment, user: Option<User>) -> Result<Com
427 }
428 };
429
443 - // Send verification email if user confirms
430 if !confirm {
431 let spinner = Spinner::new(
432 spinners::Spinners::SimpleDotsScrolling,
447 - style("Cancel operation.").green().bold().to_string(),
433 + style("Cancelled.").green().bold().to_string(),
434 );
435 return Ok(CommandResult {
436 spinner,
437 symbol: succeed_symbol(),
452 - msg: succeed_message("Doing nothing."),
438 + msg: succeed_message("Cancelled."),
439 });
440 }
441 resend_reset_password_instruction(env, user).await
crates/cli/src/account/logout/mod.rs
+2 -4
@@ -14,7 +14,6 @@ use {
14 };
15
16 pub async fn process_logout(env: Environment) -> Result<CommandResult> {
17 - // Logout if user confirms
17 if let Some(token_path) = smb_token_file_path(env) {
18 let confirm = match Confirm::with_theme(&ColorfulTheme::default())
19 .with_prompt("Do you want to logout? y/n")
@@ -30,10 +29,10 @@ pub async fn process_logout(env: Environment) -> Result<CommandResult> {
29 return Ok(CommandResult {
30 spinner: Spinner::new(
31 spinners::Spinners::SimpleDotsScrolling,
33 - succeed_message("Cancel operation"),
32 + succeed_message("Cancelled."),
33 ),
34 symbol: succeed_symbol(),
36 - msg: succeed_message("Doing nothing."),
35 + msg: succeed_message("Cancelled."),
36 });
37 }
38
@@ -42,7 +41,6 @@ pub async fn process_logout(env: Environment) -> Result<CommandResult> {
41 succeed_message("Logging you out"),
42 );
43
45 - // Call backend
44 match do_process_logout(env).await {
45 Ok(_) => {
46 fs::remove_file(token_path)?;
crates/smbcloud-model/src/error_codes.rs
+2 -2
@@ -39,7 +39,7 @@ pub enum ErrorCode {
39 MissingConfig = 4,
40 // #[error("Missing id in repository. Please regenerate with 'smb init'.")]
41 // MissingId,
42 - #[error("Cancel operation.")]
42 + #[error("Cancelled.")]
43 Cancel = 5,
44 // Account
45 #[error("Unauthorized access.")]
@@ -122,7 +122,7 @@ impl ErrorCode {
122 // CLI Generic errors
123 ErrorCode::InputError => "Input error.",
124 ErrorCode::MissingConfig => "Missing config.",
125 - ErrorCode::Cancel => "Cancelled operation.",
125 + ErrorCode::Cancel => "Cancelled.",
126 // Projects
127 ErrorCode::ProjectNotFound => "Project not found.",
128 ErrorCode::UnsupportedRunner => "Unsupported runner.",
crates/smbcloud-networking/README.md
+1 -1
@@ -2,7 +2,7 @@
2
3 Low-level HTTP client helpers for smbCloud services.
4
5 -This crate was inspired by this excellent blog post: [Designing a Web API client in Rust](https://nullderef.com/blog/web-api-client/).
5 +This crate takes a few cues from [Designing a Web API client in Rust](https://nullderef.com/blog/web-api-client/).
6
7 > Explore more on the [smbCloud Services](https://smbcloud.xyz/services) page.
8
npm/smbcloud-cli/src/index.ts
+7 -5
@@ -20,17 +20,19 @@ function getExePath() {
20 }
21
22 try {
23 - // Since the binary will be located inside `node_modules`, we can simply call `require.resolve`
24 - return require.resolve(`@smbcloud/cli-${os}-${arch}/bin/smb${extension}`);
23 + // The binary lives in `node_modules`, so `require.resolve` gives us the installed path.
24 + return require.resolve(
25 + `@smbcloud/cli-${os}-${arch}/bin/smb${extension}`,
26 + );
27 } catch (e) {
28 throw new Error(
27 - `Couldn't find application binary inside node_modules for ${os}-${arch}`
29 + `Couldn't find application binary inside node_modules for ${os}-${arch}`,
30 );
31 }
32 }
33
34 /**
33 - * Runs the application with args using nodejs spawn
35 + * Runs the native CLI with the current process arguments.
36 */
37 function run() {
38 const args = process.argv.slice(2);
@@ -38,4 +40,4 @@ function run() {
40 process.exit(processResult.status ?? 0);
41 }
42
41 -run();
\ No newline at end of file
43 +run();
sdk/gems/model/lib/model.rb
-1
@@ -5,5 +5,4 @@ require_relative 'model/model'
5
6 module Model
7 class Error < StandardError; end
8 - # Your code goes here...
8 end
sdk/gems/model/model.gemspec
+7 -13
@@ -5,12 +5,12 @@ require_relative 'lib/model/version'
5 Gem::Specification.new do |spec|
6 spec.name = 'smbcloud-model'
7 spec.version = Model::VERSION
8 - spec.authors = ["paydii"]
8 + spec.authors = ["Seto Elkahfi"]
9 spec.email = ["hej@setoelkahfi.se"]
10
11 - spec.summary = "Ruby binding for smbcloud-cli model."
12 - spec.description = "Ruby binding for smbcloud-cli modellllllllllll."
13 - spec.homepage = "https://github.com/smbcloudXYZ/smbcloud-cli/tree/development/gems/model"
11 + spec.summary = "Ruby bindings for the smbCloud model types."
12 + spec.description = "Ruby bindings for shared smbCloud model types, backed by a native Rust extension."
13 + spec.homepage = "https://github.com/smbcloudXYZ/smbcloud-cli/tree/main/sdk/gems/model"
14 spec.license = "MIT"
15 spec.required_ruby_version = ">= 3.1.0"
16 spec.required_rubygems_version = ">= 3.3.11"
@@ -18,11 +18,10 @@ Gem::Specification.new do |spec|
18 spec.metadata["allowed_push_host"] = "https://rubygems.org"
19
20 spec.metadata["homepage_uri"] = spec.homepage
21 - spec.metadata["source_code_uri"] = "https://github.com/smbcloudXYZ/smbcloud-cli/tree/development/gems/model"
22 - spec.metadata["changelog_uri"] = "https://github.com/smbcloudXYZ/smbcloud-cli/tree/development/gems/model/CHANGELOG.md"
21 + spec.metadata["source_code_uri"] = "https://github.com/smbcloudXYZ/smbcloud-cli/tree/main/sdk/gems/model"
22 + spec.metadata["changelog_uri"] = "https://github.com/smbcloudXYZ/smbcloud-cli/tree/main/sdk/gems/model/CHANGELOG.md"
23
24 - # Specify which files should be added to the gem when it is released.
25 - # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24 + # Build the release package from tracked files in git.
25 gemspec = File.basename(__FILE__)
26 spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
27 ls.readlines("\x0", chomp: true).reject do |f|
@@ -35,10 +34,5 @@ Gem::Specification.new do |spec|
34 spec.require_paths = ["lib"]
35 spec.extensions = ["ext/model/extconf.rb"]
36
38 - # Uncomment to register a new dependency of your gem
39 - # spec.add_dependency "example-gem", "~> 1.0"
37 spec.add_dependency "rb_sys", "~> 0.9.91"
41 -
42 - # For more information and examples about making a new gem, check out our
43 - # guide at: https://bundler.io/guides/creating_gem.html
38 end