proxctl 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {proxctl-0.2.2 → proxctl-0.2.4}/CHANGELOG.md +10 -0
  2. {proxctl-0.2.2 → proxctl-0.2.4}/Cargo.lock +1 -1
  3. {proxctl-0.2.2 → proxctl-0.2.4}/Cargo.toml +1 -1
  4. {proxctl-0.2.2 → proxctl-0.2.4}/PKG-INFO +1 -1
  5. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/firewall.rs +83 -211
  6. {proxctl-0.2.2 → proxctl-0.2.4}/src/main.rs +54 -5
  7. {proxctl-0.2.2 → proxctl-0.2.4}/.github/workflows/ci.yml +0 -0
  8. {proxctl-0.2.2 → proxctl-0.2.4}/.github/workflows/release.yml +0 -0
  9. {proxctl-0.2.2 → proxctl-0.2.4}/.gitignore +0 -0
  10. {proxctl-0.2.2 → proxctl-0.2.4}/LICENSE +0 -0
  11. {proxctl-0.2.2 → proxctl-0.2.4}/Makefile +0 -0
  12. {proxctl-0.2.2 → proxctl-0.2.4}/README.md +0 -0
  13. {proxctl-0.2.2 → proxctl-0.2.4}/prek.toml +0 -0
  14. {proxctl-0.2.2 → proxctl-0.2.4}/proxctl_py/__init__.py +0 -0
  15. {proxctl-0.2.2 → proxctl-0.2.4}/proxctl_py/__main__.py +0 -0
  16. {proxctl-0.2.2 → proxctl-0.2.4}/pyproject.toml +0 -0
  17. {proxctl-0.2.2 → proxctl-0.2.4}/src/api/client.rs +0 -0
  18. {proxctl-0.2.2 → proxctl-0.2.4}/src/api/error.rs +0 -0
  19. {proxctl-0.2.2 → proxctl-0.2.4}/src/api/mod.rs +0 -0
  20. {proxctl-0.2.2 → proxctl-0.2.4}/src/api/token.rs +0 -0
  21. {proxctl-0.2.2 → proxctl-0.2.4}/src/api/types.rs +0 -0
  22. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/access.rs +0 -0
  23. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/api.rs +0 -0
  24. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/container.rs +0 -0
  25. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/diff.rs +0 -0
  26. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/firewall.rs +0 -0
  27. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/manifest.rs +0 -0
  28. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/mod.rs +0 -0
  29. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/reconciler.rs +0 -0
  30. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/apply/vm.rs +0 -0
  31. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/backup.rs +0 -0
  32. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/ceph.rs +0 -0
  33. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/cluster.rs +0 -0
  34. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/config.rs +0 -0
  35. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/firewall.rs +0 -0
  36. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/lifecycle.rs +0 -0
  37. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/migrate.rs +0 -0
  38. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/mod.rs +0 -0
  39. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/container/snapshot.rs +0 -0
  40. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/export.rs +0 -0
  41. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/mod.rs +0 -0
  42. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/node.rs +0 -0
  43. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/pool.rs +0 -0
  44. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/storage.rs +0 -0
  45. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/task.rs +0 -0
  46. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/agent.rs +0 -0
  47. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/cloudinit.rs +0 -0
  48. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/config.rs +0 -0
  49. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/firewall.rs +0 -0
  50. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/lifecycle.rs +0 -0
  51. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/migrate.rs +0 -0
  52. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/mod.rs +0 -0
  53. {proxctl-0.2.2 → proxctl-0.2.4}/src/commands/vm/snapshot.rs +0 -0
  54. {proxctl-0.2.2 → proxctl-0.2.4}/src/lib.rs +0 -0
  55. {proxctl-0.2.2 → proxctl-0.2.4}/src/output.rs +0 -0
  56. {proxctl-0.2.2 → proxctl-0.2.4}/src/schema.rs +0 -0
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
 
6
6
 
7
+
8
+
9
+ ## [0.2.4](https://github.com/rvben/proxctl/compare/v0.2.3...v0.2.4) - 2026-04-03
10
+
11
+ ### Added
12
+
13
+ - **init**: add API docs URL, colored output, and next steps to config init ([53e3ca9](https://github.com/rvben/proxctl/commit/53e3ca91dfd0f448205afb8da4902ace2c98880f))
14
+
15
+ ## [0.2.3](https://github.com/rvben/proxctl/compare/v0.2.2...v0.2.3) - 2026-04-03
16
+
7
17
  ## [0.2.2](https://github.com/rvben/proxctl/compare/v0.2.1...v0.2.2) - 2026-04-03
8
18
 
9
19
  ### Added
@@ -955,7 +955,7 @@ dependencies = [
955
955
 
956
956
  [[package]]
957
957
  name = "proxctl"
958
- version = "0.2.2"
958
+ version = "0.2.4"
959
959
  dependencies = [
960
960
  "clap",
961
961
  "clap_complete",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "proxctl"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  edition = "2024"
5
5
  rust-version = "1.90"
6
6
  description = "CLI for Proxmox VE"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxctl
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: System Administrators
@@ -1,4 +1,4 @@
1
- use clap::Subcommand;
1
+ use clap::{Args, Subcommand};
2
2
  use owo_colors::OwoColorize;
3
3
  use serde_json::json;
4
4
 
@@ -6,6 +6,40 @@ use crate::api::Error;
6
6
  use crate::api::client::ProxmoxClient;
7
7
  use crate::output::{OutputConfig, use_color};
8
8
 
9
+ #[derive(Args)]
10
+ pub struct FirewallRuleArgs {
11
+ /// Rule action (ACCEPT, DROP, REJECT)
12
+ #[arg(long)]
13
+ pub action: String,
14
+ /// Rule type (in, out, group)
15
+ #[arg(long, rename_all = "kebab-case")]
16
+ pub r#type: String,
17
+ /// Enable the rule
18
+ #[arg(long)]
19
+ pub enable: Option<bool>,
20
+ /// Network interface (e.g. vmbr0, vmbr0v30)
21
+ #[arg(long)]
22
+ pub iface: Option<String>,
23
+ /// Source address
24
+ #[arg(long)]
25
+ pub source: Option<String>,
26
+ /// Destination address
27
+ #[arg(long)]
28
+ pub dest: Option<String>,
29
+ /// Destination port
30
+ #[arg(long)]
31
+ pub dport: Option<String>,
32
+ /// Protocol
33
+ #[arg(long)]
34
+ pub proto: Option<String>,
35
+ /// Macro (e.g. SSH, HTTP, HTTPS)
36
+ #[arg(long, rename_all = "kebab-case")]
37
+ pub r#macro: Option<String>,
38
+ /// Comment
39
+ #[arg(long)]
40
+ pub comment: Option<String>,
41
+ }
42
+
9
43
  fn require_node<'a>(node: Option<&'a str>, global_node: Option<&'a str>) -> Result<&'a str, Error> {
10
44
  node.or(global_node)
11
45
  .ok_or_else(|| Error::Config("node name required (use --node or PROXMOX_NODE)".to_string()))
@@ -33,36 +67,8 @@ pub enum ClusterFirewallCommand {
33
67
  Rules,
34
68
  /// Add a cluster firewall rule
35
69
  Add {
36
- /// Rule action (ACCEPT, DROP, REJECT)
37
- #[arg(long)]
38
- action: String,
39
- /// Rule type (in, out, group)
40
- #[arg(long, rename_all = "kebab-case")]
41
- r#type: String,
42
- /// Enable the rule
43
- #[arg(long)]
44
- enable: Option<bool>,
45
- /// Network interface (e.g. vmbr0, vmbr0v30)
46
- #[arg(long)]
47
- iface: Option<String>,
48
- /// Source address
49
- #[arg(long)]
50
- source: Option<String>,
51
- /// Destination address
52
- #[arg(long)]
53
- dest: Option<String>,
54
- /// Destination port
55
- #[arg(long)]
56
- dport: Option<String>,
57
- /// Protocol
58
- #[arg(long)]
59
- proto: Option<String>,
60
- /// Macro (e.g. SSH, HTTP, HTTPS)
61
- #[arg(long, rename_all = "kebab-case")]
62
- r#macro: Option<String>,
63
- /// Comment
64
- #[arg(long)]
65
- comment: Option<String>,
70
+ #[command(flatten)]
71
+ rule: Box<FirewallRuleArgs>,
66
72
  },
67
73
  /// Delete a cluster firewall rule
68
74
  Delete {
@@ -88,36 +94,8 @@ pub enum NodeFirewallCommand {
88
94
  /// Node name
89
95
  #[arg(long)]
90
96
  node: Option<String>,
91
- /// Rule action (ACCEPT, DROP, REJECT)
92
- #[arg(long)]
93
- action: String,
94
- /// Rule type (in, out, group)
95
- #[arg(long, rename_all = "kebab-case")]
96
- r#type: String,
97
- /// Enable the rule
98
- #[arg(long)]
99
- enable: Option<bool>,
100
- /// Network interface (e.g. vmbr0, vmbr0v30)
101
- #[arg(long)]
102
- iface: Option<String>,
103
- /// Source address
104
- #[arg(long)]
105
- source: Option<String>,
106
- /// Destination address
107
- #[arg(long)]
108
- dest: Option<String>,
109
- /// Destination port
110
- #[arg(long)]
111
- dport: Option<String>,
112
- /// Protocol
113
- #[arg(long)]
114
- proto: Option<String>,
115
- /// Macro (e.g. SSH, HTTP, HTTPS)
116
- #[arg(long, rename_all = "kebab-case")]
117
- r#macro: Option<String>,
118
- /// Comment
119
- #[arg(long)]
120
- comment: Option<String>,
97
+ #[command(flatten)]
98
+ rule: Box<FirewallRuleArgs>,
121
99
  },
122
100
  /// Delete a node firewall rule
123
101
  Delete {
@@ -214,33 +192,19 @@ pub async fn run(
214
192
  match cmd {
215
193
  FirewallCommand::Cluster(sub) => match sub {
216
194
  ClusterFirewallCommand::Rules => cluster_rules(client, out).await,
217
- ClusterFirewallCommand::Add {
218
- action,
219
- r#type,
220
- enable,
221
- iface,
222
- source,
223
- dest,
224
- dport,
225
- proto,
226
- r#macro,
227
- comment,
228
- } => {
229
- cluster_add_rule(
230
- client,
231
- out,
232
- &action,
233
- &r#type,
234
- enable,
235
- iface.as_deref(),
236
- source.as_deref(),
237
- dest.as_deref(),
238
- dport.as_deref(),
239
- proto.as_deref(),
240
- r#macro.as_deref(),
241
- comment.as_deref(),
242
- )
243
- .await
195
+ ClusterFirewallCommand::Add { rule } => {
196
+ let params = build_rule_params(&rule);
197
+ let param_refs: Vec<(&str, &str)> = params
198
+ .iter()
199
+ .map(|(k, v)| (k.as_str(), v.as_str()))
200
+ .collect();
201
+ let _: serde_json::Value =
202
+ client.post("/cluster/firewall/rules", &param_refs).await?;
203
+ out.print_result(
204
+ &json!({"status": "rule added", "scope": "cluster"}),
205
+ "Cluster firewall rule added",
206
+ );
207
+ Ok(())
244
208
  }
245
209
  ClusterFirewallCommand::Delete { pos, yes } => {
246
210
  cluster_delete_rule(client, out, pos, yes).await
@@ -251,36 +215,20 @@ pub async fn run(
251
215
  let n = require_node(node.as_deref(), global_node)?;
252
216
  node_rules(client, out, n).await
253
217
  }
254
- NodeFirewallCommand::Add {
255
- node,
256
- action,
257
- r#type,
258
- enable,
259
- iface,
260
- source,
261
- dest,
262
- dport,
263
- proto,
264
- r#macro,
265
- comment,
266
- } => {
218
+ NodeFirewallCommand::Add { node, rule } => {
267
219
  let n = require_node(node.as_deref(), global_node)?;
268
- node_add_rule(
269
- client,
270
- out,
271
- n,
272
- &action,
273
- &r#type,
274
- enable,
275
- iface.as_deref(),
276
- source.as_deref(),
277
- dest.as_deref(),
278
- dport.as_deref(),
279
- proto.as_deref(),
280
- r#macro.as_deref(),
281
- comment.as_deref(),
282
- )
283
- .await
220
+ let params = build_rule_params(&rule);
221
+ let param_refs: Vec<(&str, &str)> = params
222
+ .iter()
223
+ .map(|(k, v)| (k.as_str(), v.as_str()))
224
+ .collect();
225
+ let path = format!("/nodes/{n}/firewall/rules");
226
+ let _: serde_json::Value = client.post(&path, &param_refs).await?;
227
+ out.print_result(
228
+ &json!({"status": "rule added", "scope": "node", "node": n}),
229
+ &format!("Node {n} firewall rule added"),
230
+ );
231
+ Ok(())
284
232
  }
285
233
  NodeFirewallCommand::Delete { node, pos, yes } => {
286
234
  let n = require_node(node.as_deref(), global_node)?;
@@ -351,46 +299,34 @@ fn print_rules(data: &[serde_json::Value]) {
351
299
  }
352
300
  }
353
301
 
354
- #[allow(clippy::too_many_arguments)]
355
- fn build_rule_params(
356
- action: &str,
357
- rule_type: &str,
358
- enable: Option<bool>,
359
- iface: Option<&str>,
360
- source: Option<&str>,
361
- dest: Option<&str>,
362
- dport: Option<&str>,
363
- proto: Option<&str>,
364
- r#macro: Option<&str>,
365
- comment: Option<&str>,
366
- ) -> Vec<(String, String)> {
302
+ fn build_rule_params(rule: &FirewallRuleArgs) -> Vec<(String, String)> {
367
303
  let mut params: Vec<(String, String)> = vec![
368
- ("action".to_string(), action.to_string()),
369
- ("type".to_string(), rule_type.to_string()),
304
+ ("action".to_string(), rule.action.clone()),
305
+ ("type".to_string(), rule.r#type.clone()),
370
306
  ];
371
- if let Some(e) = enable {
307
+ if let Some(e) = rule.enable {
372
308
  params.push(("enable".to_string(), if e { "1" } else { "0" }.to_string()));
373
309
  }
374
- if let Some(i) = iface {
375
- params.push(("iface".to_string(), i.to_string()));
310
+ if let Some(ref i) = rule.iface {
311
+ params.push(("iface".to_string(), i.clone()));
376
312
  }
377
- if let Some(s) = source {
378
- params.push(("source".to_string(), s.to_string()));
313
+ if let Some(ref s) = rule.source {
314
+ params.push(("source".to_string(), s.clone()));
379
315
  }
380
- if let Some(d) = dest {
381
- params.push(("dest".to_string(), d.to_string()));
316
+ if let Some(ref d) = rule.dest {
317
+ params.push(("dest".to_string(), d.clone()));
382
318
  }
383
- if let Some(dp) = dport {
384
- params.push(("dport".to_string(), dp.to_string()));
319
+ if let Some(ref dp) = rule.dport {
320
+ params.push(("dport".to_string(), dp.clone()));
385
321
  }
386
- if let Some(p) = proto {
387
- params.push(("proto".to_string(), p.to_string()));
322
+ if let Some(ref p) = rule.proto {
323
+ params.push(("proto".to_string(), p.clone()));
388
324
  }
389
- if let Some(m) = r#macro {
390
- params.push(("macro".to_string(), m.to_string()));
325
+ if let Some(ref m) = rule.r#macro {
326
+ params.push(("macro".to_string(), m.clone()));
391
327
  }
392
- if let Some(c) = comment {
393
- params.push(("comment".to_string(), c.to_string()));
328
+ if let Some(ref c) = rule.comment {
329
+ params.push(("comment".to_string(), c.clone()));
394
330
  }
395
331
  params
396
332
  }
@@ -412,37 +348,6 @@ async fn cluster_rules(client: &ProxmoxClient, out: OutputConfig) -> Result<(),
412
348
  Ok(())
413
349
  }
414
350
 
415
- #[allow(clippy::too_many_arguments)]
416
- async fn cluster_add_rule(
417
- client: &ProxmoxClient,
418
- out: OutputConfig,
419
- action: &str,
420
- rule_type: &str,
421
- enable: Option<bool>,
422
- iface: Option<&str>,
423
- source: Option<&str>,
424
- dest: Option<&str>,
425
- dport: Option<&str>,
426
- proto: Option<&str>,
427
- r#macro: Option<&str>,
428
- comment: Option<&str>,
429
- ) -> Result<(), Error> {
430
- let params = build_rule_params(
431
- action, rule_type, enable, iface, source, dest, dport, proto, r#macro, comment,
432
- );
433
- let param_refs: Vec<(&str, &str)> = params
434
- .iter()
435
- .map(|(k, v)| (k.as_str(), v.as_str()))
436
- .collect();
437
- let _: serde_json::Value = client.post("/cluster/firewall/rules", &param_refs).await?;
438
-
439
- out.print_result(
440
- &json!({"status": "rule added", "scope": "cluster"}),
441
- "Cluster firewall rule added",
442
- );
443
- Ok(())
444
- }
445
-
446
351
  async fn cluster_delete_rule(
447
352
  client: &ProxmoxClient,
448
353
  out: OutputConfig,
@@ -481,39 +386,6 @@ async fn node_rules(client: &ProxmoxClient, out: OutputConfig, node: &str) -> Re
481
386
  Ok(())
482
387
  }
483
388
 
484
- #[allow(clippy::too_many_arguments)]
485
- async fn node_add_rule(
486
- client: &ProxmoxClient,
487
- out: OutputConfig,
488
- node: &str,
489
- action: &str,
490
- rule_type: &str,
491
- enable: Option<bool>,
492
- iface: Option<&str>,
493
- source: Option<&str>,
494
- dest: Option<&str>,
495
- dport: Option<&str>,
496
- proto: Option<&str>,
497
- r#macro: Option<&str>,
498
- comment: Option<&str>,
499
- ) -> Result<(), Error> {
500
- let params = build_rule_params(
501
- action, rule_type, enable, iface, source, dest, dport, proto, r#macro, comment,
502
- );
503
- let param_refs: Vec<(&str, &str)> = params
504
- .iter()
505
- .map(|(k, v)| (k.as_str(), v.as_str()))
506
- .collect();
507
- let path = format!("/nodes/{node}/firewall/rules");
508
- let _: serde_json::Value = client.post(&path, &param_refs).await?;
509
-
510
- out.print_result(
511
- &json!({"status": "rule added", "scope": "node", "node": node}),
512
- &format!("Node {node} firewall rule added"),
513
- );
514
- Ok(())
515
- }
516
-
517
389
  async fn node_delete_rule(
518
390
  client: &ProxmoxClient,
519
391
  out: OutputConfig,
@@ -537,6 +537,24 @@ fn normalize_host(host: &str) -> String {
537
537
  }
538
538
  }
539
539
 
540
+ fn sym_ok() -> String {
541
+ use owo_colors::OwoColorize;
542
+ if proxctl::output::use_color() {
543
+ "✔".green().to_string()
544
+ } else {
545
+ "✔".to_owned()
546
+ }
547
+ }
548
+
549
+ fn sym_dim(s: &str) -> String {
550
+ use owo_colors::OwoColorize;
551
+ if proxctl::output::use_color() {
552
+ s.dimmed().to_string()
553
+ } else {
554
+ s.to_owned()
555
+ }
556
+ }
557
+
540
558
  async fn run_config_init() -> Result<(), Error> {
541
559
  use dialoguer::{Confirm, Input};
542
560
 
@@ -589,7 +607,9 @@ async fn run_config_init() -> Result<(), Error> {
589
607
  .map_err(|e| Error::Other(format!("failed to read password: {e}")))?;
590
608
 
591
609
  // Step 6: Verify credentials — POST to /api2/json/access/ticket
592
- println!("Connecting to {base_url} ...");
610
+ let sep = sym_dim("──────────────");
611
+ eprintln!();
612
+ eprintln!(" Connecting to {base_url} ...");
593
613
 
594
614
  let http = reqwest::Client::builder()
595
615
  .danger_accept_invalid_certs(insecure)
@@ -627,7 +647,16 @@ async fn run_config_init() -> Result<(), Error> {
627
647
  .ok_or_else(|| Error::Auth("ticket response missing data.CSRFPreventionToken".to_string()))?
628
648
  .to_string();
629
649
 
630
- println!("Authenticated. Creating API token ...");
650
+ eprintln!(" {} Authenticated", sym_ok());
651
+ eprintln!();
652
+ eprintln!(
653
+ " {}",
654
+ sym_dim(&format!(
655
+ "API docs: {base_url}/pve-docs/api-viewer/index.html"
656
+ ))
657
+ );
658
+ eprintln!();
659
+ eprintln!(" Creating API token ...");
631
660
 
632
661
  // Step 7: Create API token
633
662
  let token_id = "proxctl";
@@ -644,7 +673,7 @@ async fn run_config_init() -> Result<(), Error> {
644
673
 
645
674
  let token_json: serde_json::Value = if create_resp.status().as_u16() == 400 {
646
675
  // Token already exists — delete it and recreate
647
- println!("Token already exists, recreating ...");
676
+ eprintln!(" Token already exists, recreating ...");
648
677
 
649
678
  let del_resp = http
650
679
  .delete(&token_url)
@@ -705,8 +734,28 @@ async fn run_config_init() -> Result<(), Error> {
705
734
  insecure,
706
735
  )?;
707
736
 
708
- println!("Config saved to {}", config_path.display());
709
- println!("Run `proxctl health` to verify connectivity.");
737
+ eprintln!();
738
+ eprintln!(
739
+ " {} Configuration saved to {}",
740
+ sym_ok(),
741
+ config_path.display()
742
+ );
743
+ eprintln!();
744
+ eprintln!("{sep}");
745
+ eprintln!(" Next steps:");
746
+ eprintln!(
747
+ " {}",
748
+ sym_dim("proxctl health # verify connectivity")
749
+ );
750
+ eprintln!(
751
+ " {}",
752
+ sym_dim("proxctl vm list # list virtual machines")
753
+ );
754
+ eprintln!(
755
+ " {}",
756
+ sym_dim("proxctl completions zsh # shell completions")
757
+ );
758
+ eprintln!("{sep}");
710
759
 
711
760
  Ok(())
712
761
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes