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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '1.26'
check-latest: true

- name: Download dependencies
run: go mod download
Expand Down Expand Up @@ -106,6 +107,7 @@ jobs:
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '1.26'
check-latest: true

- name: Run golangci-lint
uses: golangci/golangci-lint-action@9fae48acfc02a90574d7c304a1758ef9895495fa # v7
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ jobs:
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '1.26'
check-latest: true

- name: Run tests
run: go test ./... -short
Expand Down
21 changes: 10 additions & 11 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -908,12 +908,11 @@ nylas scheduler pages create # Create booking page
nylas scheduler pages update <page-id> # Update page
nylas scheduler pages delete <page-id> # Delete page

# Bookings
nylas scheduler bookings list # List bookings
nylas scheduler bookings show <booking-id> # Show booking details
nylas scheduler bookings confirm <booking-id> # Confirm booking
nylas scheduler bookings reschedule <booking-id> # Reschedule booking
nylas scheduler bookings cancel <booking-id> # Cancel booking
# Bookings (--configuration-id required: booking endpoints use a Scheduler session token, not the API key)
nylas scheduler bookings show <booking-id> --configuration-id <config-id> # Show booking details
nylas scheduler bookings confirm <booking-id> --configuration-id <config-id> --salt <salt> # Confirm booking
nylas scheduler bookings reschedule <booking-id> --configuration-id <config-id> # Reschedule booking
nylas scheduler bookings cancel <booking-id> --configuration-id <config-id> # Cancel booking

# Configurations
nylas scheduler configurations list # List configurations
Expand Down Expand Up @@ -966,11 +965,11 @@ nylas admin connectors update <connector-id> # Update connector
nylas admin connectors delete <connector-id> # Delete connector

# Credentials
nylas admin credentials list <connector-id> # List credentials
nylas admin credentials show <credential-id> # Show credential
nylas admin credentials create # Create credential
nylas admin credentials update <credential-id> # Update credential
nylas admin credentials delete <credential-id> # Delete credential
nylas admin credentials list [connector] # List credentials (provider auto-detected)
nylas admin credentials show <credential-id> --connector <provider> # Show credential
nylas admin credentials create --connector <provider> # Create credential
nylas admin credentials update <credential-id> --connector <provider> # Update credential
nylas admin credentials delete <credential-id> --connector <provider> # Delete credential

# Grants
nylas admin grants list # List all grants
Expand Down
15 changes: 11 additions & 4 deletions docs/RPC.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ Standard JSON-RPC 2.0 codes:

Conventions:
- `grant_id` is **optional** on per-grant methods — it falls back to the server's default grant.
App-level methods (admin, scheduler configs, etc.) take **no** grant.
App-level methods (admin, etc.) take **no** grant. Scheduler configs are **per-grant** (the
Nylas v3 configuration endpoints are grant-scoped).
- Required ids return `-32602` when missing.
- Create/update params **embed the corresponding `domain.*Request` struct** at the top level — i.e.
the request fields sit alongside `grant_id`/ids (see `internal/domain` for exact fields).
Expand Down Expand Up @@ -277,9 +278,15 @@ Conventions:
(all per-grant; `notetaker_id` required where applicable).

### Scheduler
- Configs: `scheduler.config.list` / `.get` / `.create` / `.update` / `.delete`
- Sessions: `scheduler.session.create` / `.get`
- Bookings: `scheduler.booking.get` / `.confirm` / `.reschedule` / `.cancel` (`{ cancelled }`)
- Configs (per-grant): `scheduler.config.list` / `.get` / `.create` / `.update` / `.delete`
- Sessions: `scheduler.session.create` (requires `configuration_id` or `slug`; `time_to_live` in
minutes, max 30, legacy alias `ttl`) / `.get`
- Bookings (all require `configuration_id` — booking endpoints authenticate with a Scheduler
session token minted from the configuration):
`scheduler.booking.get` / `.confirm` (`salt` + `status` required; `cancellation_reason`, legacy
alias `reason`, applies when declining) / `.reschedule` (returns the booking; adds `warning`
when the reschedule was applied but the record could not be read back) / `.cancel`
(`{ cancelled }`; `cancellation_reason`, legacy alias `reason`)
- Group events (per-grant): `scheduler.groupEvent.list` (requires `config_id`, `calendar_id`,
`start_time`, `end_time`) / `.create` / `.update` / `.delete` / `.import`

Expand Down
58 changes: 30 additions & 28 deletions docs/commands/admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,58 +186,60 @@ Settings:

Manage authentication credentials for connectors.

The connector provider (e.g. `google`) is auto-detected when the application
has exactly one connector. Pass it explicitly — positionally for `list`, or
with `--connector` for show/create/update/delete — when there are several.

```bash
# List credentials
# List credentials (connector auto-detected, or pass the provider)
nylas admin credentials list
nylas admin creds list # Alias
nylas admin credentials list --json
nylas admin creds list google # Alias, explicit provider
nylas admin credentials list google --json

# Show credential details
nylas admin credentials show <credential-id>
nylas admin cred show <credential-id> --json
nylas admin cred show <credential-id> --connector google --json

# Create credential
nylas admin credentials create --connector-id <connector-id> \
# Create a connector credential. --client-id/--client-secret are the PROVIDER's
# OAuth app credentials (e.g. your own Google Cloud / Azure app), NOT your Nylas
# app's — Nylas uses them to broker auth through that provider application.
nylas admin credentials create --connector google \
--name "Production Credentials" \
--credential-type oauth

# Create credential with data
nylas admin cred create --connector-id <connector-id> \
--name "Service Account" \
--credential-type service_account \
--credential-data '{"private_key":"..."}'
--type connector \
--client-id "<google-oauth-app-client-id>" \
--client-secret "<google-oauth-app-client-secret>"

# Update credential
nylas admin credentials update <credential-id> --name "Updated Name"
nylas admin credentials update <credential-id> --connector google --name "Updated Name"

# Delete credential
nylas admin credentials delete <credential-id>
nylas admin credentials delete <credential-id> --connector google
nylas admin cred delete <credential-id> --yes
```

**Example: List credentials**
```bash
$ nylas admin credentials list
$ nylas admin credentials list google

Found 2 credential(s):

NAME ID CONNECTOR TYPE
Production OAuth cred_oauth_123 conn_google_123 oauth
Service Account cred_sa_456 conn_google_123 service_account
NAME ID CREATED AT
Production Creds cred_conn_123 Dec 1, 2024 10:00 AM
Service Account cred_sa_456 Dec 3, 2024 9:15 AM
```

> The Nylas v3 credential object returns only `id`, `name`, `created_at`, and
> `updated_at`; `credential_type`/`credential_data` are create-time inputs and are
> never echoed back.

**Example: Show credential details**
```bash
$ nylas admin credentials show cred_oauth_123

Credential: Production OAuth
ID: cred_oauth_123
Connector ID: conn_google_123
Name: Production OAuth
Type: oauth
$ nylas admin credentials show cred_conn_123 --connector google

Created: Dec 1, 2024 10:00 AM
Updated: Dec 15, 2024 2:30 PM
Credential: Production Creds
ID: cred_conn_123
Created At: Dec 1, 2024 10:00 AM
Updated At: Dec 15, 2024 2:30 PM
```

### Grants
Expand Down
47 changes: 31 additions & 16 deletions docs/commands/scheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ Nylas Scheduler enables you to create customizable booking workflows for schedul

Manage scheduling configurations (meeting types):

> Configurations are grant-scoped (`/v3/grants/{grant_id}/scheduling/configurations`).
> The grant is taken from your default grant, or pass it as an optional trailing
> `[grant-id]` positional. Leading positionals like `<config-id>` are not mistaken
> for a grant.

```bash
# List all scheduler configurations
# List all scheduler configurations (uses default grant)
nylas scheduler configurations list
nylas scheduler configs list # Alias
nylas scheduler configurations list --json
nylas scheduler configurations list <grant-id> # Explicit grant

# Show configuration details
nylas scheduler configurations show <config-id>
Expand Down Expand Up @@ -119,8 +125,9 @@ When both `--file` and flags are provided, flags take precedence over file value
Create temporary booking sessions for configurations:

```bash
# Create a session for a configuration
nylas scheduler sessions create <config-id>
# Create a session for a configuration (TTL is in minutes, max 30)
nylas scheduler sessions create --config-id <config-id>
nylas scheduler sessions create --config-id <config-id> --ttl 10

# Show session details
nylas scheduler sessions show <session-id>
Expand All @@ -136,23 +143,31 @@ nylas scheduler sessions show <session-id>
Manage scheduled appointments:

```bash
# List all bookings
nylas scheduler bookings list
nylas scheduler bookings list --json
# Booking commands are authorized by a Scheduler session token that the CLI
# mints from the booking's configuration, so --configuration-id is required on
# every booking command (the API key is not accepted on booking endpoints).

# Show booking details
nylas scheduler bookings show <booking-id>
nylas scheduler bookings show <booking-id> --configuration-id <config-id>

# Confirm a booking
nylas scheduler bookings confirm <booking-id>
# Confirm a booking. --salt is required and comes from the booking reference
# (in the organizer confirmation link, the cancel/reschedule URL, or a Scheduler
# webhook). It cannot be looked up from the booking ID.
nylas scheduler bookings confirm <booking-id> --configuration-id <config-id> --salt <salt>

# Reschedule a booking
nylas scheduler bookings reschedule <booking-id> \\
--start-time "2024-03-20T10:00:00Z"
--configuration-id <config-id> \\
--start-time 1710930600 --end-time 1710934200
# If the reschedule is applied but the booking cannot be read back afterwards,
# the command still succeeds and prints a warning on stderr (in --json mode the
# output additionally carries a "warning" field); re-run `bookings show` to
# verify the booking's current server-side record.

# Cancel a booking
nylas scheduler bookings cancel <booking-id>
nylas scheduler bookings cancel <booking-id> --configuration-id <config-id>
nylas scheduler bookings cancel <booking-id> \\
--configuration-id <config-id> \\
--reason "Meeting no longer needed"
```

Expand Down Expand Up @@ -210,12 +225,12 @@ nylas scheduler pages create \\
# 3. Share the booking URL with prospects
# URL format: https://schedule.nylas.com/product-demo

# 4. View bookings
nylas scheduler bookings list
# 4. Show a booking (booking IDs arrive via Scheduler webhooks or confirmation links)
nylas scheduler bookings show <booking-id> --configuration-id <config-id>

# 5. Manage bookings
nylas scheduler bookings confirm <booking-id>
nylas scheduler bookings reschedule <booking-id> --start-time "..."
# 5. Manage bookings (--configuration-id is required on booking commands)
nylas scheduler bookings confirm <booking-id> --configuration-id <config-id> --salt <salt>
nylas scheduler bookings reschedule <booking-id> --configuration-id <config-id> --start-time <unix> --end-time <unix>
```

**Note:** Some scheduler features may not be available in all Nylas API versions or require specific subscription tiers.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/nylas/cli

go 1.26.0

toolchain go1.26.3
toolchain go1.26.5

require (
charm.land/huh/v2 v2.0.3
Expand Down
70 changes: 53 additions & 17 deletions internal/adapters/nylas/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,24 +313,54 @@ func (c *HTTPClient) ListCredentials(ctx context.Context, connectorID string) ([
return nil, err
}

queryURL := fmt.Sprintf("%s/v3/connectors/%s/credentials", c.baseURL, url.PathEscape(connectorID))
// The v3 creds list is offset-paginated (default page size 10), so follow
// the pages until a short page rather than silently truncating.
baseURL := fmt.Sprintf("%s/v3/connectors/%s/creds", c.baseURL, url.PathEscape(connectorID))
const pageSize = 200
// Safety ceiling (mirrors ListAllGrants' maxGrantPages) so a misbehaving
// endpoint that never returns a short page can't loop forever.
const maxCredentialPages = 1000

// Non-nil so an empty result marshals to `[]`, not `null`.
all := make([]domain.ConnectorCredential, 0)
offset := 0
for range maxCredentialPages {
queryURL := NewQueryBuilder().AddInt("limit", pageSize).AddInt("offset", offset).BuildURL(baseURL)

var result struct {
Data []domain.ConnectorCredential `json:"data"`
}
if err := c.doGet(ctx, queryURL, &result); err != nil {
return nil, err
var result struct {
Data []domain.ConnectorCredential `json:"data"`
Limit int `json:"limit"`
}
if err := c.doGet(ctx, queryURL, &result); err != nil {
return nil, err
}
all = append(all, result.Data...)

// The API echoes the effective page size; a page shorter than that (or
// empty) is the last one. Falling back to the requested size handles a
// response that omits `limit`.
effective := result.Limit
if effective <= 0 {
effective = pageSize
}
if len(result.Data) < effective {
return all, nil
}
offset += len(result.Data)
}
return result.Data, nil
return nil, fmt.Errorf("failed to paginate credentials: exceeded max page count (%d)", maxCredentialPages)
}

// GetCredential retrieves a specific credential.
func (c *HTTPClient) GetCredential(ctx context.Context, credentialID string) (*domain.ConnectorCredential, error) {
// GetCredential retrieves a specific credential for a connector.
func (c *HTTPClient) GetCredential(ctx context.Context, connectorID, credentialID string) (*domain.ConnectorCredential, error) {
if err := validateRequired("connector ID", connectorID); err != nil {
return nil, err
}
if err := validateRequired("credential ID", credentialID); err != nil {
return nil, err
}

queryURL := fmt.Sprintf("%s/v3/credentials/%s", c.baseURL, url.PathEscape(credentialID))
queryURL := fmt.Sprintf("%s/v3/connectors/%s/creds/%s", c.baseURL, url.PathEscape(connectorID), url.PathEscape(credentialID))

var result struct {
Data domain.ConnectorCredential `json:"data"`
Expand All @@ -347,7 +377,7 @@ func (c *HTTPClient) CreateCredential(ctx context.Context, connectorID string, r
return nil, err
}

queryURL := fmt.Sprintf("%s/v3/connectors/%s/credentials", c.baseURL, url.PathEscape(connectorID))
queryURL := fmt.Sprintf("%s/v3/connectors/%s/creds", c.baseURL, url.PathEscape(connectorID))

resp, err := c.doJSONRequest(ctx, "POST", queryURL, req)
if err != nil {
Expand All @@ -363,13 +393,16 @@ func (c *HTTPClient) CreateCredential(ctx context.Context, connectorID string, r
return &result.Data, nil
}

// UpdateCredential updates an existing credential.
func (c *HTTPClient) UpdateCredential(ctx context.Context, credentialID string, req *domain.UpdateCredentialRequest) (*domain.ConnectorCredential, error) {
// UpdateCredential updates an existing credential for a connector.
func (c *HTTPClient) UpdateCredential(ctx context.Context, connectorID, credentialID string, req *domain.UpdateCredentialRequest) (*domain.ConnectorCredential, error) {
if err := validateRequired("connector ID", connectorID); err != nil {
return nil, err
}
if err := validateRequired("credential ID", credentialID); err != nil {
return nil, err
}

queryURL := fmt.Sprintf("%s/v3/credentials/%s", c.baseURL, url.PathEscape(credentialID))
queryURL := fmt.Sprintf("%s/v3/connectors/%s/creds/%s", c.baseURL, url.PathEscape(connectorID), url.PathEscape(credentialID))

resp, err := c.doJSONRequest(ctx, "PATCH", queryURL, req)
if err != nil {
Expand All @@ -385,12 +418,15 @@ func (c *HTTPClient) UpdateCredential(ctx context.Context, credentialID string,
return &result.Data, nil
}

// DeleteCredential deletes a credential.
func (c *HTTPClient) DeleteCredential(ctx context.Context, credentialID string) error {
// DeleteCredential deletes a credential for a connector.
func (c *HTTPClient) DeleteCredential(ctx context.Context, connectorID, credentialID string) error {
if err := validateRequired("connector ID", connectorID); err != nil {
return err
}
if err := validateRequired("credential ID", credentialID); err != nil {
return err
}
queryURL := fmt.Sprintf("%s/v3/credentials/%s", c.baseURL, url.PathEscape(credentialID))
queryURL := fmt.Sprintf("%s/v3/connectors/%s/creds/%s", c.baseURL, url.PathEscape(connectorID), url.PathEscape(credentialID))
return c.doDelete(ctx, queryURL)
}

Expand Down
Loading
Loading