Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 16 additions & 1 deletion docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ nylas auth migrate # Migrate from v2 to v3

## Dashboard

Manage your Nylas Dashboard account, applications, and API keys directly from the CLI.
Manage your Nylas Dashboard account, applications, domains, and API keys directly from the CLI.

### Account

Expand Down Expand Up @@ -161,6 +161,21 @@ nylas dashboard apps apikeys create --expires 30 # Expire in 30 days

After creating a key, you choose: activate in CLI (recommended), copy to clipboard, or save to file.

### Domains

```bash
nylas dashboard domains list # List inbox and Agent Account domains
nylas dashboard domains check example.com # Check org-scoped availability
nylas dashboard domains create example.com --region us # Register a domain
nylas dashboard domains show example.com # Show a registered domain
nylas dashboard domains dns example.com # Show DNS records to configure
nylas dashboard domains verify example.com --type ownership # Verify one record
nylas dashboard domains verify example.com --all # Verify all supported records
nylas dashboard domains delete example.com --yes # Delete a domain
```

Domain creation registers the domain in your active Dashboard organization and requires `--region us|eu`. Existing-domain commands infer the region from `domains list` when `--region` is omitted; pass `--region` to override or disambiguate. Configure the DNS records from `domains dns`, then run `domains verify`.

---

## Demo Mode (No Account Required)
Expand Down
153 changes: 153 additions & 0 deletions internal/adapters/dashboard/account_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"

"github.com/nylas/cli/internal/domain"
"github.com/nylas/cli/internal/ports"
Expand Down Expand Up @@ -219,6 +220,158 @@ func (c *AccountClient) SwitchOrg(ctx context.Context, orgPublicID, userToken, o
return &result, nil
}

// ListDomains lists inbox/agent-account domains for the active dashboard organization.
func (c *AccountClient) ListDomains(ctx context.Context, limit int, pageToken, userToken, orgToken string) (domain.DashboardInboxDomainPage, error) {
q := url.Values{}
if limit > 0 {
q.Set("limit", fmt.Sprintf("%d", limit))
}
if pageToken != "" {
q.Set("pageToken", pageToken)
}
path := "/orgs/inbox/domains"
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}

headers := bearerHeaders(userToken, orgToken)
raw, err := c.doGetRawResponse(ctx, path, headers, userToken)
if err != nil {
return domain.DashboardInboxDomainPage{}, fmt.Errorf("failed to list domains: %w", err)
}

result, err := decodeDomainPage(raw)
if err != nil {
return domain.DashboardInboxDomainPage{}, fmt.Errorf("failed to list domains: %w", err)
}
return result, nil
}

func decodeDomainPage(raw rawResponse) (domain.DashboardInboxDomainPage, error) {
var domains []domain.DashboardInboxDomain
if err := json.Unmarshal(raw.Data, &domains); err == nil {
return domain.DashboardInboxDomainPage{
Domains: domains,
NextCursor: raw.NextCursor,
}, nil
}

var payload struct {
Domains *[]domain.DashboardInboxDomain `json:"domains"`
NextCursor string `json:"nextCursor"`
NextCursorSnake string `json:"next_cursor"`
PageToken string `json:"pageToken"`
}
if err := json.Unmarshal(raw.Data, &payload); err != nil {
return domain.DashboardInboxDomainPage{}, fmt.Errorf("failed to decode response: %w", err)
}
if payload.Domains == nil {
return domain.DashboardInboxDomainPage{}, fmt.Errorf("failed to decode response: missing domains")
}

nextCursor := raw.NextCursor
for _, cursor := range []string{payload.NextCursor, payload.NextCursorSnake, payload.PageToken} {
if nextCursor == "" && cursor != "" {
nextCursor = cursor
}
}

return domain.DashboardInboxDomainPage{
Domains: *payload.Domains,
NextCursor: nextCursor,
}, nil
}

// GetDomain retrieves an inbox domain by ID or address.
func (c *AccountClient) GetDomain(ctx context.Context, domainIDOrAddress, region, userToken, orgToken string) (*domain.DashboardInboxDomain, error) {
path := "/orgs/inbox/domains/" + url.PathEscape(domainIDOrAddress)
if region != "" {
path += "?region=" + url.QueryEscape(region)
}

headers := bearerHeaders(userToken, orgToken)
var result domain.DashboardInboxDomain
if err := c.doGet(ctx, path, headers, userToken, &result); err != nil {
return nil, fmt.Errorf("failed to get domain: %w", err)
}
return &result, nil
}

// CheckDomainAvailability checks org-scoped availability for a domain address.
func (c *AccountClient) CheckDomainAvailability(ctx context.Context, domainAddress, userToken, orgToken string) (*domain.DashboardInboxDomainAvailability, error) {
path := "/orgs/inbox/domains/availability?domainAddress=" + url.QueryEscape(domainAddress)
headers := bearerHeaders(userToken, orgToken)

var result domain.DashboardInboxDomainAvailability
if err := c.doGet(ctx, path, headers, userToken, &result); err != nil {
return nil, fmt.Errorf("failed to check domain availability: %w", err)
}
return &result, nil
}

// CreateDomain creates/registers an inbox domain.
func (c *AccountClient) CreateDomain(ctx context.Context, input domain.DashboardCreateInboxDomainInput, userToken, orgToken string) (*domain.DashboardInboxDomain, error) {
headers := bearerHeaders(userToken, orgToken)
var result domain.DashboardInboxDomain
if err := c.doPost(ctx, "/orgs/inbox/domains", input, headers, userToken, &result); err != nil {
return nil, fmt.Errorf("failed to create domain: %w", err)
}
return &result, nil
}

// UpdateDomain updates an inbox domain.
func (c *AccountClient) UpdateDomain(ctx context.Context, domainID, region string, input domain.DashboardUpdateInboxDomainInput, userToken, orgToken string) (*domain.DashboardInboxDomain, error) {
path := "/orgs/inbox/domains/" + url.PathEscape(domainID) + "?region=" + url.QueryEscape(region)
headers := bearerHeaders(userToken, orgToken)

var result domain.DashboardInboxDomain
if err := c.doPatch(ctx, path, input, headers, userToken, &result); err != nil {
return nil, fmt.Errorf("failed to update domain: %w", err)
}
return &result, nil
}

// DeleteDomain deletes an inbox domain.
func (c *AccountClient) DeleteDomain(ctx context.Context, domainID, region, userToken, orgToken string) (bool, error) {
path := "/orgs/inbox/domains/" + url.PathEscape(domainID) + "?region=" + url.QueryEscape(region)
headers := bearerHeaders(userToken, orgToken)

var result struct {
Success bool `json:"success"`
}
if err := c.doDelete(ctx, path, headers, userToken, &result); err != nil {
return false, fmt.Errorf("failed to delete domain: %w", err)
}
return result.Success, nil
}

// GetDomainInfo returns DNS-record info for a verification type.
func (c *AccountClient) GetDomainInfo(ctx context.Context, domainID, region, verificationType, userToken, orgToken string) (*domain.DashboardDomainVerificationResult, error) {
q := url.Values{}
q.Set("region", region)
q.Set("type", verificationType)
path := "/orgs/inbox/domains/" + url.PathEscape(domainID) + "/info?" + q.Encode()
headers := bearerHeaders(userToken, orgToken)

var result domain.DashboardDomainVerificationResult
if err := c.doGet(ctx, path, headers, userToken, &result); err != nil {
return nil, fmt.Errorf("failed to get domain DNS info: %w", err)
}
return &result, nil
}

// VerifyDomain triggers verification for a DNS/authentication record type.
func (c *AccountClient) VerifyDomain(ctx context.Context, domainID, region string, input domain.DashboardVerifyInboxDomainInput, userToken, orgToken string) (*domain.DashboardDomainVerificationResult, error) {
path := "/orgs/inbox/domains/" + url.PathEscape(domainID) + "/verify?region=" + url.QueryEscape(region)
headers := bearerHeaders(userToken, orgToken)

var result domain.DashboardDomainVerificationResult
if err := c.doPost(ctx, path, input, headers, userToken, &result); err != nil {
return nil, fmt.Errorf("failed to verify domain: %w", err)
}
return &result, nil
}

// bearerHeaders creates the Authorization and X-Nylas-Org headers.
func bearerHeaders(userToken, orgToken string) map[string]string {
h := map[string]string{
Expand Down
Loading
Loading