1pub const ALIAS_EMPTY: &str = "Alias can't be empty. Use --alias to specify one.";
4pub const ALIAS_WHITESPACE: &str =
5 "Alias can't contain whitespace. Use --alias to pick a simpler name.";
6pub const ALIAS_PATTERN_CHARS: &str =
7 "Alias can't contain pattern characters. Use --alias to pick a different name.";
8pub const HOSTNAME_WHITESPACE: &str = "Hostname can't contain whitespace.";
9pub const USER_WHITESPACE: &str = "User can't contain whitespace.";
10pub const PASSWORD_EMPTY: &str = "Password can't be empty.";
11pub const CANCELLED: &str = "Cancelled.";
12pub use super::SNIPPET_DESCRIPTION_CONTROL_CHARS as DESCRIPTION_CONTROL_CHARS;
15
16pub use super::contains_control_chars as control_chars;
17
18pub use super::AWS_SSM_WITHOUT_PROFILE;
21pub use super::aws_ssm_profile_unsafe;
22
23pub use super::welcome_aboard as welcome;
24
25pub fn gen_assets_done(count: usize, dir: &str) -> String {
29 format!("Wrote {count} SVG asset(s) to {dir}.")
30}
31
32pub fn gen_assets_hero_done(path: &str) -> String {
35 format!("Wrote animated hero SVG to {path}.")
36}
37
38pub const IMPORT_NO_FILE: &str =
41 "Provide a file or use --known-hosts. Run 'purple import --help' for details.";
42
43pub const NO_PROVIDERS: &str = "No providers configured. Run 'purple provider add' to set one up.";
46
47pub const PROVIDER_LIST: &str = "digitalocean, vultr, linode, hetzner, upcloud, proxmox, aws, \
51 scaleway, gcp, azure, tailscale, oracle, ovh, leaseweb, i3d, transip, teleport, netbox";
52
53pub fn unknown_provider(name: &str) -> String {
57 format!("Never heard of '{}'. Try: {}.", name, PROVIDER_LIST)
58}
59
60pub fn skipping_unknown_provider(name: &str) -> String {
64 format!(
65 "Skipping unknown provider '{}'. Try: {}.",
66 name, PROVIDER_LIST
67 )
68}
69
70pub fn alias_already_exists(alias: &str) -> String {
73 format!(
74 "'{}' already exists. Use --alias to pick a different name.",
75 alias
76 )
77}
78
79pub fn import_parse_failures(count: usize) -> String {
82 let s = if count == 1 { "" } else { "s" };
83 format!(
84 "! {} line{} could not be parsed (invalid format).",
85 count, s
86 )
87}
88
89pub fn import_read_errors(count: usize) -> String {
90 let s = if count == 1 { "" } else { "s" };
91 format!("! {} line{} could not be read (encoding error).", count, s)
92}
93
94pub fn no_config_for(provider: &str) -> String {
95 format!(
96 "No configuration for {}. Run 'purple provider add {}' first.",
97 provider, provider
98 )
99}
100
101pub fn saved_config(provider: &str) -> String {
102 format!("Saved {} configuration.", provider)
103}
104
105pub fn no_config_to_remove(provider: &str) -> String {
106 format!("No configuration for '{}'. Nothing to remove.", provider)
107}
108
109pub fn removed_config(provider: &str) -> String {
110 format!("Removed {} configuration.", provider)
111}
112
113pub fn removed_configs(provider: &str, count: usize) -> String {
114 format!("Removed {} {} configurations.", count, provider)
115}
116
117pub fn invalid_label_flag(reason: &str) -> String {
118 format!("Invalid --label: {}", reason)
119}
120
121pub fn add_requires_label(provider: &str) -> String {
122 format!(
123 "Provider '{}' already has labeled configs. Pass --label to add another.",
124 provider
125 )
126}
127
128pub fn add_label_collides_with_bare(provider: &str) -> String {
129 format!(
130 "Provider '{}' has a bare config. Remove it first or use the TUI add flow which prompts for labels.",
131 provider
132 )
133}
134
135pub fn no_tunnels_for(alias: &str) -> String {
138 format!("No tunnels configured for {}.", alias)
139}
140
141pub fn tunnels_for(alias: &str) -> String {
142 format!("Tunnels for {}:", alias)
143}
144
145pub const NO_TUNNELS: &str = "No tunnels configured.";
146
147pub fn starting_tunnel(alias: &str) -> String {
148 format!("Starting tunnel for {}... (Ctrl+C to stop)", alias)
149}
150
151pub fn host_not_found(alias: &str) -> String {
152 format!("No host '{}' found.", alias)
153}
154
155pub fn added_forward(forward: &str, alias: &str) -> String {
156 format!("Added {} to {}.", forward, alias)
157}
158
159pub fn forward_exists(forward: &str, alias: &str) -> String {
160 format!("Forward {} already exists on {}.", forward, alias)
161}
162
163pub fn forward_not_found(forward: &str, alias: &str) -> String {
164 format!("No matching forward {} found on {}.", forward, alias)
165}
166
167pub fn removed_forward(forward: &str, alias: &str) -> String {
168 format!("Removed {} from {}.", forward, alias)
169}
170
171pub fn no_forwards(alias: &str) -> String {
172 format!("No forwarding directives configured for '{}'.", alias)
173}
174
175pub fn save_config_failed(e: &impl std::fmt::Display) -> String {
176 format!("Failed to save config: {}", e)
177}
178
179pub fn included_host_read_only(alias: &str) -> String {
180 format!(
181 "Host '{}' is from an included file and cannot be modified.",
182 alias
183 )
184}
185
186pub fn operation_failed(e: &impl std::fmt::Display) -> String {
187 format!("Failed: {}", e)
188}
189
190pub const NO_SNIPPETS: &str = "No snippets configured. Use 'purple snippet add' to create one.";
193
194pub use super::snippet_added;
195pub use super::snippet_removed;
196pub use super::snippet_updated;
197
198pub const SNIPPET_NOT_FOUND_HINT: &str = "Tip: bash or zsh host? Run purple snippet add again \
201 with --interactive (and --description, if it has one) to load ~/.bashrc or ~/.zshrc.";
202
203pub fn snippet_not_found(name: &str) -> String {
204 format!("No snippet '{}' found.", name)
205}
206
207pub fn no_hosts_with_tag(tag: &str) -> String {
208 format!("No hosts found with tag '{}'.", tag)
209}
210
211pub const SPECIFY_TARGET: &str = "Specify a host alias, --tag or --all.";
212
213pub fn beaming_up(alias: &str) -> String {
216 format!("Beaming you up to {}...\n", alias)
217}
218
219pub fn running_snippet_on(name: &str, alias: &str) -> String {
220 format!("Running '{}' on {}...\n", name, alias)
221}
222
223pub fn host_separator(alias: &str) -> String {
224 format!("── {} ──", alias)
225}
226
227pub fn exited_with_code(code: i32) -> String {
228 format!("Exited with code {}.", code)
229}
230
231pub const DONE: &str = "Done.";
232
233pub fn done_multi(name: &str, count: usize) -> String {
234 format!("Done. Ran '{}' on {} hosts.", name, count)
235}
236
237pub const PRESS_ENTER: &str = "Press Enter to continue...";
238
239pub fn host_failed(alias: &str, e: &impl std::fmt::Display) -> String {
240 format!("[{}] Failed: {}", alias, e)
241}
242
243pub fn skipping_host(alias: &str, e: &impl std::fmt::Display) -> String {
244 format!("Skipping {}: {}", alias, e)
245}
246
247pub fn password_removed(alias: &str) -> String {
250 format!("Password removed for {}.", alias)
251}
252
253pub fn log_deleted(path: &impl std::fmt::Display) -> String {
256 format!("Log file deleted: {}", path)
257}
258
259pub fn no_log_file(path: &impl std::fmt::Display) -> String {
260 format!("No log file found at {}", path)
261}
262
263pub const BUILTIN_THEMES: &str = "Built-in themes:";
266pub const CUSTOM_THEMES: &str = "\nCustom themes:";
267
268pub fn theme_set(name: &str) -> String {
269 format!("Theme set to: {}", name)
270}
271
272pub fn syncing(name: &str, summary: &str) -> String {
275 format!("\x1b[2K\rSyncing {}... {}", name, summary)
276}
277
278pub fn syncing_start(name: &str) -> String {
283 format!("Syncing {}... ", name)
284}
285
286pub fn vault_signing_host(alias: &str) -> String {
290 format!("Signing {}... ", alias)
291}
292
293pub fn vault_sign_host_block_gone(alias: &str) -> String {
298 format!(
299 " warning: {} no longer in ssh config; CertificateFile not written (cert saved on disk)",
300 alias
301 )
302}
303
304pub const SYNC_FAILED: &str = "failed.";
308
309pub fn servers_found_with_failures(count: usize, failures: usize, total: usize) -> String {
310 format!(
311 "{} servers found ({} of {} failed to fetch).",
312 count, failures, total
313 )
314}
315
316pub fn servers_found(count: usize) -> String {
317 format!("{} servers found.", count)
318}
319
320pub fn sync_result(prefix: &str, added: usize, updated: usize, unchanged: usize) -> String {
321 format!(
322 "{}Added {}, updated {}, unchanged {}.",
323 prefix, added, updated, unchanged
324 )
325}
326
327pub fn sync_removed(count: usize) -> String {
328 format!(" Removed {}.", count)
329}
330
331pub fn sync_stale(count: usize) -> String {
332 format!(" Marked {} stale.", count)
333}
334
335pub fn sync_skip_remove(display_name: &str) -> String {
336 format!(
337 "! {}: skipping --remove due to partial failures.",
338 display_name
339 )
340}
341
342pub fn sync_error(display_name: &str, e: &impl std::fmt::Display) -> String {
343 format!("! {}: {}", display_name, e)
344}
345
346pub const SYNC_SKIP_WRITE: &str =
347 "! Skipping config write due to sync failures. Fix the errors and re-run.";
348
349pub const PROXMOX_URL_REQUIRED: &str =
352 "Proxmox requires --url (e.g. --url https://pve.example.com:8006).";
353pub const NETBOX_URL_REQUIRED: &str =
354 "NetBox requires --url (e.g. --url https://netbox.example.com).";
355pub const AWS_REGIONS_REQUIRED: &str =
356 "AWS requires --regions (e.g. --regions us-east-1,eu-west-1).";
357pub const AZURE_REGIONS_REQUIRED: &str =
358 "Azure requires --regions with one or more subscription IDs.";
359pub const GCP_PROJECT_REQUIRED: &str = "GCP requires --project (e.g. --project my-gcp-project-id).";
360pub use super::ALIAS_PREFIX_INVALID;
361
362pub fn ssm_mode_invalid(value: &str) -> String {
365 format!(
366 "Unknown --ssm value '{}'. Use off, auto or always.",
367 value.trim()
368 )
369}
370
371pub fn provider_replaced_hint(provider: &str) -> String {
379 format!(
380 "Updated the existing [{}] config in place. To keep two side by side, add the second one from the TUI with 'a' in the provider list, which names this config first.",
381 provider
382 )
383}
384
385pub const WARN_URL_NOT_USED: &str =
386 "Warning: --url is only used by self-hosted providers (Proxmox, NetBox). Ignoring.";
387pub const WARN_PROFILE_NOT_USED: &str =
388 "Warning: --profile is only used by the AWS provider. Ignoring.";
389pub const WARN_SSM_NOT_USED: &str = "Warning: --ssm is only used by the AWS provider. Ignoring.";
390pub const WARN_PROJECT_NOT_USED: &str =
391 "Warning: --project is only used by the GCP provider. Ignoring.";
392pub const WARN_COMPARTMENT_NOT_USED: &str =
393 "Warning: --compartment is only used by the Oracle provider. Ignoring.";
394pub const WARN_NO_VERIFY_TLS_NOT_USED: &str =
395 "Warning: --no-verify-tls is only used by self-hosted providers (Proxmox, NetBox). Ignoring.";
396pub const WARN_VERIFY_TLS_NOT_USED: &str =
397 "Warning: --verify-tls is only used by self-hosted providers (Proxmox, NetBox). Ignoring.";
398pub const WARN_FILTER_NOT_USED: &str =
399 "Warning: --filter is only used by the NetBox provider. Ignoring.";
400pub const WARN_REGIONS_NOT_USED: &str = "Warning: --regions is only used by the AWS, Scaleway, GCP, Azure and Oracle providers. \
401 Ignoring.";
402
403pub fn warn_token_resolved_empty(display_name: &str) -> String {
407 format!(
408 "Warning: empty token. {} will look for credentials elsewhere when it syncs.",
409 display_name
410 )
411}
412
413pub fn warn_token_replaced_with_empty(display_name: &str) -> String {
416 format!(
417 "Warning: empty token replaces the one stored for {}. It will look for credentials elsewhere when it syncs.",
418 display_name
419 )
420}
421
422pub const SYNC_RESULT_PREFIX_LIVE: &str = " ";
427pub const SYNC_RESULT_PREFIX_DRY_RUN: &str = " Would have: ";
428
429pub const PROVIDER_URL_REQUIRES_HTTPS: &str =
434 "URL must start with https://. For self-signed certificates use --no-verify-tls.";
435
436pub use super::PROVIDER_TOKEN_REQUIRED_GCP;
440pub use super::PROVIDER_TOKEN_REQUIRED_ORACLE;
441pub use super::azure_subscription_id_invalid;
442pub use super::provider_token_required;
443
444pub const SCALEWAY_REGIONS_REQUIRED: &str = "Scaleway requires --regions with one or more zones \
448 (e.g. --regions fr-par-1,nl-ams-1).";
449
450pub const ORACLE_COMPARTMENT_REQUIRED: &str =
451 "Oracle requires --compartment (e.g. --compartment ocid1.compartment.oc1..aaa...).";
452
453pub fn vault_no_role(alias: &str) -> String {
456 format!(
457 "No Vault SSH role configured for '{}'. Set it in the host form \
458 (Vault SSH Role field) or in the provider config (vault_role).",
459 alias
460 )
461}
462
463pub fn vault_cert_signed(path: &impl std::fmt::Display) -> String {
464 format!("Certificate signed: {}", path)
465}
466
467pub fn vault_sign_failed(e: &impl std::fmt::Display) -> String {
468 format!("failed: {}", e)
469}
470
471pub fn vault_config_update_warning(e: &impl std::fmt::Display) -> String {
472 format!("Warning: Failed to update SSH config: {}", e)
473}
474
475pub const NO_HOSTS: &str = "No hosts configured. Run 'purple' to add some!";
478
479pub const NO_TOKEN: &str =
482 "No token provided. Use --token, --token-stdin, or set PURPLE_TOKEN env var.";
483
484pub mod whats_new {
487 pub const HEADER: &str = "purple release notes";
488}