diff --git a/CHANGELOG.md b/CHANGELOG.md index 71724741..6c6f64c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to the Inkbox SDK, CLI, and skills live here. Versions move in lockstep across `@inkbox/sdk` (TypeScript), `inkbox` (Python), `@inkbox/cli`, and `inkbox` (Rust, crates.io). +## 0.5.5 — Action-only contact-rule updates + +### Changed + +- Version bumped to 0.5.5 across `@inkbox/sdk` (TypeScript), `inkbox` (Python), `@inkbox/cli`, and `inkbox` (Rust). The CLI depends on `@inkbox/sdk` `^0.5.5`. +- Contact-rule updates now require an allow/block `action`. + +### Removed + +- **Source-breaking:** identity updates and contact-rule updates no longer accept lifecycle status. The CLI likewise removes identity/contact-rule `--status` flags. Remove those arguments and flags before upgrading. + +### Compatibility + +- Response models continue to parse existing `paused` rows for backward compatibility. + ## 0.5.4 — Dedicated outbound iMessage groups ### Added diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 8c294eaa..986dc829 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.5.5 — Action-only contact-rule updates + +### Changed + +- Contact-rule update commands now require `--action`. +- CLI version moved in lockstep with `@inkbox/sdk` 0.5.5 and depends on `^0.5.5`. + +### Removed + +- Identity and contact-rule update commands no longer accept lifecycle `--status`. + ## 0.5.4 — Dedicated outbound iMessage groups ### Added diff --git a/cli/README.md b/cli/README.md index 9c4ef909..74fe4519 100644 --- a/cli/README.md +++ b/cli/README.md @@ -90,7 +90,6 @@ inkbox identity update # Update an identity --display-name # New display name ("" to clear) --description # New description ("" to clear) --clear-description # Explicit null (mutually exclusive with --description) - --status # active or paused --imessage-enabled # Toggle iMessage reachability (true/false) --imessage-filter-mode # whitelist or blacklist (admin API key required) inkbox identity refresh # Re-fetch identity from API @@ -332,7 +331,7 @@ inkbox imessage contact-rule list -i # Allow/block rules for the ide inkbox imessage contact-rule create -i # Add a rule --action # 'allow' or 'block' --match-target # Phone number to match (E.164) -inkbox imessage contact-rule update -i # Change action/status (admin key) +inkbox imessage contact-rule update -i --action allow|block inkbox imessage contact-rule delete -i # Delete a rule (admin key) inkbox imessage contact-rule list-all # Org-wide rule list (admin key) --agent-identity-id # Narrow to one identity diff --git a/cli/package-lock.json b/cli/package-lock.json index 39e5e87c..0c953a95 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,15 +1,15 @@ { "name": "@inkbox/cli", - "version": "0.5.4", + "version": "0.5.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@inkbox/cli", - "version": "0.5.4", + "version": "0.5.5", "license": "MIT", "dependencies": { - "@inkbox/sdk": "^0.5.4", + "@inkbox/sdk": "^0.5.5", "commander": "^13.0.0", "undici": "^7.28.0" }, @@ -27,7 +27,7 @@ }, "../sdk/typescript": { "name": "@inkbox/sdk", - "version": "0.5.4", + "version": "0.5.5", "license": "MIT", "dependencies": { "@peculiar/x509": "^2.0.0", diff --git a/cli/package.json b/cli/package.json index dcfabd89..9dd75bd4 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@inkbox/cli", - "version": "0.5.4", + "version": "0.5.5", "description": "CLI for the Inkbox API", "license": "MIT", "type": "module", @@ -18,7 +18,7 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "@inkbox/sdk": "^0.5.4", + "@inkbox/sdk": "^0.5.5", "commander": "^13.0.0", "undici": "^7.28.0" }, diff --git a/cli/src/client.ts b/cli/src/client.ts index f43477a3..71c99468 100644 --- a/cli/src/client.ts +++ b/cli/src/client.ts @@ -5,7 +5,7 @@ import { Inkbox } from "@inkbox/sdk"; import type { Command } from "commander"; // Keep in sync with package.json "version". -export const CLI_VERSION = "0.5.4"; +export const CLI_VERSION = "0.5.5"; export interface GlobalOpts { apiKey?: string; diff --git a/cli/src/commands/identity.ts b/cli/src/commands/identity.ts index d072323f..1c6711ba 100644 --- a/cli/src/commands/identity.ts +++ b/cli/src/commands/identity.ts @@ -8,7 +8,6 @@ import type { MailRuleMatchType, PhoneRuleAction, PhoneRuleMatchType, - ContactRuleStatus, } from "@inkbox/sdk"; import { parseTotpUri } from "@inkbox/sdk"; @@ -193,7 +192,7 @@ function registerIdentityMailRuleCommands(parent: Command): void { rules .command("create ") - .description("Create a mail contact rule (always starts active; use `update` to pause)") + .description("Create a mail allow or block contact rule") .requiredOption("--action ", "allow or block") .requiredOption("--match-type ", "exact_email or domain") .requiredOption("--match-target ", "Address or domain to match") @@ -216,21 +215,19 @@ function registerIdentityMailRuleCommands(parent: Command): void { rules .command("update ") - .description("Update action and/or status on a mail rule (admin-only)") - .option("--action ", "allow or block") - .option("--status ", "active or paused") + .description("Update the action on a mail rule (admin-only)") + .requiredOption("--action ", "allow or block") .action( withErrorHandler(async function ( this: Command, handle: string, ruleId: string, - cmdOpts: { action?: string; status?: string }, + cmdOpts: { action: string }, ) { const opts = getGlobalOpts(this); const inkbox = createClient(opts); const rule = await inkbox.mailIdentityContactRules.update(handle, ruleId, { - action: cmdOpts.action as MailRuleAction | undefined, - status: cmdOpts.status as ContactRuleStatus | undefined, + action: cmdOpts.action as MailRuleAction, }); output(rule as unknown as Record, { json: !!opts.json }); }), @@ -348,21 +345,19 @@ function registerIdentityPhoneRuleCommands(parent: Command): void { rules .command("update ") - .description("Update action and/or status on a phone rule (admin-only)") - .option("--action ", "allow or block") - .option("--status ", "active or paused") + .description("Update the action on a phone rule (admin-only)") + .requiredOption("--action ", "allow or block") .action( withErrorHandler(async function ( this: Command, handle: string, ruleId: string, - cmdOpts: { action?: string; status?: string }, + cmdOpts: { action: string }, ) { const opts = getGlobalOpts(this); const inkbox = createClient(opts); const rule = await inkbox.phoneIdentityContactRules.update(handle, ruleId, { - action: cmdOpts.action as PhoneRuleAction | undefined, - status: cmdOpts.status as ContactRuleStatus | undefined, + action: cmdOpts.action as PhoneRuleAction, }); output(rule as unknown as Record, { json: !!opts.json }); }), @@ -609,7 +604,6 @@ export function registerIdentityCommands(program: Command): void { .option("--imessage-filter-mode ", "iMessage contact-rule mode: whitelist or blacklist (admin-only)") .option("--mail-filter-mode ", "Mail contact-rule mode: whitelist or blacklist (admin-only)") .option("--phone-filter-mode ", "Phone contact-rule mode: whitelist or blacklist (admin-only; identity must have a phone number)") - .option("--status ", "active or paused") .action( withErrorHandler(async function ( this: Command, @@ -623,7 +617,6 @@ export function registerIdentityCommands(program: Command): void { imessageFilterMode?: string; mailFilterMode?: string; phoneFilterMode?: string; - status?: string; }, ) { if (cmdOpts.description !== undefined && cmdOpts.clearDescription) { @@ -641,9 +634,6 @@ export function registerIdentityCommands(program: Command): void { throw new Error(`${flag} must be 'whitelist' or 'blacklist'`); } } - if (cmdOpts.status !== undefined && cmdOpts.status !== "active" && cmdOpts.status !== "paused") { - throw new Error("--status must be 'active' or 'paused'"); - } const opts = getGlobalOpts(this); const inkbox = createClient(opts); const id = await inkbox.getIdentity(handle); @@ -655,7 +645,6 @@ export function registerIdentityCommands(program: Command): void { imessageFilterMode?: "whitelist" | "blacklist"; mailFilterMode?: "whitelist" | "blacklist"; phoneFilterMode?: "whitelist" | "blacklist"; - status?: "active" | "paused"; } = {}; if (cmdOpts.newHandle !== undefined) updateOpts.newHandle = cmdOpts.newHandle; if (cmdOpts.displayName !== undefined) { @@ -678,9 +667,6 @@ export function registerIdentityCommands(program: Command): void { if (cmdOpts.phoneFilterMode !== undefined) { updateOpts.phoneFilterMode = cmdOpts.phoneFilterMode as "whitelist" | "blacklist"; } - if (cmdOpts.status !== undefined) { - updateOpts.status = cmdOpts.status as "active" | "paused"; - } await id.update(updateOpts); console.log(`Updated identity '${handle}'.`); }), diff --git a/cli/src/commands/imessage.ts b/cli/src/commands/imessage.ts index 5334d359..e9cfadea 100644 --- a/cli/src/commands/imessage.ts +++ b/cli/src/commands/imessage.ts @@ -168,31 +168,24 @@ function registerContactRuleCommands(parent: Command): void { rule .command("update ") - .description("Update an iMessage contact rule's action or status (admin-only)") + .description("Update an iMessage contact rule's action (admin-only)") .requiredOption("-i, --identity ", "Agent identity handle") - .option("--action ", "'allow' or 'block'") - .option("--status ", "'active' or 'paused'") + .requiredOption("--action ", "'allow' or 'block'") .action( withErrorHandler(async function ( this: Command, ruleId: string, - cmdOpts: { identity: string; action?: string; status?: string }, + cmdOpts: { identity: string; action: string }, ) { if (cmdOpts.action !== undefined && cmdOpts.action !== "allow" && cmdOpts.action !== "block") { throw new Error("--action must be 'allow' or 'block'"); } - if (cmdOpts.status !== undefined && cmdOpts.status !== "active" && cmdOpts.status !== "paused") { - throw new Error("--status must be 'active' or 'paused'"); - } const opts = getGlobalOpts(this); const inkbox = createClient(opts); const row = await inkbox.imessageContactRules.update( cmdOpts.identity, ruleId, - { - action: cmdOpts.action as never, - status: cmdOpts.status as never, - }, + { action: cmdOpts.action as never }, ); output(row, { json: !!opts.json }); }), diff --git a/cli/src/commands/mailbox.ts b/cli/src/commands/mailbox.ts index a367c0d9..2922a294 100644 --- a/cli/src/commands/mailbox.ts +++ b/cli/src/commands/mailbox.ts @@ -3,7 +3,6 @@ import { FilterMode, MailRuleAction, MailRuleMatchType, - ContactRuleStatus, } from "@inkbox/sdk"; import type { Mailbox } from "@inkbox/sdk"; import { createClient, getGlobalOpts, resolveBaseUrl } from "../client.js"; @@ -222,7 +221,7 @@ function registerMailboxRulesCommands(parent: Command): void { rules .command("create") - .description("Create a rule (always starts active; use `update` to pause)") + .description("Create an allow or block rule") .requiredOption("--mailbox ", "Mailbox email address") .requiredOption("--action ", "allow or block") .requiredOption("--match-type ", "exact_email or domain") @@ -250,21 +249,19 @@ function registerMailboxRulesCommands(parent: Command): void { rules .command("update ") - .description("Update action and/or status on a rule (admin-only)") + .description("Update the action on a rule (admin-only)") .requiredOption("--mailbox ", "Mailbox email address") - .option("--action ", "allow or block") - .option("--status ", "active or paused") + .requiredOption("--action ", "allow or block") .action( withErrorHandler(async function ( this: Command, ruleId: string, - cmdOpts: { mailbox: string; action?: string; status?: string }, + cmdOpts: { mailbox: string; action: string }, ) { const opts = getGlobalOpts(this); const inkbox = createClient(opts); const rule = await inkbox.mailContactRules.update(cmdOpts.mailbox, ruleId, { - action: cmdOpts.action as MailRuleAction | undefined, - status: cmdOpts.status as ContactRuleStatus | undefined, + action: cmdOpts.action as MailRuleAction, }); output(rule as unknown as Record, { json: !!opts.json }); }), diff --git a/cli/src/commands/number.ts b/cli/src/commands/number.ts index c3c2ae9f..0da4febc 100644 --- a/cli/src/commands/number.ts +++ b/cli/src/commands/number.ts @@ -1,6 +1,5 @@ import { Command } from "commander"; import { - ContactRuleStatus, FilterMode, PhoneRuleAction, PhoneRuleMatchType, @@ -111,7 +110,7 @@ function registerNumberRulesCommands(parent: Command): void { rules .command("create") - .description("Create a rule (always starts active; use `update` to pause)") + .description("Create an allow or block rule") .requiredOption("--number ", "Phone number id") .requiredOption("--action ", "allow or block") .requiredOption("--match-target ", "Phone number to match (E.164)") @@ -139,21 +138,19 @@ function registerNumberRulesCommands(parent: Command): void { rules .command("update ") - .description("Update action and/or status on a rule (admin-only)") + .description("Update the action on a rule (admin-only)") .requiredOption("--number ", "Phone number id") - .option("--action ", "allow or block") - .option("--status ", "active or paused") + .requiredOption("--action ", "allow or block") .action( withErrorHandler(async function ( this: Command, ruleId: string, - cmdOpts: { number: string; action?: string; status?: string }, + cmdOpts: { number: string; action: string }, ) { const opts = getGlobalOpts(this); const inkbox = createClient(opts); const rule = await inkbox.phoneContactRules.update(cmdOpts.number, ruleId, { - action: cmdOpts.action as PhoneRuleAction | undefined, - status: cmdOpts.status as ContactRuleStatus | undefined, + action: cmdOpts.action as PhoneRuleAction, }); output(rule as unknown as Record, { json: !!opts.json }); }), diff --git a/sdk/python/CHANGELOG.md b/sdk/python/CHANGELOG.md index 81bc011e..0c957a83 100644 --- a/sdk/python/CHANGELOG.md +++ b/sdk/python/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.5.5 — Action-only contact-rule updates + +### Changed + +- Contact-rule update methods now require `action`. + +### Removed + +- **Source-breaking:** identity update methods and contact-rule update methods no longer accept lifecycle `status`. Remove those keyword arguments before upgrading. + +### Compatibility + +- Response models continue to parse existing `paused` rows. + ## 0.5.4 — Dedicated outbound iMessage groups ### Added diff --git a/sdk/python/README.md b/sdk/python/README.md index 0848a066..1919fd01 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -146,9 +146,8 @@ identity.refresh() # re-fetch channels from API # List all identities for your org all_identities = inkbox.list_identities() -# Update handle, display name, description, status. For description, +# Update handle, display name, and description. For description, # pass None to clear and omit the kwarg to leave untouched. -identity.update(status="paused") identity.update(new_handle="sales-bot-v2") identity.update(display_name="New Name", description="New blurb") identity.update(description=None) # clear diff --git a/sdk/python/inkbox/agent_identity.py b/sdk/python/inkbox/agent_identity.py index f49cfefe..dd60d77a 100644 --- a/sdk/python/inkbox/agent_identity.py +++ b/sdk/python/inkbox/agent_identity.py @@ -40,7 +40,6 @@ IMessageNumberType, ) from inkbox.mail.types import ( - ContactRuleStatus, FilterMode, ForwardMode, MailIdentityContactRule, @@ -1296,17 +1295,11 @@ def update_mail_contact_rule( self, rule_id: UUID | str, *, - action: MailRuleAction | str = _UNSET, # type: ignore[assignment] - status: ContactRuleStatus | str = _UNSET, # type: ignore[assignment] + action: MailRuleAction | str, ) -> MailIdentityContactRule: - """Update a mail rule's ``action`` or ``status`` (admin-only).""" - kwargs: dict[str, Any] = {} - if action is not _UNSET: - kwargs["action"] = action - if status is not _UNSET: - kwargs["status"] = status + """Update a mail rule's ``action`` (admin-only).""" return self._inkbox._mail_identity_contact_rules.update( - self.agent_handle, rule_id, **kwargs, + self.agent_handle, rule_id, action=action, ) def delete_mail_contact_rule(self, rule_id: UUID | str) -> None: @@ -1364,18 +1357,12 @@ def update_phone_contact_rule( self, rule_id: UUID | str, *, - action: PhoneRuleAction | str = _UNSET, # type: ignore[assignment] - status: ContactRuleStatus | str = _UNSET, # type: ignore[assignment] + action: PhoneRuleAction | str, ) -> PhoneIdentityContactRule: - """Update a phone rule's ``action`` or ``status`` (admin-only).""" + """Update a phone rule's ``action`` (admin-only).""" self._require_phone() - kwargs: dict[str, Any] = {} - if action is not _UNSET: - kwargs["action"] = action - if status is not _UNSET: - kwargs["status"] = status return self._inkbox._phone_identity_contact_rules.update( - self.agent_handle, rule_id, **kwargs, + self.agent_handle, rule_id, action=action, ) def delete_phone_contact_rule(self, rule_id: UUID | str) -> None: @@ -1412,10 +1399,9 @@ def update( imessage_filter_mode: FilterMode | str | None = None, mail_filter_mode: FilterMode | str | None = None, phone_filter_mode: FilterMode | str | None = None, - status: str | None = None, ) -> None: """Update this identity's handle, display name, description, - iMessage reachability, contact-rule filter modes, and/or status. + iMessage reachability, and contact-rule filter modes. Only provided fields are applied; omitted fields are left unchanged. For ``display_name`` and ``description``, explicit @@ -1444,8 +1430,6 @@ def update( phone_filter_mode: ``"whitelist"`` or ``"blacklist"`` for this identity's phone contact rules (admin-only). Rejected with a 422 when the identity has no phone number. - status: ``"active"`` or ``"paused"``. Call :meth:`delete` - to remove the identity; ``"deleted"`` is rejected here. """ update_kwargs: dict[str, Any] = {} if new_handle is not None: @@ -1480,8 +1464,6 @@ def update( if isinstance(phone_filter_mode, FilterMode) else phone_filter_mode ) - if status is not None: - update_kwargs["status"] = status result = self._inkbox._ids_resource.update( self.agent_handle, **update_kwargs, ) diff --git a/sdk/python/inkbox/identities/resources/identities.py b/sdk/python/inkbox/identities/resources/identities.py index 351754f7..7fe76a76 100644 --- a/sdk/python/inkbox/identities/resources/identities.py +++ b/sdk/python/inkbox/identities/resources/identities.py @@ -130,10 +130,9 @@ def update( imessage_filter_mode: str | None = None, mail_filter_mode: str | None = None, phone_filter_mode: str | None = None, - status: str | None = None, ) -> _AgentIdentityData: """Update an identity's handle, display name, description, - iMessage reachability, contact-rule filter modes, and/or status. + iMessage reachability, and contact-rule filter modes. Only provided fields are applied; omitted fields are left unchanged. For ``display_name`` and ``description``, explicit @@ -161,8 +160,6 @@ def update( phone_filter_mode: ``"whitelist"`` or ``"blacklist"`` for this identity's phone contact rules (admin-only). The server rejects this with 422 when the identity has no phone number. - status: ``"active"`` or ``"paused"``. Call :meth:`delete` - to remove an identity; ``"deleted"`` is rejected here. """ body: dict[str, Any] = {} if new_handle is not None: @@ -205,8 +202,6 @@ def update( body["mail_filter_mode"] = mail_filter_mode if phone_filter_mode is not None: body["phone_filter_mode"] = phone_filter_mode - if status is not None: - body["status"] = status headers = None if idempotency_key is not None: headers = { diff --git a/sdk/python/inkbox/imessage/resources/contact_rules.py b/sdk/python/inkbox/imessage/resources/contact_rules.py index 87233f3d..765cca51 100644 --- a/sdk/python/inkbox/imessage/resources/contact_rules.py +++ b/sdk/python/inkbox/imessage/resources/contact_rules.py @@ -18,13 +18,11 @@ IMessageRuleAction, IMessageRuleMatchType, ) -from inkbox.mail.types import ContactRuleStatus if TYPE_CHECKING: from inkbox._http import HttpTransport _ORG_BASE = "/contact-rules" -_UNSET = object() def _rule_path(agent_handle: str, rule_id: UUID | str | None = None) -> str: @@ -77,8 +75,7 @@ def create( match_target: str, match_type: IMessageRuleMatchType | str = IMessageRuleMatchType.EXACT_NUMBER, ) -> IMessageContactRule: - """Create a rule. New rules are always ``active``; use - :meth:`update` to pause one after creation. + """Create a rule. Use :meth:`update` to change its allow/block action. Raises :class:`DuplicateContactRuleError` on 409 when a non-deleted rule with the same ``(match_type, match_target)`` already exists. @@ -100,19 +97,12 @@ def update( agent_handle: str, rule_id: UUID | str, *, - action: IMessageRuleAction | str = _UNSET, # type: ignore[assignment] - status: ContactRuleStatus | str = _UNSET, # type: ignore[assignment] + action: IMessageRuleAction | str, ) -> IMessageContactRule: - """Update ``action`` or ``status`` (admin-only).""" - body: dict[str, Any] = {} - if action is not _UNSET: - body["action"] = ( - action.value if isinstance(action, IMessageRuleAction) else action - ) - if status is not _UNSET: - body["status"] = ( - status.value if isinstance(status, ContactRuleStatus) else status - ) + """Update ``action`` (admin-only).""" + body = { + "action": action.value if isinstance(action, IMessageRuleAction) else action, + } data = self._http.patch(_rule_path(agent_handle, rule_id), json=body) return IMessageContactRule._from_dict(data) diff --git a/sdk/python/inkbox/mail/resources/contact_rules.py b/sdk/python/inkbox/mail/resources/contact_rules.py index 3b9d9ff4..8c07ab17 100644 --- a/sdk/python/inkbox/mail/resources/contact_rules.py +++ b/sdk/python/inkbox/mail/resources/contact_rules.py @@ -10,7 +10,6 @@ from uuid import UUID from inkbox.mail.types import ( - ContactRuleStatus, MailContactRule, MailRuleAction, MailRuleMatchType, @@ -21,7 +20,6 @@ _BASE = "/mailboxes" _ORG_BASE = "/contact-rules" -_UNSET = object() def _rule_path(email_address: str, rule_id: UUID | str | None = None) -> str: @@ -80,8 +78,7 @@ def create( match_type: MailRuleMatchType | str, match_target: str, ) -> MailContactRule: - """Create a rule. New rules are always ``active``; use - :meth:`update` to pause one after creation. + """Create a rule. Use :meth:`update` to change its allow/block action. Raises :class:`DuplicateContactRuleError` on 409 when a non-deleted rule with the same ``(match_type, match_target)`` already exists. @@ -101,23 +98,16 @@ def update( email_address: str, rule_id: UUID | str, *, - action: MailRuleAction | str = _UNSET, # type: ignore[assignment] - status: ContactRuleStatus | str = _UNSET, # type: ignore[assignment] + action: MailRuleAction | str, ) -> MailContactRule: - """Update ``action`` or ``status`` (admin-only). + """Update ``action`` (admin-only). ``match_type`` and ``match_target`` are immutable — delete + re-create to change them. """ - body: dict[str, Any] = {} - if action is not _UNSET: - body["action"] = ( - action.value if isinstance(action, MailRuleAction) else action - ) - if status is not _UNSET: - body["status"] = ( - status.value if isinstance(status, ContactRuleStatus) else status - ) + body = { + "action": action.value if isinstance(action, MailRuleAction) else action, + } data = self._http.patch(_rule_path(email_address, rule_id), json=body) return MailContactRule._from_dict(data) diff --git a/sdk/python/inkbox/mail/resources/identity_contact_rules.py b/sdk/python/inkbox/mail/resources/identity_contact_rules.py index 5316f013..7db91c9b 100644 --- a/sdk/python/inkbox/mail/resources/identity_contact_rules.py +++ b/sdk/python/inkbox/mail/resources/identity_contact_rules.py @@ -21,7 +21,6 @@ from uuid import UUID from inkbox.mail.types import ( - ContactRuleStatus, MailIdentityContactRule, MailRuleAction, MailRuleMatchType, @@ -31,7 +30,6 @@ from inkbox._http import HttpTransport _ORG_BASE = "/mail/contact-rules" -_UNSET = object() def _rule_path(agent_handle: str, rule_id: UUID | str | None = None) -> str: @@ -81,8 +79,7 @@ def create( match_type: MailRuleMatchType | str, match_target: str, ) -> MailIdentityContactRule: - """Create a rule for an agent identity. New rules are always - ``active``; use :meth:`update` to pause one after creation. + """Create a rule for an agent identity. Raises :class:`DuplicateContactRuleError` on 409 when a non-deleted rule with the same ``(match_type, match_target)`` already exists. @@ -102,23 +99,16 @@ def update( agent_handle: str, rule_id: UUID | str, *, - action: MailRuleAction | str = _UNSET, # type: ignore[assignment] - status: ContactRuleStatus | str = _UNSET, # type: ignore[assignment] + action: MailRuleAction | str, ) -> MailIdentityContactRule: - """Update ``action`` or ``status`` (admin-only). + """Update ``action`` (admin-only). ``match_type`` and ``match_target`` are immutable — delete + re-create to change them. """ - body: dict[str, Any] = {} - if action is not _UNSET: - body["action"] = ( - action.value if isinstance(action, MailRuleAction) else action - ) - if status is not _UNSET: - body["status"] = ( - status.value if isinstance(status, ContactRuleStatus) else status - ) + body = { + "action": action.value if isinstance(action, MailRuleAction) else action, + } data = self._http.patch(_rule_path(agent_handle, rule_id), json=body) return MailIdentityContactRule._from_dict(data) diff --git a/sdk/python/inkbox/phone/resources/contact_rules.py b/sdk/python/inkbox/phone/resources/contact_rules.py index 36558758..1fc86b90 100644 --- a/sdk/python/inkbox/phone/resources/contact_rules.py +++ b/sdk/python/inkbox/phone/resources/contact_rules.py @@ -9,7 +9,6 @@ from typing import TYPE_CHECKING, Any from uuid import UUID -from inkbox.mail.types import ContactRuleStatus from inkbox.phone.types import ( PhoneContactRule, PhoneRuleAction, @@ -21,7 +20,6 @@ _BASE = "/numbers" _ORG_BASE = "/contact-rules" -_UNSET = object() def _rule_path(phone_number_id: UUID | str, rule_id: UUID | str | None = None) -> str: @@ -80,8 +78,7 @@ def create( match_target: str, match_type: PhoneRuleMatchType | str = PhoneRuleMatchType.EXACT_NUMBER, ) -> PhoneContactRule: - """Create a rule. New rules are always ``active``; use - :meth:`update` to pause one after creation. + """Create a rule. Use :meth:`update` to change its allow/block action. Raises :class:`DuplicateContactRuleError` on 409 when a non-deleted rule with the same ``(match_type, match_target)`` already exists. @@ -101,19 +98,12 @@ def update( phone_number_id: UUID | str, rule_id: UUID | str, *, - action: PhoneRuleAction | str = _UNSET, # type: ignore[assignment] - status: ContactRuleStatus | str = _UNSET, # type: ignore[assignment] + action: PhoneRuleAction | str, ) -> PhoneContactRule: - """Update ``action`` or ``status`` (admin-only).""" - body: dict[str, Any] = {} - if action is not _UNSET: - body["action"] = ( - action.value if isinstance(action, PhoneRuleAction) else action - ) - if status is not _UNSET: - body["status"] = ( - status.value if isinstance(status, ContactRuleStatus) else status - ) + """Update ``action`` (admin-only).""" + body = { + "action": action.value if isinstance(action, PhoneRuleAction) else action, + } data = self._http.patch(_rule_path(phone_number_id, rule_id), json=body) return PhoneContactRule._from_dict(data) diff --git a/sdk/python/inkbox/phone/resources/identity_contact_rules.py b/sdk/python/inkbox/phone/resources/identity_contact_rules.py index 522f3151..24722c8c 100644 --- a/sdk/python/inkbox/phone/resources/identity_contact_rules.py +++ b/sdk/python/inkbox/phone/resources/identity_contact_rules.py @@ -24,7 +24,6 @@ from typing import TYPE_CHECKING, Any from uuid import UUID -from inkbox.mail.types import ContactRuleStatus from inkbox.phone.types import ( PhoneIdentityContactRule, PhoneRuleAction, @@ -35,7 +34,6 @@ from inkbox._http import HttpTransport _ORG_BASE = "/phone/contact-rules" -_UNSET = object() def _rule_path(agent_handle: str, rule_id: UUID | str | None = None) -> str: @@ -87,8 +85,7 @@ def create( match_target: str, match_type: PhoneRuleMatchType | str = PhoneRuleMatchType.EXACT_NUMBER, ) -> PhoneIdentityContactRule: - """Create a rule for an agent identity. New rules are always - ``active``; use :meth:`update` to pause one after creation. + """Create a rule for an agent identity. The identity must have a phone number — otherwise the server returns 422. @@ -111,19 +108,12 @@ def update( agent_handle: str, rule_id: UUID | str, *, - action: PhoneRuleAction | str = _UNSET, # type: ignore[assignment] - status: ContactRuleStatus | str = _UNSET, # type: ignore[assignment] + action: PhoneRuleAction | str, ) -> PhoneIdentityContactRule: - """Update ``action`` or ``status`` (admin-only).""" - body: dict[str, Any] = {} - if action is not _UNSET: - body["action"] = ( - action.value if isinstance(action, PhoneRuleAction) else action - ) - if status is not _UNSET: - body["status"] = ( - status.value if isinstance(status, ContactRuleStatus) else status - ) + """Update ``action`` (admin-only).""" + body = { + "action": action.value if isinstance(action, PhoneRuleAction) else action, + } data = self._http.patch(_rule_path(agent_handle, rule_id), json=body) return PhoneIdentityContactRule._from_dict(data) diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index d6e86b87..1b715af5 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "inkbox" -version = "0.5.4" +version = "0.5.5" description = "Python SDK for the Inkbox API" readme = "README.md" requires-python = ">=3.11" @@ -26,7 +26,7 @@ dev = [ "pytest>=8.0", "pytest-asyncio>=0.23", "pytest-cov>=5.0", - "ruff>=0.4", + "ruff>=0.4,<0.16", ] [project.urls] diff --git a/sdk/python/tests/test_contact_rules.py b/sdk/python/tests/test_contact_rules.py index 49415761..4a23b815 100644 --- a/sdk/python/tests/test_contact_rules.py +++ b/sdk/python/tests/test_contact_rules.py @@ -10,7 +10,7 @@ import pytest from inkbox.mail.resources.contact_rules import MailContactRulesResource -from inkbox.mail.types import MailRuleAction, MailRuleMatchType +from inkbox.mail.types import ContactRuleStatus, MailRuleAction, MailRuleMatchType from inkbox.phone.resources.contact_rules import PhoneContactRulesResource from inkbox.phone.types import PhoneRuleAction @@ -88,17 +88,18 @@ def test_create_sends_enum_values(self, transport): ) assert isinstance(rule.id, UUID) - def test_update_only_sends_supplied_fields(self, transport): + def test_update_sends_action_only(self, transport): transport.patch.return_value = {**MAIL_RULE_DICT, "status": "paused"} resource = MailContactRulesResource(transport) rid = "aaaa1111-0000-0000-0000-000000000011" - resource.update("box@inkbox.ai", rid, status="paused") + rule = resource.update("box@inkbox.ai", rid, action="block") transport.patch.assert_called_once_with( f"/mailboxes/box@inkbox.ai/contact-rules/{rid}", - json={"status": "paused"}, + json={"action": "block"}, ) + assert rule.status is ContactRuleStatus.PAUSED def test_list_all_org_wide(self, transport): transport.get.return_value = [MAIL_RULE_DICT] diff --git a/sdk/python/tests/test_identity_scoped_features.py b/sdk/python/tests/test_identity_scoped_features.py index 0ef21ff5..e07b29e7 100644 --- a/sdk/python/tests/test_identity_scoped_features.py +++ b/sdk/python/tests/test_identity_scoped_features.py @@ -104,9 +104,9 @@ def test_get_update_delete_paths(self): rid = MAIL_RULE_DICT["id"] res.get("my-agent", rid) http.get.assert_called_with(f"/identities/my-agent/mail-contact-rules/{rid}") - res.update("my-agent", rid, status="paused") + res.update("my-agent", rid, action="allow") http.patch.assert_called_once_with( - f"/identities/my-agent/mail-contact-rules/{rid}", json={"status": "paused"} + f"/identities/my-agent/mail-contact-rules/{rid}", json={"action": "allow"} ) res.delete("my-agent", rid) http.delete.assert_called_once_with( @@ -312,11 +312,11 @@ def test_create_mail_contact_rule_delegates(self): match_target="spam@example.com", ) - def test_update_mail_contact_rule_only_forwards_set_kwargs(self): + def test_update_mail_contact_rule_forwards_action(self): identity, inkbox = _identity() - identity.update_mail_contact_rule("rid", status="paused") + identity.update_mail_contact_rule("rid", action="allow") inkbox._mail_identity_contact_rules.update.assert_called_once_with( - identity.agent_handle, "rid", status="paused", + identity.agent_handle, "rid", action="allow", ) def test_list_phone_contact_rules_without_phone_returns_empty(self): @@ -334,7 +334,7 @@ def test_phone_rule_cgud_requires_phone_number(self): with pytest.raises(InkboxError, match="no phone number"): identity.create_phone_contact_rule(action="block", match_target="+14155550199") with pytest.raises(InkboxError, match="no phone number"): - identity.update_phone_contact_rule("rid", status="paused") + identity.update_phone_contact_rule("rid", action="block") with pytest.raises(InkboxError, match="no phone number"): identity.delete_phone_contact_rule("rid") diff --git a/sdk/python/tests/test_imessage.py b/sdk/python/tests/test_imessage.py index 71b00fe0..c6930dfd 100644 --- a/sdk/python/tests/test_imessage.py +++ b/sdk/python/tests/test_imessage.py @@ -582,12 +582,12 @@ def test_updates_rule(self, client, transport): transport.patch.return_value = IMESSAGE_CONTACT_RULE_DICT client._imessage_contact_rules.update( - HANDLE, RULE_ID, status=ContactRuleStatus.PAUSED, + HANDLE, RULE_ID, action=IMessageRuleAction.ALLOW, ) transport.patch.assert_called_once_with( f"/identities/{HANDLE}/contact-rules/{RULE_ID}", - json={"status": "paused"}, + json={"action": "allow"}, ) def test_deletes_rule(self, client, transport): diff --git a/sdk/python/uv.lock b/sdk/python/uv.lock index ef2f543a..6fd198e6 100644 --- a/sdk/python/uv.lock +++ b/sdk/python/uv.lock @@ -398,7 +398,7 @@ wheels = [ [[package]] name = "inkbox" -version = "0.5.4" +version = "0.5.5" source = { editable = "." } dependencies = [ { name = "argon2-cffi" }, @@ -428,7 +428,7 @@ requires-dist = [ { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=5.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.4" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.4,<0.16" }, ] provides-extras = ["dev"] diff --git a/sdk/rust/Cargo.lock b/sdk/rust/Cargo.lock index 049c3bf0..93e8981f 100644 --- a/sdk/rust/Cargo.lock +++ b/sdk/rust/Cargo.lock @@ -1187,7 +1187,7 @@ dependencies = [ [[package]] name = "inkbox" -version = "0.5.4" +version = "0.5.5" dependencies = [ "aes-gcm", "argon2", diff --git a/sdk/rust/Cargo.toml b/sdk/rust/Cargo.toml index 7175b4e4..c4452440 100644 --- a/sdk/rust/Cargo.toml +++ b/sdk/rust/Cargo.toml @@ -8,7 +8,7 @@ [package] name = "inkbox" -version = "0.5.4" +version = "0.5.5" edition = "2021" rust-version = "1.74" description = "Rust SDK for the Inkbox API" diff --git a/sdk/rust/src/agent_identity.rs b/sdk/rust/src/agent_identity.rs index 875713b7..baa72b8c 100644 --- a/sdk/rust/src/agent_identity.rs +++ b/sdk/rust/src/agent_identity.rs @@ -48,15 +48,14 @@ use crate::imessage::types::{ IMessageReactionType, IMessageSendStyle, IdentityIMessageNumber, }; use crate::mail::types::{ - ContactRuleStatus as MailContactRuleStatus, FilterMode, ForwardMode, MailIdentityContactRule, - MailRuleAction, MailRuleMatchType, Message, MessageDetail, MessageDirection, ThreadDetail, + FilterMode, ForwardMode, MailIdentityContactRule, MailRuleAction, MailRuleMatchType, Message, + MessageDetail, MessageDirection, ThreadDetail, }; use crate::phone::resources::texts::TextRecipients; use crate::phone::types::{ - CallOrigin, ContactRuleStatus as PhoneContactRuleStatus, HostedAgentConfig, IncomingCallAction, - IncomingCallActionConfig, PhoneCall, PhoneCallWithRateLimit, PhoneIdentityContactRule, - PhoneRuleAction, PhoneRuleMatchType, PhoneTranscript, TextConversationSummary, - TextConversationUpdateResult, TextMessage, + CallOrigin, HostedAgentConfig, IncomingCallAction, IncomingCallActionConfig, PhoneCall, + PhoneCallWithRateLimit, PhoneIdentityContactRule, PhoneRuleAction, PhoneRuleMatchType, + PhoneTranscript, TextConversationSummary, TextConversationUpdateResult, TextMessage, }; use crate::signing_keys::{SigningKey, SigningKeyStatus}; use crate::tunnels::types::TunnelSummary; @@ -1187,20 +1186,15 @@ impl AgentIdentity { ) } - /// Update a mail rule's `action` or `status` (admin-only). `None` arguments - /// are left unchanged. + /// Update a mail rule's `action` (admin-only). pub fn update_mail_contact_rule( &self, rule_id: &str, - action: Option, - status: Option, + action: MailRuleAction, ) -> Result { - self.inkbox.mail_identity_contact_rules().update( - &self.agent_handle(), - rule_id, - action, - status, - ) + self.inkbox + .mail_identity_contact_rules() + .update(&self.agent_handle(), rule_id, action) } /// Delete one of this identity's mail contact rules (admin-only). @@ -1262,21 +1256,17 @@ impl AgentIdentity { ) } - /// Update a phone rule's `action` or `status` (admin-only). `None` - /// arguments are left unchanged. Errors if this identity has no phone number. + /// Update a phone rule's `action` (admin-only). Errors if this identity has + /// no phone number. pub fn update_phone_contact_rule( &self, rule_id: &str, - action: Option, - status: Option, + action: PhoneRuleAction, ) -> Result { self.require_phone()?; - self.inkbox.phone_identity_contact_rules().update( - &self.agent_handle(), - rule_id, - action, - status, - ) + self.inkbox + .phone_identity_contact_rules() + .update(&self.agent_handle(), rule_id, action) } /// Delete one of this identity's phone contact rules (admin-only). @@ -1313,7 +1303,7 @@ impl AgentIdentity { // ----------------------------------------------------------------------- /// Update this identity's handle, display name, description, iMessage - /// reachability, contact-rule filter modes, and/or status. + /// reachability and contact-rule filter modes. /// /// Only provided fields are applied; omitted fields are left unchanged. For /// `display_name` and `description`, `Unset::Value(None)` clears the column; @@ -1330,8 +1320,6 @@ impl AgentIdentity { /// * `phone_filter_mode` - `"whitelist"` or `"blacklist"` for this identity's /// phone contact rules (admin-only). Rejected with 422 when the identity /// has no phone number. - /// * `status` - `"active"` or `"paused"`. Call [`Self::delete`] to remove the - /// identity; `"deleted"` is rejected here. #[allow(clippy::too_many_arguments)] pub fn update( &self, @@ -1342,7 +1330,6 @@ impl AgentIdentity { imessage_filter_mode: Option<&str>, mail_filter_mode: Option<&str>, phone_filter_mode: Option<&str>, - status: Option<&str>, ) -> Result<()> { self.update_with_imessage_number( new_handle, @@ -1352,7 +1339,6 @@ impl AgentIdentity { imessage_filter_mode, mail_filter_mode, phone_filter_mode, - status, Unset::Omit, None, None, @@ -1374,7 +1360,6 @@ impl AgentIdentity { imessage_filter_mode: Option<&str>, mail_filter_mode: Option<&str>, phone_filter_mode: Option<&str>, - status: Option<&str>, imessage_number_id: Unset, imessage_number_type: Option, idempotency_key: Option<&str>, @@ -1388,7 +1373,6 @@ impl AgentIdentity { imessage_filter_mode, mail_filter_mode, phone_filter_mode, - status, imessage_number_id, imessage_number_type, idempotency_key, @@ -1670,7 +1654,7 @@ mod tests { ) .err(), identity - .update_phone_contact_rule("rid", Some(PhoneRuleAction::Block), None) + .update_phone_contact_rule("rid", PhoneRuleAction::Block) .err(), identity.delete_phone_contact_rule("rid").err(), ]; @@ -2090,7 +2074,6 @@ mod tests { None, None, None, - None, Unset::Omit, Some(IMessageNumberType::DedicatedOutbound), Some("identity-claim-123"), @@ -2135,7 +2118,6 @@ mod tests { None, None, None, - None, ) .unwrap(); diff --git a/sdk/rust/src/identities/resources/identities.rs b/sdk/rust/src/identities/resources/identities.rs index 0f194611..4c57e394 100644 --- a/sdk/rust/src/identities/resources/identities.rs +++ b/sdk/rust/src/identities/resources/identities.rs @@ -167,7 +167,7 @@ impl IdentitiesResource { } /// Update an identity's handle, display name, description, iMessage - /// reachability, and/or status. + /// reachability and contact-rule filter modes. /// /// Only provided fields are applied; omitted fields are left unchanged. For /// `display_name` and `description`, `Unset::Value(None)` clears the column; @@ -185,8 +185,6 @@ impl IdentitiesResource { /// * `phone_filter_mode` - `"whitelist"` or `"blacklist"` for this identity's /// phone contact rules (admin-only). The server rejects this with 422 when /// the identity has no phone number. - /// * `status` - `"active"` or `"paused"`. Call [`Self::delete`] to remove an - /// identity; `"deleted"` is rejected here. #[allow(clippy::too_many_arguments)] pub fn update( &self, @@ -198,7 +196,6 @@ impl IdentitiesResource { imessage_filter_mode: Option<&str>, mail_filter_mode: Option<&str>, phone_filter_mode: Option<&str>, - status: Option<&str>, ) -> Result { self.update_with_imessage_number( agent_handle, @@ -209,7 +206,6 @@ impl IdentitiesResource { imessage_filter_mode, mail_filter_mode, phone_filter_mode, - status, Unset::Omit, None, None, @@ -235,7 +231,6 @@ impl IdentitiesResource { imessage_filter_mode: Option<&str>, mail_filter_mode: Option<&str>, phone_filter_mode: Option<&str>, - status: Option<&str>, imessage_number_id: Unset, imessage_number_type: Option, idempotency_key: Option<&str>, @@ -313,10 +308,6 @@ impl IdentitiesResource { if let Some(mode) = phone_filter_mode { body.insert("phone_filter_mode".into(), Value::String(mode.to_string())); } - if let Some(s) = status { - body.insert("status".into(), Value::String(s.to_string())); - } - let body = Value::Object(body); let path = format!("/{agent_handle}"); let response = match idempotency_key { @@ -567,7 +558,6 @@ mod tests { None, None, None, - None, Unset::Value(Some(number_id)), None, None, @@ -599,7 +589,6 @@ mod tests { None, None, None, - None, Unset::Omit, Some(IMessageNumberType::DedicatedOutbound), Some("identity-claim-123"), @@ -627,7 +616,6 @@ mod tests { None, None, None, - None, Unset::Value(None), None, None, @@ -650,7 +638,6 @@ mod tests { None, None, None, - None, Unset::Omit, Some(IMessageNumberType::DedicatedInbound), None, diff --git a/sdk/rust/src/imessage/resources/contact_rules.rs b/sdk/rust/src/imessage/resources/contact_rules.rs index e4a91905..0793218d 100644 --- a/sdk/rust/src/imessage/resources/contact_rules.rs +++ b/sdk/rust/src/imessage/resources/contact_rules.rs @@ -11,9 +11,7 @@ use uuid::Uuid; use crate::error::Result; use crate::http::HttpTransport; -use crate::imessage::types::{ - ContactRuleStatus, IMessageContactRule, IMessageRuleAction, IMessageRuleMatchType, -}; +use crate::imessage::types::{IMessageContactRule, IMessageRuleAction, IMessageRuleMatchType}; const ORG_BASE: &str = "/contact-rules"; @@ -79,8 +77,7 @@ impl IMessageContactRulesResource { Ok(serde_json::from_value(data)?) } - /// Create a rule. New rules are always `active`; use [`Self::update`] to - /// pause one after creation. + /// Create a rule. Use [`Self::update`] to change its allow/block action. /// /// Returns [`crate::error::InkboxError::DuplicateContactRule`] on 409 when a /// non-deleted rule with the same `(match_type, match_target)` already exists. @@ -110,32 +107,19 @@ impl IMessageContactRulesResource { Ok(serde_json::from_value(data)?) } - /// Update `action` or `status` (admin-only). - /// - /// `None` arguments are omitted from the request body, mirroring the - /// Python `_UNSET` sentinel. + /// Update `action` (admin-only). /// /// # Arguments /// * `agent_handle` - Handle of the agent identity owning the rule. /// * `rule_id` - Id of the rule to update. - /// * `action` - Optional new action. - /// * `status` - Optional new status. + /// * `action` - New action. pub fn update( &self, agent_handle: &str, rule_id: &str, - action: Option, - status: Option, + action: IMessageRuleAction, ) -> Result { - // Build the body inserting only the fields that were supplied. - let mut map = serde_json::Map::new(); - if let Some(a) = action { - map.insert("action".to_string(), json!(a.as_str())); - } - if let Some(s) = status { - map.insert("status".to_string(), json!(s.as_str())); - } - let body = serde_json::Value::Object(map); + let body = json!({"action": action.as_str()}); let data = self .http .patch(&rule_path(agent_handle, Some(rule_id)), &body)?; diff --git a/sdk/rust/src/mail/resources/contact_rules.rs b/sdk/rust/src/mail/resources/contact_rules.rs index 54865425..103554a8 100644 --- a/sdk/rust/src/mail/resources/contact_rules.rs +++ b/sdk/rust/src/mail/resources/contact_rules.rs @@ -7,7 +7,7 @@ use uuid::Uuid; use crate::error::Result; use crate::http::HttpTransport; -use crate::mail::types::{ContactRuleStatus, MailContactRule, MailRuleAction, MailRuleMatchType}; +use crate::mail::types::{MailContactRule, MailRuleAction, MailRuleMatchType}; const BASE: &str = "/mailboxes"; const ORG_BASE: &str = "/contact-rules"; @@ -73,8 +73,8 @@ impl MailContactRulesResource { Ok(serde_json::from_value(data)?) } - /// Create a rule. New rules are always `active`; use - /// [`update`](Self::update) to pause one after creation. + /// Create a rule. Use [`update`](Self::update) to change its allow/block + /// action. /// /// Returns [`crate::error::InkboxError::DuplicateContactRule`] on 409 when /// a non-deleted rule with the same `(match_type, match_target)` already @@ -99,29 +99,20 @@ impl MailContactRulesResource { Ok(serde_json::from_value(data)?) } - /// Update `action` or `status` (admin-only). + /// Update `action` (admin-only). /// /// `match_type` and `match_target` are immutable — delete + re-create to - /// change them. Pass `None` for a field to leave it untouched (mirrors the - /// Python `_UNSET` sentinel: omitted keys are never sent). + /// change them. pub fn update( &self, email_address: &str, rule_id: &str, - action: Option, - status: Option, + action: MailRuleAction, ) -> Result { - let mut body = serde_json::Map::new(); - if let Some(a) = action { - body.insert("action".into(), Value::String(a.as_str().to_string())); - } - if let Some(s) = status { - body.insert("status".into(), Value::String(s.as_str().to_string())); - } - let data = self.http.patch( - &rule_path(email_address, Some(rule_id)), - &Value::Object(body), - )?; + let body = json!({"action": action.as_str()}); + let data = self + .http + .patch(&rule_path(email_address, Some(rule_id)), &body)?; Ok(serde_json::from_value(data)?) } diff --git a/sdk/rust/src/mail/resources/identity_contact_rules.rs b/sdk/rust/src/mail/resources/identity_contact_rules.rs index 26ae23a3..339bb294 100644 --- a/sdk/rust/src/mail/resources/identity_contact_rules.rs +++ b/sdk/rust/src/mail/resources/identity_contact_rules.rs @@ -19,9 +19,7 @@ use uuid::Uuid; use crate::error::Result; use crate::http::HttpTransport; -use crate::mail::types::{ - ContactRuleStatus, MailIdentityContactRule, MailRuleAction, MailRuleMatchType, -}; +use crate::mail::types::{MailIdentityContactRule, MailRuleAction, MailRuleMatchType}; const ORG_BASE: &str = "/mail/contact-rules"; @@ -98,8 +96,8 @@ impl MailIdentityContactRulesResource { Ok(serde_json::from_value(data)?) } - /// Create a rule for an agent identity. New rules are always `active`; use - /// [`Self::update`] to pause one after creation. + /// Create a rule for an agent identity. Use [`Self::update`] to change its + /// allow/block action. /// /// Returns [`crate::error::InkboxError::DuplicateContactRule`] on 409 when a /// non-deleted rule with the same `(match_type, match_target)` already exists. @@ -123,29 +121,20 @@ impl MailIdentityContactRulesResource { Ok(serde_json::from_value(data)?) } - /// Update `action` or `status` (admin-only). + /// Update `action` (admin-only). /// /// `match_type` and `match_target` are immutable — delete + re-create to - /// change them. `None` arguments are omitted from the body, mirroring the - /// Python `_UNSET` sentinel. + /// change them. pub fn update( &self, agent_handle: &str, rule_id: &str, - action: Option, - status: Option, + action: MailRuleAction, ) -> Result { - let mut body = serde_json::Map::new(); - if let Some(a) = action { - body.insert("action".into(), Value::String(a.as_str().to_string())); - } - if let Some(s) = status { - body.insert("status".into(), Value::String(s.as_str().to_string())); - } - let data = self.http.patch( - &rule_path(agent_handle, Some(rule_id)), - &Value::Object(body), - )?; + let body = json!({"action": action.as_str()}); + let data = self + .http + .patch(&rule_path(agent_handle, Some(rule_id)), &body)?; Ok(serde_json::from_value(data)?) } diff --git a/sdk/rust/src/phone/resources/contact_rules.rs b/sdk/rust/src/phone/resources/contact_rules.rs index 6c7a508f..0c79d2c5 100644 --- a/sdk/rust/src/phone/resources/contact_rules.rs +++ b/sdk/rust/src/phone/resources/contact_rules.rs @@ -6,9 +6,7 @@ use serde_json::{Map, Value}; use crate::error::Result; use crate::http::HttpTransport; -use crate::phone::types::{ - ContactRuleStatus, PhoneContactRule, PhoneRuleAction, PhoneRuleMatchType, -}; +use crate::phone::types::{PhoneContactRule, PhoneRuleAction, PhoneRuleMatchType}; const BASE: &str = "/numbers"; const ORG_BASE: &str = "/contact-rules"; @@ -80,8 +78,8 @@ impl PhoneContactRulesResource { Ok(serde_json::from_value(data)?) } - /// Create a rule. New rules are always `active`; use [`update`](Self::update) - /// to pause one after creation. + /// Create a rule. Use [`update`](Self::update) to change its allow/block + /// action. /// /// Returns [`InkboxError::DuplicateContactRule`](crate::error::InkboxError) /// on 409 when a non-deleted rule with the same `(match_type, match_target)` @@ -105,26 +103,15 @@ impl PhoneContactRulesResource { Ok(serde_json::from_value(data)?) } - /// Update `action` or `status` (admin-only). Omitted (`None`) fields are - /// left unchanged, matching the Python `_UNSET` sentinel. + /// Update `action` (admin-only). pub fn update( &self, phone_number_id: &str, rule_id: &str, - action: Option, - status: Option, + action: PhoneRuleAction, ) -> Result { let mut body = Map::new(); - if let Some(a) = action { - body.insert("action".into(), a.as_str().into()); - } - if let Some(s) = status { - let s = match s { - ContactRuleStatus::Active => "active", - ContactRuleStatus::Paused => "paused", - }; - body.insert("status".into(), s.into()); - } + body.insert("action".into(), action.as_str().into()); let data = self .http .patch(&rule_path(phone_number_id, Some(rule_id)), &body)?; diff --git a/sdk/rust/src/phone/resources/identity_contact_rules.rs b/sdk/rust/src/phone/resources/identity_contact_rules.rs index 9337e628..884426f9 100644 --- a/sdk/rust/src/phone/resources/identity_contact_rules.rs +++ b/sdk/rust/src/phone/resources/identity_contact_rules.rs @@ -21,9 +21,7 @@ use serde_json::{Map, Value}; use crate::error::Result; use crate::http::HttpTransport; -use crate::phone::types::{ - ContactRuleStatus, PhoneIdentityContactRule, PhoneRuleAction, PhoneRuleMatchType, -}; +use crate::phone::types::{PhoneIdentityContactRule, PhoneRuleAction, PhoneRuleMatchType}; const ORG_BASE: &str = "/phone/contact-rules"; @@ -95,8 +93,8 @@ impl PhoneIdentityContactRulesResource { Ok(serde_json::from_value(data)?) } - /// Create a rule for an agent identity. New rules are always `active`; use - /// [`Self::update`] to pause one after creation. + /// Create a rule for an agent identity. Use [`Self::update`] to change its + /// allow/block action. /// /// The identity must have a phone number — otherwise the server returns 422. /// @@ -121,26 +119,15 @@ impl PhoneIdentityContactRulesResource { Ok(serde_json::from_value(data)?) } - /// Update `action` or `status` (admin-only). Omitted (`None`) fields are - /// left unchanged, matching the Python `_UNSET` sentinel. + /// Update `action` (admin-only). pub fn update( &self, agent_handle: &str, rule_id: &str, - action: Option, - status: Option, + action: PhoneRuleAction, ) -> Result { let mut body = Map::new(); - if let Some(a) = action { - body.insert("action".into(), a.as_str().into()); - } - if let Some(s) = status { - let s = match s { - ContactRuleStatus::Active => "active", - ContactRuleStatus::Paused => "paused", - }; - body.insert("status".into(), s.into()); - } + body.insert("action".into(), action.as_str().into()); let data = self .http .patch(&rule_path(agent_handle, Some(rule_id)), &body)?; diff --git a/sdk/typescript/CHANGELOG.md b/sdk/typescript/CHANGELOG.md index b0927756..c9b71f53 100644 --- a/sdk/typescript/CHANGELOG.md +++ b/sdk/typescript/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.5.5 — Action-only contact-rule updates + +### Changed + +- Contact-rule update methods now require `action`. + +### Removed + +- **Source-breaking:** identity update options and contact-rule update options no longer accept lifecycle `status`. Remove those properties before upgrading. + +### Compatibility + +- Response models continue to parse existing `paused` rows. + ## 0.5.4 — Dedicated outbound iMessage groups ### Added diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index 395b6ff4..b6257d1b 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -157,8 +157,7 @@ await identity2.refresh(); // re-fetch channels from API // List all identities for your org const allIdentities = await inkbox.listIdentities(); -// Update status or handle -await identity.update({ status: "paused" }); +// Update identity metadata or handle await identity.update({ newHandle: "sales-bot-v2" }); // Release the phone number (carrier release + local delete). Mailbox and @@ -508,6 +507,7 @@ import { DedicatedIMessageNumberQuotaExceededError, IdempotencyKeyReusedError, IMessageNumberType, + IMessageRuleAction, IMessageSendStyle, } from "@inkbox/sdk"; @@ -548,7 +548,7 @@ await identity.sendIMessage({ conversationId: convos[0].id, mediaUrls: [upload.m // Per-identity allow/block rules, interpreted via imessageFilterMode. await inkbox.imessageContactRules.create("my-agent", { - action: "block", + action: IMessageRuleAction.BLOCK, matchTarget: "+15555550999", }); diff --git a/sdk/typescript/package-lock.json b/sdk/typescript/package-lock.json index db2fcd3a..781cb67f 100644 --- a/sdk/typescript/package-lock.json +++ b/sdk/typescript/package-lock.json @@ -1,12 +1,12 @@ { "name": "@inkbox/sdk", - "version": "0.5.4", + "version": "0.5.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@inkbox/sdk", - "version": "0.5.4", + "version": "0.5.5", "license": "MIT", "dependencies": { "@peculiar/x509": "^2.0.0", diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json index 6dcb5d8f..259becfa 100644 --- a/sdk/typescript/package.json +++ b/sdk/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@inkbox/sdk", - "version": "0.5.4", + "version": "0.5.5", "description": "TypeScript SDK for the Inkbox API", "license": "MIT", "type": "module", diff --git a/sdk/typescript/src/agent_identity.ts b/sdk/typescript/src/agent_identity.ts index 49b1c0bb..99373585 100644 --- a/sdk/typescript/src/agent_identity.ts +++ b/sdk/typescript/src/agent_identity.ts @@ -1030,7 +1030,7 @@ export class AgentIdentity { return this._inkbox._mailIdentityContactRules.create(this.agentHandle, options); } - /** Update a mail rule's `action` or `status` (admin-only). */ + /** Update a mail rule's `action` (admin-only). */ async updateMailContactRule( ruleId: string, options: UpdateMailIdentityContactRuleOptions, @@ -1077,7 +1077,7 @@ export class AgentIdentity { return this._inkbox._phoneIdentityContactRules.create(this.agentHandle, options); } - /** Update a phone rule's `action` or `status` (admin-only). */ + /** Update a phone rule's `action` (admin-only). */ async updatePhoneContactRule( ruleId: string, options: UpdatePhoneIdentityContactRuleOptions, @@ -1117,7 +1117,7 @@ export class AgentIdentity { /** * Update this identity's handle, display name, description, iMessage - * reachability, and/or status. + * reachability, and/or contact-rule filter modes. * * Only provided fields are applied; omitted fields are left unchanged. * For `displayName` and `description`, explicit `null` clears the column; @@ -1142,8 +1142,6 @@ export class AgentIdentity { * @param options.phoneFilterMode - `"whitelist"` or `"blacklist"` for this * identity's phone contact rules (admin-only). Rejected with a 422 when * the identity has no phone number. - * @param options.status - `"active"` or `"paused"`. Call `delete()` - * to remove the identity; `"deleted"` is rejected here. */ async update(options: UpdateIdentityOptions): Promise { const data = await this._inkbox._idsResource.update(this.agentHandle, options); diff --git a/sdk/typescript/src/identities/resources/identities.ts b/sdk/typescript/src/identities/resources/identities.ts index 338dfadb..f9bc1452 100644 --- a/sdk/typescript/src/identities/resources/identities.ts +++ b/sdk/typescript/src/identities/resources/identities.ts @@ -108,7 +108,7 @@ export class IdentitiesResource { /** * Update an identity's handle, display name, description, iMessage - * reachability, and/or status. + * reachability, and/or contact-rule filter modes. * * Only provided fields are applied; omitted fields are left unchanged. * For `displayName` and `description`, explicit `null` clears the value @@ -131,8 +131,6 @@ export class IdentitiesResource { * @param options.phoneFilterMode - `"whitelist"` or `"blacklist"` for this * identity's phone contact rules (admin-only). The server rejects this * with 422 when the identity has no phone number. - * @param options.status - `"active"` or `"paused"`. Call `delete()` to - * remove an identity; `"deleted"` is rejected here. */ async update( agentHandle: string, @@ -171,7 +169,6 @@ export class IdentitiesResource { if (options.imessageFilterMode !== undefined) body["imessage_filter_mode"] = options.imessageFilterMode; if (options.mailFilterMode !== undefined) body["mail_filter_mode"] = options.mailFilterMode; if (options.phoneFilterMode !== undefined) body["phone_filter_mode"] = options.phoneFilterMode; - if (options.status !== undefined) body["status"] = options.status; try { const data = options.idempotencyKey === undefined ? await this.http.patch(`/${agentHandle}`, body) diff --git a/sdk/typescript/src/identities/types.ts b/sdk/typescript/src/identities/types.ts index 6f8bfe5c..dd858e8f 100644 --- a/sdk/typescript/src/identities/types.ts +++ b/sdk/typescript/src/identities/types.ts @@ -97,7 +97,6 @@ export interface UpdateIdentityOptions { imessageFilterMode?: "whitelist" | "blacklist"; mailFilterMode?: "whitelist" | "blacklist"; phoneFilterMode?: "whitelist" | "blacklist"; - status?: "active" | "paused"; } export interface IdentityMailbox { diff --git a/sdk/typescript/src/imessage/resources/contactRules.ts b/sdk/typescript/src/imessage/resources/contactRules.ts index 51f9b02f..f233bf89 100644 --- a/sdk/typescript/src/imessage/resources/contactRules.ts +++ b/sdk/typescript/src/imessage/resources/contactRules.ts @@ -9,7 +9,6 @@ */ import { HttpTransport } from "../../_http.js"; -import { ContactRuleStatus } from "../../mail/types.js"; import { IMessageContactRule, IMessageRuleAction, @@ -39,8 +38,7 @@ export interface CreateIMessageContactRuleOptions { } export interface UpdateIMessageContactRuleOptions { - action?: IMessageRuleAction; - status?: ContactRuleStatus; + action: IMessageRuleAction; } export interface ListAllIMessageContactRulesOptions { @@ -78,8 +76,7 @@ export class IMessageContactRulesResource { } /** - * Create a rule. New rules are always `active`; use {@link update} - * to pause one after creation. + * Create a rule with an allow/block action. * * @throws {DuplicateContactRuleError} 409 when a non-deleted rule with * the same `(matchType, matchTarget)` already exists. @@ -100,15 +97,13 @@ export class IMessageContactRulesResource { return parseIMessageContactRule(data); } - /** Update `action` or `status` (admin-only). */ + /** Update `action` (admin-only). */ async update( agentHandle: string, ruleId: string, options: UpdateIMessageContactRuleOptions, ): Promise { - const body: Record = {}; - if (options.action !== undefined) body.action = options.action; - if (options.status !== undefined) body.status = options.status; + const body = { action: options.action }; const data = await this.http.patch( rulePath(agentHandle, ruleId), body, diff --git a/sdk/typescript/src/mail/resources/contactRules.ts b/sdk/typescript/src/mail/resources/contactRules.ts index 3d5486f5..eff04ff8 100644 --- a/sdk/typescript/src/mail/resources/contactRules.ts +++ b/sdk/typescript/src/mail/resources/contactRules.ts @@ -11,7 +11,6 @@ import { HttpTransport } from "../../_http.js"; import { - ContactRuleStatus, MailContactRule, MailRuleAction, MailRuleMatchType, @@ -41,8 +40,7 @@ export interface CreateMailContactRuleOptions { } export interface UpdateMailContactRuleOptions { - action?: MailRuleAction; - status?: ContactRuleStatus; + action: MailRuleAction; } export interface ListAllMailContactRulesOptions { @@ -100,9 +98,7 @@ export class MailContactRulesResource { ruleId: string, options: UpdateMailContactRuleOptions, ): Promise { - const body: Record = {}; - if (options.action !== undefined) body.action = options.action; - if (options.status !== undefined) body.status = options.status; + const body = { action: options.action }; const data = await this.http.patch( rulePath(emailAddress, ruleId), body, diff --git a/sdk/typescript/src/mail/resources/identityContactRules.ts b/sdk/typescript/src/mail/resources/identityContactRules.ts index d86842c6..4ceab227 100644 --- a/sdk/typescript/src/mail/resources/identityContactRules.ts +++ b/sdk/typescript/src/mail/resources/identityContactRules.ts @@ -17,7 +17,6 @@ import { HttpTransport } from "../../_http.js"; import { - ContactRuleStatus, MailIdentityContactRule, MailRuleAction, MailRuleMatchType, @@ -46,8 +45,7 @@ export interface CreateMailIdentityContactRuleOptions { } export interface UpdateMailIdentityContactRuleOptions { - action?: MailRuleAction; - status?: ContactRuleStatus; + action: MailRuleAction; } export interface ListAllMailIdentityContactRulesOptions { @@ -86,8 +84,7 @@ export class MailIdentityContactRulesResource { } /** - * Create a rule for an agent identity. New rules are always `active`; - * use {@link update} to pause one after creation. + * Create a rule for an agent identity. * * @throws {DuplicateContactRuleError} 409 when a non-deleted rule with * the same `(matchType, matchTarget)` already exists. @@ -109,7 +106,7 @@ export class MailIdentityContactRulesResource { } /** - * Update `action` or `status` (admin-only). + * Update `action` (admin-only). * * `matchType` and `matchTarget` are immutable — delete + re-create to * change them. @@ -119,9 +116,7 @@ export class MailIdentityContactRulesResource { ruleId: string, options: UpdateMailIdentityContactRuleOptions, ): Promise { - const body: Record = {}; - if (options.action !== undefined) body.action = options.action; - if (options.status !== undefined) body.status = options.status; + const body = { action: options.action }; const data = await this.http.patch( rulePath(agentHandle, ruleId), body, diff --git a/sdk/typescript/src/phone/resources/contactRules.ts b/sdk/typescript/src/phone/resources/contactRules.ts index 1c3711e3..603e0c03 100644 --- a/sdk/typescript/src/phone/resources/contactRules.ts +++ b/sdk/typescript/src/phone/resources/contactRules.ts @@ -10,7 +10,6 @@ */ import { HttpTransport } from "../../_http.js"; -import { ContactRuleStatus } from "../../mail/types.js"; import { PhoneContactRule, PhoneRuleAction, @@ -41,8 +40,7 @@ export interface CreatePhoneContactRuleOptions { } export interface UpdatePhoneContactRuleOptions { - action?: PhoneRuleAction; - status?: ContactRuleStatus; + action: PhoneRuleAction; } export interface ListAllPhoneContactRulesOptions { @@ -100,9 +98,7 @@ export class PhoneContactRulesResource { ruleId: string, options: UpdatePhoneContactRuleOptions, ): Promise { - const body: Record = {}; - if (options.action !== undefined) body.action = options.action; - if (options.status !== undefined) body.status = options.status; + const body = { action: options.action }; const data = await this.http.patch( rulePath(phoneNumberId, ruleId), body, diff --git a/sdk/typescript/src/phone/resources/identityContactRules.ts b/sdk/typescript/src/phone/resources/identityContactRules.ts index 1c613f14..d5985726 100644 --- a/sdk/typescript/src/phone/resources/identityContactRules.ts +++ b/sdk/typescript/src/phone/resources/identityContactRules.ts @@ -19,7 +19,6 @@ */ import { HttpTransport } from "../../_http.js"; -import { ContactRuleStatus } from "../../mail/types.js"; import { PhoneIdentityContactRule, PhoneRuleAction, @@ -49,8 +48,7 @@ export interface CreatePhoneIdentityContactRuleOptions { } export interface UpdatePhoneIdentityContactRuleOptions { - action?: PhoneRuleAction; - status?: ContactRuleStatus; + action: PhoneRuleAction; } export interface ListAllPhoneIdentityContactRulesOptions { @@ -93,8 +91,8 @@ export class PhoneIdentityContactRulesResource { } /** - * Create a rule for an agent identity. New rules are always `active`; - * use {@link update} to pause one after creation. The identity must have + * Create a rule for an agent identity. + * The identity must have * a phone number — otherwise the server returns 422. * * @throws {DuplicateContactRuleError} 409 when a non-deleted rule with @@ -116,15 +114,13 @@ export class PhoneIdentityContactRulesResource { return parsePhoneIdentityContactRule(data); } - /** Update `action` or `status` (admin-only). */ + /** Update `action` (admin-only). */ async update( agentHandle: string, ruleId: string, options: UpdatePhoneIdentityContactRuleOptions, ): Promise { - const body: Record = {}; - if (options.action !== undefined) body.action = options.action; - if (options.status !== undefined) body.status = options.status; + const body = { action: options.action }; const data = await this.http.patch( rulePath(agentHandle, ruleId), body, diff --git a/sdk/typescript/src/version.ts b/sdk/typescript/src/version.ts index 699d14ea..feb45d28 100644 --- a/sdk/typescript/src/version.ts +++ b/sdk/typescript/src/version.ts @@ -1,2 +1,2 @@ // Keep in sync with package.json "version". -export const VERSION = "0.5.4"; +export const VERSION = "0.5.5"; diff --git a/sdk/typescript/tests/contactRules.test.ts b/sdk/typescript/tests/contactRules.test.ts index 43ab29c6..5995c419 100644 --- a/sdk/typescript/tests/contactRules.test.ts +++ b/sdk/typescript/tests/contactRules.test.ts @@ -79,18 +79,19 @@ describe("MailContactRulesResource", () => { }); }); - it("update only sends supplied fields", async () => { + it("update sends action and still parses a paused response", async () => { vi.mocked(fetch).mockResolvedValue(ok({ ...MAIL_RULE_DICT, status: "paused" })); const http = new HttpTransport("k", BASE); const resource = new MailContactRulesResource(http); - await resource.update("box@inkbox.ai", "aaaa1111-0000-0000-0000-000000000011", { - status: ContactRuleStatus.PAUSED, + const rule = await resource.update("box@inkbox.ai", "aaaa1111-0000-0000-0000-000000000011", { + action: MailRuleAction.BLOCK, }); const call = vi.mocked(fetch).mock.calls[0][1] as RequestInit; const body = JSON.parse(call.body as string); - expect(body).toEqual({ status: "paused" }); + expect(body).toEqual({ action: "block" }); + expect(rule.status).toBe(ContactRuleStatus.PAUSED); }); it("listAll hits /contact-rules with mailboxId param", async () => { diff --git a/sdk/typescript/tests/identity-scoped-features.test.ts b/sdk/typescript/tests/identity-scoped-features.test.ts index 77d73194..8d5060f0 100644 --- a/sdk/typescript/tests/identity-scoped-features.test.ts +++ b/sdk/typescript/tests/identity-scoped-features.test.ts @@ -17,7 +17,6 @@ import { parseAgentIdentitySummary, } from "../src/identities/types.js"; import { - ContactRuleStatus, FilterMode, MailIdentityContactRule, MailRuleAction, @@ -153,10 +152,10 @@ describe("MailIdentityContactRulesResource", () => { `/identities/my-agent/mail-contact-rules/${rid}`, ); - await resource.update("my-agent", rid, { status: ContactRuleStatus.PAUSED }); + await resource.update("my-agent", rid, { action: MailRuleAction.ALLOW }); expect(http.patch).toHaveBeenCalledWith( `/identities/my-agent/mail-contact-rules/${rid}`, - { status: "paused" }, + { action: "allow" }, ); await resource.delete("my-agent", rid); @@ -458,12 +457,12 @@ describe("AgentIdentity contact-rule delegation", () => { parseMailIdentityContactRule(MAIL_RULE_DICT), ); - await identity.updateMailContactRule("rid", { status: ContactRuleStatus.PAUSED }); + await identity.updateMailContactRule("rid", { action: MailRuleAction.ALLOW }); expect(inkbox._mailIdentityContactRules.update).toHaveBeenCalledWith( identity.agentHandle, "rid", - { status: ContactRuleStatus.PAUSED }, + { action: MailRuleAction.ALLOW }, ); }); @@ -506,7 +505,7 @@ describe("AgentIdentity contact-rule delegation", () => { ).rejects.toThrow(/no phone number/); await expect(identity.getPhoneContactRule("rid")).rejects.toThrow(InkboxError); await expect( - identity.updatePhoneContactRule("rid", { status: ContactRuleStatus.PAUSED }), + identity.updatePhoneContactRule("rid", { action: PhoneRuleAction.BLOCK }), ).rejects.toThrow(InkboxError); await expect(identity.deletePhoneContactRule("rid")).rejects.toThrow(InkboxError); expect(inkbox._phoneIdentityContactRules.create).not.toHaveBeenCalled(); diff --git a/sdk/typescript/tests/imessage.test.ts b/sdk/typescript/tests/imessage.test.ts index 01dd5d04..a307060e 100644 --- a/sdk/typescript/tests/imessage.test.ts +++ b/sdk/typescript/tests/imessage.test.ts @@ -658,16 +658,16 @@ describe("IMessageContactRulesResource", () => { expect(rule.agentIdentityId).toBe(IDENTITY_ID); }); - it("update patches action/status", async () => { + it("update patches action", async () => { vi.mocked(fetch).mockResolvedValue(ok(CONTACT_RULE_DICT)); const resource = new IMessageContactRulesResource(new HttpTransport("k", BASE)); - await resource.update(HANDLE, RULE_ID, { status: ContactRuleStatus.PAUSED }); + await resource.update(HANDLE, RULE_ID, { action: IMessageRuleAction.ALLOW }); const { url, init } = lastCall(); expect(url).toBe(`${BASE}/identities/${HANDLE}/contact-rules/${RULE_ID}`); expect(init.method).toBe("PATCH"); - expect(JSON.parse(init.body as string)).toEqual({ status: "paused" }); + expect(JSON.parse(init.body as string)).toEqual({ action: "allow" }); }); it("delete targets the rule path", async () => { diff --git a/skills/inkbox-cli/SKILL.md b/skills/inkbox-cli/SKILL.md index 20265b1e..5f0042fa 100644 --- a/skills/inkbox-cli/SKILL.md +++ b/skills/inkbox-cli/SKILL.md @@ -114,7 +114,6 @@ inkbox identity update [--new-handle ] [--display-name ] [--description | --clear-description] [--mail-filter-mode whitelist|blacklist] [--phone-filter-mode whitelist|blacklist] - [--status active|paused] inkbox identity refresh ``` @@ -175,7 +174,7 @@ inkbox identity mail-rules list [--action allow|block] [--match-type ex inkbox identity mail-rules list-all [--agent-identity-id ] [--action …] [--match-type …] # admin-only, org-wide inkbox identity mail-rules get inkbox identity mail-rules create --action allow|block --match-type exact_email|domain --match-target -inkbox identity mail-rules update [--action allow|block] [--status active|paused] # admin-only +inkbox identity mail-rules update --action allow|block # admin-only inkbox identity mail-rules delete # admin-only # Phone rules — require the identity to have a phone number; only exact_number is supported. @@ -183,11 +182,11 @@ inkbox identity phone-rules list [--action allow|block] [--match-type e inkbox identity phone-rules list-all [--agent-identity-id ] [--action …] # admin-only, org-wide inkbox identity phone-rules get inkbox identity phone-rules create --action allow|block --match-target [--match-type exact_number] -inkbox identity phone-rules update [--action allow|block] [--status active|paused] # admin-only +inkbox identity phone-rules update --action allow|block # admin-only inkbox identity phone-rules delete # admin-only ``` -New rules always start active; use `update --status paused` to pause one. These replace the deprecated `inkbox mailbox rules` / `inkbox number rules` groups below. +New rules always start active. These replace the deprecated `inkbox mailbox rules` / `inkbox number rules` groups below. ### Identity Signing Key @@ -333,7 +332,7 @@ inkbox imessage upload-media ./photo.jpg -i --content-type image/jpeg # Contact rules are scoped to the identity (not a phone number): inkbox imessage contact-rule list -i inkbox imessage contact-rule create -i --action block --match-target +15559999999 -inkbox imessage contact-rule update -i --status paused # admin-only +inkbox imessage contact-rule update -i --action allow|block # admin-only inkbox imessage contact-rule delete -i # admin-only inkbox imessage contact-rule list-all # admin-only, org-wide ``` @@ -479,8 +478,8 @@ inkbox domain set-default inkbox mailbox rules list --mailbox [--action allow|block] [--match-type exact_email|domain] [--limit ] [--offset ] inkbox mailbox rules list --all-mailboxes [--mailbox-id ] [--action …] [--match-type …] # admin-only inkbox mailbox rules get --mailbox -inkbox mailbox rules create --mailbox --action allow|block --match-type exact_email|domain --match-target [--status active|paused] -inkbox mailbox rules update --mailbox [--action allow|block] [--status active|paused] # admin-only +inkbox mailbox rules create --mailbox --action allow|block --match-type exact_email|domain --match-target +inkbox mailbox rules update --mailbox --action allow|block # admin-only inkbox mailbox rules delete --mailbox # admin-only ``` @@ -504,8 +503,8 @@ Use `--state` only when provisioning a local number. Phone-number rows also carr inkbox number rules list --number [--action allow|block] [--match-type exact_number] [--limit ] [--offset ] inkbox number rules list --all-numbers [--phone-number-id ] [--action …] [--match-type …] # admin-only inkbox number rules get --number -inkbox number rules create --number --action allow|block --match-target [--match-type exact_number] [--status active|paused] -inkbox number rules update --number [--action allow|block] [--status active|paused] # admin-only +inkbox number rules create --number --action allow|block --match-target [--match-type exact_number] +inkbox number rules update --number --action allow|block # admin-only inkbox number rules delete --number # admin-only ``` diff --git a/skills/inkbox-python/SKILL.md b/skills/inkbox-python/SKILL.md index ad00b817..540cfc11 100644 --- a/skills/inkbox-python/SKILL.md +++ b/skills/inkbox-python/SKILL.md @@ -83,7 +83,6 @@ identity = inkbox.get_identity("sales-agent") identities = inkbox.list_identities() # → list[AgentIdentitySummary] identity.update(new_handle="new-name") # rename -identity.update(status="paused") # or "active" identity.refresh() # re-fetch from API, updates cached channels identity.delete() # cascades: mailbox + tunnel + phone-number release ``` @@ -506,7 +505,7 @@ rule = inkbox.imessage_contact_rules.create( "my-agent", action=IMessageRuleAction.BLOCK, match_target="+15559999999", ) rules = inkbox.imessage_contact_rules.list("my-agent") -inkbox.imessage_contact_rules.update("my-agent", rule.id, status="paused") # admin-only +inkbox.imessage_contact_rules.update("my-agent", rule.id, action="allow") # admin-only inkbox.imessage_contact_rules.delete("my-agent", rule.id) # admin-only all_rules = inkbox.imessage_contact_rules.list_all() # admin-only, org-wide ``` @@ -813,7 +812,7 @@ from inkbox import ( identity = inkbox.get_identity("sales-agent") # Mail rules via the identity convenience methods. New rules always start -# active; call `update(..., status="paused")` afterwards to pause one. +# active. rule = identity.create_mail_contact_rule( action=MailRuleAction.ALLOW, # or BLOCK match_type=MailRuleMatchType.DOMAIN, # or EXACT_EMAIL @@ -821,7 +820,7 @@ rule = identity.create_mail_contact_rule( ) identity.list_mail_contact_rules() identity.get_mail_contact_rule(rule.id) -identity.update_mail_contact_rule(rule.id, status="paused") # admin-only +identity.update_mail_contact_rule(rule.id, action="allow") # admin-only identity.delete_mail_contact_rule(rule.id) # admin-only # Phone rules — same shape, only match_type="exact_number" is supported. diff --git a/skills/inkbox-ts/SKILL.md b/skills/inkbox-ts/SKILL.md index a64e43b1..ff046c63 100644 --- a/skills/inkbox-ts/SKILL.md +++ b/skills/inkbox-ts/SKILL.md @@ -82,7 +82,6 @@ const identity = await inkbox.getIdentity("sales-agent"); const identities = await inkbox.listIdentities(); // AgentIdentitySummary[] await identity.update({ newHandle: "new-name" }); // rename -await identity.update({ status: "paused" }); // or "active" await identity.refresh(); // re-fetch from API, updates cached channels await identity.delete(); // cascades: mailbox + tunnel + phone-number release ``` @@ -488,7 +487,9 @@ const rule = await inkbox.imessageContactRules.create("my-agent", { matchTarget: "+15559999999", }); const rules = await inkbox.imessageContactRules.list("my-agent"); -await inkbox.imessageContactRules.update("my-agent", rule.id, { status: "paused" }); // admin-only +await inkbox.imessageContactRules.update("my-agent", rule.id, { + action: IMessageRuleAction.ALLOW, +}); // admin-only await inkbox.imessageContactRules.delete("my-agent", rule.id); // admin-only const allRules = await inkbox.imessageContactRules.listAll(); // admin-only, org-wide ``` @@ -800,8 +801,7 @@ import { const identity = await inkbox.getIdentity("sales-agent"); -// Mail rules via the identity convenience methods. New rules always start -// active; call `update(..., { status: "paused" })` afterwards to pause one. +// Mail rules via the identity convenience methods. const rule = await identity.createMailContactRule({ action: MailRuleAction.ALLOW, // or BLOCK matchType: MailRuleMatchType.DOMAIN, // or EXACT_EMAIL @@ -809,7 +809,9 @@ const rule = await identity.createMailContactRule({ }); await identity.listMailContactRules(); await identity.getMailContactRule(rule.id); -await identity.updateMailContactRule(rule.id, { status: "paused" }); // admin-only +await identity.updateMailContactRule(rule.id, { + action: MailRuleAction.ALLOW, +}); // admin-only await identity.deleteMailContactRule(rule.id); // admin-only // Phone rules — same shape, only matchType: "exact_number" is supported. @@ -823,7 +825,9 @@ await identity.listPhoneContactRules(); // Equivalent org-level resources, keyed by agentHandle, with an org-wide listAll: await inkbox.mailIdentityContactRules.create("sales-agent", { - action: "allow", matchType: "domain", matchTarget: "example.com", + action: MailRuleAction.ALLOW, + matchType: MailRuleMatchType.DOMAIN, + matchTarget: "example.com", }); await inkbox.mailIdentityContactRules.list("sales-agent"); await inkbox.mailIdentityContactRules.listAll({ agentIdentityId: identity.id }); // admin-only, org-wide @@ -832,7 +836,9 @@ await inkbox.phoneIdentityContactRules.listAll(); // Duplicate (matchType, matchTarget) on the same identity throws 409: try { await identity.createMailContactRule({ - action: "allow", matchType: "domain", matchTarget: "example.com", + action: MailRuleAction.ALLOW, + matchType: MailRuleMatchType.DOMAIN, + matchTarget: "example.com", }); } catch (e) { if (e instanceof DuplicateContactRuleError) { @@ -860,14 +866,25 @@ The legacy per-mailbox `inkbox.mailContactRules` and per-number (Sunset 2026-08-31). Prefer the identity-keyed surface above. ```typescript +import { + MailRuleAction, + MailRuleMatchType, + PhoneRuleAction, + PhoneRuleMatchType, +} from "@inkbox/sdk"; + // Deprecated — per-mailbox mail rule: await inkbox.mailContactRules.create(mailbox.emailAddress, { - action: "allow", matchType: "domain", matchTarget: "example.com", + action: MailRuleAction.ALLOW, + matchType: MailRuleMatchType.DOMAIN, + matchTarget: "example.com", }); await inkbox.mailContactRules.listAll({ mailboxId: mailbox.id }); // Deprecated — per-number phone rule: await inkbox.phoneContactRules.create(num.id, { - action: "block", matchType: "exact_number", matchTarget: "+15551234567", + action: PhoneRuleAction.BLOCK, + matchType: PhoneRuleMatchType.EXACT_NUMBER, + matchTarget: "+15551234567", }); ```