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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ ci-full.txt
ci-full-verification.txt
*-verification.txt
test-integration.txt
internal/air/photos.db

# IDE
.idea/
Expand Down
2 changes: 2 additions & 0 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ nylas agent account list # List agent accounts
nylas agent account create <email> # Create agent account
nylas agent account create <email> --app-password PW # Create account with IMAP/SMTP app password
nylas agent account create <email> --policy-id <policy-id> # Create account attached to a policy
nylas agent account update [agent-id|email] --app-password PW # Add or rotate IMAP/SMTP app password
nylas agent account get <agent-id|email> # Show one agent account
nylas agent account delete <agent-id|email> # Delete/revoke agent account
nylas agent account delete <agent-id|email> --yes # Skip confirmation
Expand All @@ -471,6 +472,7 @@ nylas agent rule list --all # List all rules attached to agen
nylas agent rule read <rule-id> # Read one rule
nylas agent rule get <rule-id> # Show one rule
nylas agent rule create --name NAME --condition from.domain,is,example.com --action mark_as_spam # Create a rule from common flags
nylas agent rule create --name NAME --trigger outbound --condition recipient.domain,is,example.com --condition outbound.type,is,compose --action archive # Create an outbound rule
nylas agent rule create --data-file rule.json # Create a rule from full JSON
nylas agent rule update <rule-id> --name NAME --description TEXT # Update a rule
nylas agent rule delete <rule-id> --yes # Delete a rule
Expand Down
4 changes: 2 additions & 2 deletions docs/commands/agent-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,13 @@ To remove a policy from active use:

## Relationship to Agent Accounts

Policies are attached at agent account creation time:
Policies are primarily attached at agent account creation time:

```bash
nylas agent account create me@yourapp.nylas.email --policy-id <policy-id>
```

There is currently no separate `agent account update` command, so the main CLI-managed attachment point is account creation.
The CLI now has `nylas agent account update`, but it currently manages mutable account settings such as `--app-password`, not `settings.policy_id`. In practice, policy attachment remains a create-time workflow on the agent account surface.

## Troubleshooting

Expand Down
57 changes: 54 additions & 3 deletions docs/commands/agent-rule.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ nylas agent rule list --all
nylas agent rule get <rule-id>
nylas agent rule read <rule-id>
nylas agent rule create --name "Block Example" --condition from.domain,is,example.com --action mark_as_spam
nylas agent rule create --name "Archive outbound mail" --trigger outbound --condition recipient.domain,is,example.com --condition outbound.type,is,compose --action archive
nylas agent rule create --data-file rule.json
nylas agent rule update <rule-id> --name "Updated Rule"
nylas agent rule delete <rule-id> --yes
Expand Down Expand Up @@ -43,6 +44,7 @@ Behavior:
- resolves the default local `provider=nylas` grant
- finds the policy attached to that grant
- returns the rules attached to that policy
- skips stale policy rule references that no longer exist in `/v3/rules`

### Rules for a Specific Agent Policy

Expand Down Expand Up @@ -104,6 +106,15 @@ nylas agent rule create \
--action mark_as_starred
```

```bash
nylas agent rule create \
--name "Archive outbound mail" \
--trigger outbound \
--condition recipient.domain,is,example.com \
--condition outbound.type,is,compose \
--action archive
```

Available common flags:

- `--name`
Expand All @@ -123,6 +134,31 @@ Defaults when creating from flags:
- `enabled=true`
- `match.operator=all`

Supported triggers:

- `inbound`
- `outbound`

Supported fields:

- inbound: `from.address`, `from.domain`, `from.tld`
- outbound: `from.address`, `from.domain`, `from.tld`, `recipient.address`, `recipient.domain`, `recipient.tld`, `outbound.type`

Supported operators:

- all string fields: `is`, `is_not`, `contains`, `in_list`
- `outbound.type`: `is`, `is_not`

Supported actions:

- `block`
- `mark_as_spam`
- `assign_to_folder=<folder>`
- `mark_as_read`
- `mark_as_starred`
- `archive`
- `trash`

### `--condition`

Format:
Expand All @@ -136,14 +172,18 @@ Examples:
```bash
--condition from.domain,is,example.com
--condition from.address,is,ceo@example.com
--condition subject.contains,is,invoice
--condition recipient.domain,is,example.com
--condition outbound.type,is,reply
--condition from.domain,in_list,example.com,example.org
```

Important:

- condition values are treated as strings by default
- values like `true` and `123` stay strings
- there is no implicit JSON coercion for condition values
- `in_list` expects additional comma-separated values, for example `field,in_list,list-a,list-b`
- `outbound.type` only supports `compose` and `reply`

### `--action`

Expand All @@ -159,8 +199,8 @@ Examples:
```bash
--action mark_as_spam
--action mark_as_read
--action move_to_folder=vip
--action tag=security
--action assign_to_folder=vip
--action archive
```

Action values are also treated as strings by default.
Expand Down Expand Up @@ -194,6 +234,15 @@ nylas agent rule update <rule-id> \
--action mark_as_spam
```

```bash
nylas agent rule update <rule-id> \
--trigger outbound \
--match-operator any \
--condition recipient.domain,is,example.org \
--condition outbound.type,is,reply \
--action archive
```

Behavior:

- `--condition` replaces the rule's condition set
Expand Down Expand Up @@ -249,10 +298,12 @@ If `nylas agent rule list` returns nothing:
- make sure your default grant is `provider=nylas`
- confirm that default agent account has a policy attached
- confirm the policy actually has rules attached
- if the policy only references deleted rules, `list` now returns an empty result instead of failing

If `nylas agent rule read` or `update` says the rule is not found:

- the rule may exist in the application but outside the current agent scope
- or the policy may still reference a deleted rule ID
- try `nylas agent rule list --all` to see what is reachable from agent accounts

If `nylas agent rule delete` is rejected:
Expand Down
25 changes: 23 additions & 2 deletions docs/commands/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Agent accounts are managed email identities backed by provider `nylas`. Unlike O
```bash
nylas agent account list
nylas agent account create <email>
nylas agent account update [agent-id|email] --app-password 'ValidAgentPass123ABC!'
nylas agent account get <agent-id|email>
nylas agent account delete <agent-id|email>
nylas agent policy list
Expand All @@ -20,6 +21,7 @@ nylas agent policy delete <policy-id>
nylas agent rule list
nylas agent rule read <rule-id>
nylas agent rule create --name "Block Example" --condition from.domain,is,example.com --action mark_as_spam
nylas agent rule create --name "Archive outbound mail" --trigger outbound --condition recipient.domain,is,example.com --condition outbound.type,is,compose --action archive
nylas agent rule update <rule-id> --name "Updated Rule" --description "Block example.org"
nylas agent rule delete <rule-id>
nylas agent status
Expand Down Expand Up @@ -100,12 +102,28 @@ nylas agent account create me@yourapp.nylas.email --policy-id 12345678-1234-1234
## Show Agent Account

```bash
nylas agent account get
nylas agent account get 12345678-1234-1234-1234-123456789012
nylas agent account get me@yourapp.nylas.email
nylas agent account get me@yourapp.nylas.email --json
```

You can look up an agent account by grant ID or by email address.
You can look up an agent account by grant ID or by email address. If you omit the identifier, the CLI resolves a local `provider=nylas` grant when one can be identified safely.

## Update Agent Account

```bash
nylas agent account update --app-password 'ValidAgentPass123ABC!'
nylas agent account update 12345678-1234-1234-1234-123456789012 --app-password 'ValidAgentPass123ABC!'
nylas agent account update me@yourapp.nylas.email --app-password 'ValidAgentPass123ABC!' --json
```

Behavior:
- updates the resolved local `provider=nylas` grant when no identifier is passed
- currently supports rotating or adding `settings.app_password`
- preserves the existing account email and policy attachment

Use this when you want to add mail-client access after creation or rotate an existing IMAP/SMTP app password.

## Delete Agent Account

Expand Down Expand Up @@ -161,16 +179,19 @@ nylas agent rule read <rule-id>
nylas agent rule get <rule-id>
nylas agent rule create --name "Block Example" --condition from.domain,is,example.com --action mark_as_spam
nylas agent rule create --name "VIP sender" --condition from.address,is,ceo@example.com --action mark_as_read --action mark_as_starred
nylas agent rule create --name "Archive outbound mail" --trigger outbound --condition recipient.domain,is,example.com --condition outbound.type,is,compose --action archive
nylas agent rule create --data-file rule.json
nylas agent rule update <rule-id> --name "Updated Rule" --description "Block example.org"
nylas agent rule update <rule-id> --condition from.domain,is,example.org --action mark_as_spam
nylas agent rule update <rule-id> --trigger outbound --condition recipient.domain,is,example.org --condition outbound.type,is,reply --action archive
nylas agent rule delete <rule-id> --yes
```

Summary:
- `list` uses the policy attached to the current default `provider=nylas` grant unless `--policy-id` is passed
- `list --all` shows only rules reachable from policies attached to `provider=nylas` accounts
- `list` skips stale policy rule references and returns only rules that still exist
- `create` supports common-case flags like `--name`, repeatable `--condition`, and repeatable `--action`
- both inbound and outbound rule triggers are supported on the agent rule surface
- `get` and `read` are aliases
- `update` and `delete` refuse to operate on rules that are outside the current `provider=nylas` agent scope

Expand Down
56 changes: 52 additions & 4 deletions internal/adapters/nylas/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,63 @@ func (c *HTTPClient) CreateAgentAccount(ctx context.Context, email, appPassword,
return nil, err
}

grant, err := decodeCreatedManagedGrant(resp)
grant, err := decodeManagedGrantResponse(resp)
if err != nil {
return nil, err
}
if grant.Provider != domain.ProviderNylas {
return nil, fmt.Errorf("%w: create returned non-nylas managed grant (provider=%s)", domain.ErrInvalidGrant, grant.Provider)
}

account := convertManagedGrantToAgentAccount(*grant)
if policyID != "" && account.Settings.PolicyID == "" {
account.Settings.PolicyID = policyID
return &account, nil
}

// UpdateAgentAccount updates mutable settings on an existing managed agent account grant.
func (c *HTTPClient) UpdateAgentAccount(ctx context.Context, grantID, email, appPassword string) (*domain.AgentAccount, error) {
if err := validateRequired("grant ID", grantID); err != nil {
return nil, err
}
if err := validateRequired("email", email); err != nil {
return nil, err
}

grant, err := c.getManagedGrant(ctx, grantID)
if err != nil {
return nil, err
}
if grant.Provider != domain.ProviderNylas {
return nil, fmt.Errorf("%w: grant is not a nylas agent account (provider=%s)", domain.ErrInvalidGrant, grant.Provider)
}

queryURL := fmt.Sprintf("%s/v3/grants/%s", c.baseURL, grantID)
settings := make(map[string]any)
settings["email"] = email
if grant.Settings.PolicyID != "" {
settings["policy_id"] = grant.Settings.PolicyID
}
if appPassword != "" {
settings["app_password"] = appPassword
}

payload := map[string]any{
"settings": settings,
}

resp, err := c.doJSONRequest(ctx, "PATCH", queryURL, payload)
if err != nil {
return nil, err
}

updatedGrant, err := decodeManagedGrantResponse(resp)
if err != nil {
return nil, err
}
if updatedGrant.Provider != domain.ProviderNylas {
return nil, fmt.Errorf("%w: update returned non-nylas managed grant (provider=%s)", domain.ErrInvalidGrant, updatedGrant.Provider)
}

account := convertManagedGrantToAgentAccount(*updatedGrant)
return &account, nil
}

Expand All @@ -81,7 +129,7 @@ func (c *HTTPClient) DeleteAgentAccount(ctx context.Context, grantID string) err
return c.deleteManagedGrant(ctx, grantID, domain.ProviderNylas)
}

func decodeCreatedManagedGrant(resp *http.Response) (*managedGrantResponse, error) {
func decodeManagedGrantResponse(resp *http.Response) (*managedGrantResponse, error) {
defer func() { _ = resp.Body.Close() }()

body, err := io.ReadAll(resp.Body)
Expand Down
Loading
Loading