Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fc05790
initial commit
rts1-godaddy Aug 19, 2026
de9b8e6
feat(domain): add interactive domain registration wizard
rts1-godaddy Aug 24, 2026
232323d
feat(domain): complete interactive registration wizard
rts1-godaddy Aug 25, 2026
d6dda4c
refactored retry
rts1-godaddy Aug 25, 2026
1b44de8
fix lint
rts1-godaddy Aug 25, 2026
1f151ae
Fix prompt formatting issues
rts1-godaddy Aug 26, 2026
67ff1d9
Fix cargo file pointing to local cli-engine
rts1-godaddy Aug 26, 2026
eff9c66
fix the formatting issue
rts1-godaddy Aug 26, 2026
ac25fdc
Fix the lint errors
rts1-godaddy Aug 26, 2026
519bcb5
Address copilot comments; skip prompts for agree and confirm in non-i…
rts1-godaddy Aug 26, 2026
cbf0cc0
Fixed Go Back opions\n fixed ordering of wizards
rts1-godaddy Aug 26, 2026
d01ee6b
lint fix
rts1-godaddy Aug 27, 2026
2fe2db4
fix(domain): address wizard UX review on suggest bridge and review UI
rts1-godaddy Aug 27, 2026
f9f529d
fix(domain): mask contact email preview and improve wizard back navig…
rts1-godaddy Aug 28, 2026
3d094cf
fix(domain): labesl in the order summary
rts1-godaddy Aug 28, 2026
334b329
fix(domain):update cli-engine, lint error, pii warning
rts1-godaddy Aug 28, 2026
f1561e7
fix(domain): Misc issues:
rts1-godaddy Aug 28, 2026
609394d
* Add interactive retry for all domain sub commands
rts1-godaddy Sep 1, 2026
e27fe6b
remove the patch.io
rts1-godaddy Sep 1, 2026
2e8de8e
merge: resolve main into feat/domain-register-wizard
rts1-godaddy Sep 1, 2026
aca7432
test(domain): update auth test for flattened operation command
rts1-godaddy Sep 1, 2026
3c4e348
Fix format issue
rts1-godaddy Sep 1, 2026
62f0815
refactor(domain): split common.rs to satisfy 1000-line CI limit
rts1-godaddy Sep 1, 2026
098ab58
style(domain): apply rustfmt to common module split
rts1-godaddy Sep 1, 2026
23b24ce
fix(domain): remove duplicate types import in pricing tests
rts1-godaddy Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ __pycache__
# Claude Code local runtime state (per-machine, not for commit)
**/.claude/scheduled_tasks.lock
**/.claude/scheduled_tasks.json
.cursor/
64 changes: 64 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ url = "2"
uuid = { version = "1", features = ["v4"] }
zip = { version = "8.6.0", default-features = false, features = ["deflate-flate2"] }
iso_currency = "0.5.3"
dialoguer = "0.12.0"
console = "0.16.4"
indicatif = "0.18.6"

[dev-dependencies]
httpmock = "0.8"
Expand Down
8 changes: 4 additions & 4 deletions rust/src/config/settings_form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,10 @@ fn validate_field(field: &SettingsFormV1Field, errors: &mut Vec<String>, path: &
}
match field {
SettingsFormV1Field::Select { options, .. }
| SettingsFormV1Field::MultiSelect { options, .. } => {
if options.is_empty() {
errors.push(format!("{path}.options must contain at least one option"));
}
| SettingsFormV1Field::MultiSelect { options, .. }
if options.is_empty() =>
{
errors.push(format!("{path}.options must contain at least one option"));
}
SettingsFormV1Field::ListGroup { item, .. } => {
if !is_field_name(&item.id_field) {
Expand Down
121 changes: 83 additions & 38 deletions rust/src/domain/agreements.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
//! `gddy domain agreements` — the legal agreements a TLD requires (v1).

// Interactive recovery prompts write user-facing feedback to stderr.
#![allow(clippy::print_stderr)]

use cli_engine::{
CommandResult, CommandSpec, NextActionParam, RuntimeCommandSpec, TableColumn, Tier,
CliCoreError, CommandResult, CommandSpec, NextActionParam, RuntimeCommandSpec, TableColumn,
Tier,
};
use serde_json::json;

use domains_client::types;

use super::common::{api_error, comma_joined, make_client};
use super::common::{
comma_joined, format_api_error, make_client, prompt_validated_tld, recoverable_tld_api_error,
resolve_tlds,
};
use crate::next_action::next_action;
use crate::scopes::DOMAINS_READ;

const TLD_PROMPT: &str = "TLD whose agreements to retrieve, e.g. com (without a leading dot)";

#[derive(Debug, Clone, clap::Args)]
struct AgreementsArgs {
/// TLD whose agreements to retrieve, e.g. com (repeatable).
Expand Down Expand Up @@ -44,46 +53,82 @@ pub(super) fn command() -> RuntimeCommandSpec {
.with_json_schema::<types::V1LegalAgreement>()
.with_scopes(&[DOMAINS_READ]),
|ctx, args: AgreementsArgs| async move {
let tlds = args.tld;
let privacy = args.privacy;
let debug = !ctx.middleware.debug.is_empty();
let client = make_client(&ctx).await?;
let resp = match client
.agreements()
.tlds(comma_joined(tlds))
.v1_privacy(privacy)
.send()
.await
{
Ok(r) => r,
Err(e) => return Err(api_error("retrieving legal agreements", debug, e).await),
};
let agreements: Vec<serde_json::Value> = resp
.into_inner()
.into_iter()
.map(|a| {
json!({
"agreementKey": a.agreement_key,
"title": a.title,
"url": a.url,
"content": a.content,
let mut tlds = resolve_tlds(&ctx, args.tld, TLD_PROMPT)?;

loop {
let resp = match client
.agreements()
.tlds(comma_joined(tlds.clone()))
.v1_privacy(privacy)
.send()
.await
{
Ok(r) => r,
Err(domains_client::Error::UnexpectedResponse(resp))
if ctx.is_interactive() =>
{
let status = resp.status().as_u16();
let status_display = resp.status().to_string();
let request_id = resp
.headers()
.get("x-request-id")
.and_then(|v| v.to_str().ok())
.map(str::to_owned);
let body = resp.text().await.unwrap_or_default();
if let Some(hint) = recoverable_tld_api_error(status, &body) {
eprintln!(" {hint}");
tlds = vec![prompt_validated_tld(TLD_PROMPT)?];
continue;
}
return Err(CliCoreError::message(format_api_error(
"retrieving legal agreements",
status,
&status_display,
&body,
request_id.as_deref(),
debug,
)));
}
Err(e) => {
return Err(super::common::api_error(
"retrieving legal agreements",
debug,
e,
)
.await);
}
};

let agreements: Vec<serde_json::Value> = resp
.into_inner()
.into_iter()
.map(|a| {
json!({
"agreementKey": a.agreement_key,
"title": a.title,
"url": a.url,
"content": a.content,
})
})
})
.collect();
Ok(
CommandResult::new(json!(agreements)).with_next_actions(vec![
next_action(
"domain quote <domain>",
"Price a registration and see the agreements for a specific domain",
)
.with_param("domain", NextActionParam::required()),
next_action(
"domain purchase --quote-token <quote-token> --agree --confirm",
"Register once you have a quote",
)
.with_param("quote-token", NextActionParam::required()),
]),
)
.collect();
return Ok(
CommandResult::new(json!(agreements)).with_next_actions(vec![
next_action(
"domain quote <domain>",
"Price a registration and see the agreements for a specific domain",
)
.with_param("domain", NextActionParam::required()),
next_action(
"domain purchase --quote-token <quote-token> --agree --confirm",
"Register once you have a quote",
)
.with_param("quote-token", NextActionParam::required()),
]),
);
}
},
)
}
Expand Down
34 changes: 30 additions & 4 deletions rust/src/domain/available.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use serde_json::json;

use domains_client::types;

use super::common::{api_error, format_money, make_client, period_label, validate_domain_name};
use super::common::{
api_error, format_money, make_client, period_label, period_price_map, periods_from_prices,
resolve_domain_name, term_for_period,
};
use crate::next_action::next_action;
use crate::output_schema::output_schema;
use crate::scopes::DOMAINS_READ;
Expand Down Expand Up @@ -115,7 +118,11 @@ pub(super) fn command() -> RuntimeCommandSpec {
.with_view(view_columns())
.with_scopes(&[DOMAINS_READ]),
|ctx, args: AvailableArgs| async move {
let domain = validate_domain_name(&args.domain)?;
let domain = resolve_domain_name(
&ctx,
&args.domain,
"Domain name to check (e.g. example.com)",
)?;
// --check-type fast|full → v3 optimizeFor SPEED|ACCURACY.
let optimize_for = match args.check_type.as_deref() {
Some("fast") => Some(types::OptimizationTarget::Speed),
Expand Down Expand Up @@ -159,14 +166,33 @@ pub(super) fn command() -> RuntimeCommandSpec {

let cmd = CommandResult::new(result);
if body.available.unwrap_or(false) {
// If interactive, offer to continue directly into registration.
let price_1yr = term_for_period(&prices, 1)
.and_then(|t| t.price.as_ref())
.and_then(format_money);
let currency_str = shared_currency(&prices);
match super::register::bridge::offer_registration_from_available(
&ctx,
&resolved_domain,
price_1yr,
currency_str,
periods_from_prices(&prices),
period_price_map(&prices),
)
.await?
{
super::register::BridgeHandoff::Replace(wizard_result) => {
return Ok(wizard_result);
}
super::register::BridgeHandoff::ShowHostOutput => {}
}

Ok(cmd.with_next_actions(vec![
next_action("domain quote <domain>", "Price a registration")
.with_param("domain", NextActionParam::value(resolved_domain)),
]))
} else {
Ok(cmd.with_next_actions(vec![
// `domain suggest` accepts a seed domain, so the domain just
// checked as taken is a valid query to copy/paste directly.
next_action("domain suggest <query>", "Find alternatives")
.with_param("query", NextActionParam::value(resolved_domain)),
]))
Expand Down
Loading
Loading