diff --git a/packages/cli/e2e/__tests__/help.spec.ts b/packages/cli/e2e/__tests__/help.spec.ts index 5b912a187..58aceef14 100644 --- a/packages/cli/e2e/__tests__/help.spec.ts +++ b/packages/cli/e2e/__tests__/help.spec.ts @@ -64,6 +64,7 @@ describe('help', () => { incidents Create and manage status page incidents. login Login to your Checkly account or create a new one. logout Log out and clear any local credentials. + members List account members and pending invites. rca Trigger and retrieve root cause analyses. rules Generate a rules file to use with AI IDEs and Copilots. runtimes List all supported runtimes and dependencies. diff --git a/packages/cli/package.json b/packages/cli/package.json index 424c4dd56..04b623f3d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -83,6 +83,9 @@ "import": { "description": "Import existing resources from your Checkly account to your project." }, + "members": { + "description": "List and manage members in your Checkly account." + }, "rca": { "description": "Trigger and retrieve root cause analyses." }, diff --git a/packages/cli/src/ai-context/context.ts b/packages/cli/src/ai-context/context.ts index 7f14e5c7e..8596ecebd 100644 --- a/packages/cli/src/ai-context/context.ts +++ b/packages/cli/src/ai-context/context.ts @@ -74,7 +74,7 @@ export const MANAGE_REFERENCES = [ }, { id: 'manage-account-members', - description: 'List account members and pending invites (`account members`)', + description: 'List account members and pending invites (`members`), update member roles, and delete members', }, ] as const diff --git a/packages/cli/src/ai-context/references/manage-account-members.md b/packages/cli/src/ai-context/references/manage-account-members.md index 9fc99437b..a12b90ae1 100644 --- a/packages/cli/src/ai-context/references/manage-account-members.md +++ b/packages/cli/src/ai-context/references/manage-account-members.md @@ -1,17 +1,20 @@ # Account Members -List active account members and pending or expired account invites. +List active account members and pending or expired account invites. Update active member roles and delete active members. ## Usage ```bash -npx checkly account members -npx checkly account members --output json -npx checkly account members --search alice -npx checkly account members --type invite --status pending -npx checkly account members --role admin -npx checkly account members --limit 25 -npx checkly account members --hide-id +npx checkly members +npx checkly members --output json +npx checkly members --search alice +npx checkly members --type invite --status pending +npx checkly members --role admin +npx checkly members --limit 25 +npx checkly members --hide-id +npx checkly members update alice@example.com --role read_run +npx checkly members update --role admin --id --force +npx checkly members delete alice@example.com --force ``` Flags: @@ -24,6 +27,21 @@ Flags: - `-o, --output ` — `table` (default), `json`, or `md`. - `--hide-id` — hide member and invite IDs in table output. +Role update: +- `checkly members update --role ` updates an active account member role. +- `` can be an email address or user ID. Values containing `@` are resolved as exact member emails by default; other values are treated as user IDs. +- Use `--email` or `--id` to force how `` is interpreted. +- Valid update roles are `admin`, `read_write`, `read_run`, and `read_only` (case-insensitive). +- `owner` cannot be set through this command. +- This mutation requires confirmation. In non-interactive mode, rerun with `--force` after reviewing the confirmation preview. + +Delete member: +- `checkly members delete ` removes an active account member. +- `` can be an email address or user ID. Values containing `@` are resolved as exact member emails by default; other values are treated as user IDs. +- Use `--email` or `--id` to force how `` is interpreted. +- This destructive mutation requires confirmation. In non-interactive mode, rerun with `--force` after reviewing the confirmation preview. +- Pending invite cancellation is not handled by this command. + ## JSON response shape ```json diff --git a/packages/cli/src/commands/__tests__/account-members-mutations.spec.ts b/packages/cli/src/commands/__tests__/account-members-mutations.spec.ts new file mode 100644 index 000000000..2634f99a6 --- /dev/null +++ b/packages/cli/src/commands/__tests__/account-members-mutations.spec.ts @@ -0,0 +1,275 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +vi.mock('../../helpers/cli-mode', () => ({ + detectCliMode: vi.fn(() => 'agent'), +})) + +vi.mock('../../rest/api', () => ({ + accountMembers: { getAll: vi.fn(), updateRole: vi.fn(), delete: vi.fn() }, + validateAuthentication: vi.fn().mockResolvedValue({ name: 'Test Account' }), +})) + +vi.mock('prompts', () => ({ + default: vi.fn(() => Promise.resolve({ confirm: true })), +})) + +import { detectCliMode } from '../../helpers/cli-mode.js' +import * as api from '../../rest/api.js' +import { AuthCommand } from '../authCommand.js' +import MembersUpdate, { normalizeAccountMemberUpdateRole } from '../members/update.js' +import MembersDelete from '../members/delete.js' + +const updatedMember = { + type: 'member' as const, + accountId: '11111111-1111-1111-1111-111111111111', + userId: '22222222-2222-2222-2222-222222222222', + name: 'Ada Admin', + email: 'ada@example.com', + role: 'ADMIN' as const, + status: 'ACTIVE' as const, + createdAt: '2026-01-01T00:00:00.000Z', + updatedAt: '2026-01-02T00:00:00.000Z', + isSupportMembership: false, + ssoEnabled: false, + mfaEnabled: true, +} + +function createCommandContext (Command: typeof AuthCommand, parsed: unknown) { + const logged: string[] = [] + let exitCodeValue: number | undefined + return { + parse: vi.fn().mockResolvedValue(parsed), + error: vi.fn((message: string) => { + throw new Error(message) + }), + log: vi.fn((msg?: string) => { + if (msg) logged.push(msg) + }), + exit: vi.fn((code: number) => { + exitCodeValue = code + throw new Error(`EXIT_${code}`) + }), + confirmOrAbort: AuthCommand.prototype.confirmOrAbort, + style: { + outputFormat: undefined, + shortSuccess: vi.fn(), + longError: vi.fn(), + }, + constructor: Command, + logged, + get exitCodeValue () { + return exitCodeValue + }, + } +} + +describe('account members mutation commands', () => { + beforeEach(() => { + vi.clearAllMocks() + process.exitCode = undefined + vi.mocked(api.accountMembers.getAll).mockResolvedValue({ + data: { members: [updatedMember], length: 1, nextId: null }, + } as any) + vi.mocked(api.accountMembers.updateRole).mockResolvedValue({ data: updatedMember } as any) + vi.mocked(api.accountMembers.delete).mockResolvedValue({} as any) + }) + + it('normalizes update role values and excludes owner', () => { + expect(normalizeAccountMemberUpdateRole('admin')).toBe('ADMIN') + expect(normalizeAccountMemberUpdateRole('Read_Run')).toBe('READ_RUN') + expect(normalizeAccountMemberUpdateRole(' read_only ')).toBe('READ_ONLY') + expect(normalizeAccountMemberUpdateRole('owner')).toBeUndefined() + expect(normalizeAccountMemberUpdateRole('superadmin')).toBeUndefined() + }) + + it('members update exits 2 in agent mode without --force', async () => { + vi.mocked(detectCliMode).mockReturnValue('agent') + const ctx = createCommandContext(MembersUpdate, { + args: { member: updatedMember.userId }, + flags: { + 'role': 'admin', + 'email': false, + 'id': false, + 'output': 'table', + 'force': false, + 'dry-run': false, + }, + }) + + await expect( + MembersUpdate.prototype.run.call(ctx as any), + ).rejects.toThrow('EXIT_2') + + const output = JSON.parse(ctx.logged[0]) + expect(output.status).toBe('confirmation_required') + expect(output.command).toBe('members update') + expect(output.confirmCommand).toContain('--force') + expect(output.confirmCommand).toContain(updatedMember.userId) + expect(api.accountMembers.updateRole).not.toHaveBeenCalled() + }) + + it('members update executes with --force in agent mode', async () => { + vi.mocked(detectCliMode).mockReturnValue('agent') + const ctx = createCommandContext(MembersUpdate, { + args: { member: updatedMember.userId }, + flags: { + 'role': 'read_run', + 'email': false, + 'id': false, + 'output': 'json', + 'force': true, + 'dry-run': false, + }, + }) + + await MembersUpdate.prototype.run.call(ctx as any) + + expect(api.accountMembers.updateRole).toHaveBeenCalledWith(updatedMember.userId, 'READ_RUN') + expect(JSON.parse(ctx.logged[0])).toMatchObject({ + userId: updatedMember.userId, + email: updatedMember.email, + }) + }) + + it('members update resolves an email before updating', async () => { + vi.mocked(detectCliMode).mockReturnValue('agent') + const ctx = createCommandContext(MembersUpdate, { + args: { member: updatedMember.email.toUpperCase() }, + flags: { + 'role': 'read_only', + 'email': false, + 'id': false, + 'output': 'json', + 'force': true, + 'dry-run': false, + }, + }) + + await MembersUpdate.prototype.run.call(ctx as any) + + expect(api.accountMembers.getAll).toHaveBeenCalledWith({ + search: updatedMember.email.toUpperCase(), + type: 'member', + status: 'ACTIVE', + }) + expect(api.accountMembers.updateRole).toHaveBeenCalledWith(updatedMember.userId, 'READ_ONLY') + }) + + it('members update can force email-looking values to be treated as IDs', async () => { + vi.mocked(detectCliMode).mockReturnValue('agent') + const ctx = createCommandContext(MembersUpdate, { + args: { member: 'user@example.com' }, + flags: { + 'role': 'read_only', + 'email': false, + 'id': true, + 'output': 'json', + 'force': true, + 'dry-run': false, + }, + }) + + await MembersUpdate.prototype.run.call(ctx as any) + + expect(api.accountMembers.getAll).not.toHaveBeenCalled() + expect(api.accountMembers.updateRole).toHaveBeenCalledWith('user@example.com', 'READ_ONLY') + }) + + it('members update rejects owner role locally', async () => { + const ctx = createCommandContext(MembersUpdate, { + args: { member: updatedMember.userId }, + flags: { + 'role': 'owner', + 'email': false, + 'id': false, + 'output': 'table', + 'force': true, + 'dry-run': false, + }, + }) + + await expect( + MembersUpdate.prototype.run.call(ctx as any), + ).rejects.toThrow('Invalid --role "owner"') + + expect(api.accountMembers.updateRole).not.toHaveBeenCalled() + }) + + it('members delete exits 2 in agent mode without --force', async () => { + vi.mocked(detectCliMode).mockReturnValue('agent') + const ctx = createCommandContext(MembersDelete, { + args: { member: updatedMember.userId }, + flags: { + 'email': false, + 'id': false, + 'force': false, + 'dry-run': false, + }, + }) + + await expect( + MembersDelete.prototype.run.call(ctx as any), + ).rejects.toThrow('EXIT_2') + + const output = JSON.parse(ctx.logged[0]) + expect(output.status).toBe('confirmation_required') + expect(output.command).toBe('members delete') + expect(output.classification.destructive).toBe(true) + expect(output.confirmCommand).toContain('--force') + expect(output.confirmCommand).toContain(updatedMember.userId) + expect(api.accountMembers.delete).not.toHaveBeenCalled() + }) + + it('members delete executes with --force in agent mode', async () => { + vi.mocked(detectCliMode).mockReturnValue('agent') + const ctx = createCommandContext(MembersDelete, { + args: { member: updatedMember.userId }, + flags: { + 'email': false, + 'id': false, + 'force': true, + 'dry-run': false, + }, + }) + + await MembersDelete.prototype.run.call(ctx as any) + + expect(api.accountMembers.delete).toHaveBeenCalledWith(updatedMember.userId) + }) + + it('members delete resolves an email before showing the confirmation preview', async () => { + vi.mocked(detectCliMode).mockReturnValue('agent') + const ctx = createCommandContext(MembersDelete, { + args: { member: updatedMember.email }, + flags: { + 'email': false, + 'id': false, + 'force': false, + 'dry-run': false, + }, + }) + + await expect( + MembersDelete.prototype.run.call(ctx as any), + ).rejects.toThrow('EXIT_2') + + const output = JSON.parse(ctx.logged[0]) + expect(api.accountMembers.getAll).toHaveBeenCalledWith({ + search: updatedMember.email, + type: 'member', + status: 'ACTIVE', + }) + expect(output.changes[0]).toContain(updatedMember.email) + expect(output.changes[0]).toContain(updatedMember.userId) + expect(api.accountMembers.delete).not.toHaveBeenCalled() + }) + + it('has correct metadata', () => { + expect(MembersUpdate.readOnly).toBe(false) + expect(MembersUpdate.destructive).toBe(false) + expect(MembersUpdate.idempotent).toBe(true) + expect(MembersDelete.readOnly).toBe(false) + expect(MembersDelete.destructive).toBe(true) + expect(MembersDelete.idempotent).toBe(true) + }) +}) diff --git a/packages/cli/src/commands/__tests__/account-members.spec.ts b/packages/cli/src/commands/__tests__/account-members.spec.ts index 76c0de4ad..bd781721e 100644 --- a/packages/cli/src/commands/__tests__/account-members.spec.ts +++ b/packages/cli/src/commands/__tests__/account-members.spec.ts @@ -3,7 +3,7 @@ import { normalizeAccountMemberRole, normalizeAccountMemberStatus, normalizeAccountMemberType, -} from '../account/members.js' +} from '../members.js' describe('account members flag normalization', () => { it('normalizes type values case-insensitively', () => { diff --git a/packages/cli/src/commands/__tests__/command-metadata.spec.ts b/packages/cli/src/commands/__tests__/command-metadata.spec.ts index e336f6513..5582371c7 100644 --- a/packages/cli/src/commands/__tests__/command-metadata.spec.ts +++ b/packages/cli/src/commands/__tests__/command-metadata.spec.ts @@ -35,13 +35,17 @@ import PwTest from '../pw-test.js' import SyncPlaywright from '../sync-playwright.js' import SkillsInstall from '../skills/install.js' import AccountPlan from '../account/plan.js' -import AccountMembers from '../account/members.js' +import Members from '../members.js' +import MembersUpdate from '../members/update.js' +import MembersDelete from '../members/delete.js' import Api from '../api.js' import TestSessionsGet from '../test-sessions/get.js' const commands: Array<[string, typeof BaseCommand]> = [ ['api', Api], - ['account members', AccountMembers], + ['members', Members], + ['members update', MembersUpdate], + ['members delete', MembersDelete], ['account plan', AccountPlan], ['checks list', ChecksList], ['checks get', ChecksGet], diff --git a/packages/cli/src/commands/account/members.ts b/packages/cli/src/commands/members.ts similarity index 94% rename from packages/cli/src/commands/account/members.ts rename to packages/cli/src/commands/members.ts index d0a1bfd4f..038ad1636 100644 --- a/packages/cli/src/commands/account/members.ts +++ b/packages/cli/src/commands/members.ts @@ -1,19 +1,19 @@ import { Flags } from '@oclif/core' -import { AuthCommand } from '../authCommand.js' -import { outputFlag } from '../../helpers/flags.js' -import * as api from '../../rest/api.js' -import type { OutputFormat } from '../../formatters/render.js' +import { AuthCommand } from './authCommand.js' +import { outputFlag } from '../helpers/flags.js' +import * as api from '../rest/api.js' +import type { OutputFormat } from '../formatters/render.js' import type { AccountMemberRole, AccountMemberStatus, AccountMemberType, AccountMembersListParams, -} from '../../rest/account-members.js' +} from '../rest/account-members.js' import { formatAccountMembers, formatCursorNavigationHints, formatCursorPaginationInfo, -} from '../../formatters/account-members.js' +} from '../formatters/account-members.js' const accountMemberTypes = ['member', 'invite'] as const const accountMemberRoles = ['OWNER', 'ADMIN', 'READ_WRITE', 'READ_RUN', 'READ_ONLY'] as const @@ -53,6 +53,7 @@ export function normalizeAccountMemberStatus (value: string | undefined): Accoun export default class AccountMembers extends AuthCommand { static hidden = false + static hiddenAliases = ['account members'] static readOnly = true static idempotent = true static description = 'List account members and pending invites.' diff --git a/packages/cli/src/commands/members/delete.ts b/packages/cli/src/commands/members/delete.ts new file mode 100644 index 000000000..3861e19ef --- /dev/null +++ b/packages/cli/src/commands/members/delete.ts @@ -0,0 +1,77 @@ +import { Args, Flags } from '@oclif/core' +import { AuthCommand } from '../authCommand.js' +import { dryRunFlag, forceFlag } from '../../helpers/flags.js' +import * as api from '../../rest/api.js' +import { resolveAccountMemberTarget } from '../../helpers/account-member-target.js' + +export default class AccountMembersDelete extends AuthCommand { + static hidden = false + static hiddenAliases = ['account members delete'] + static destructive = true + static idempotent = true + static description = 'Delete an account member.' + + static args = { + member: Args.string({ + name: 'member', + required: true, + description: 'The account member email or user ID.', + }), + } + + static flags = { + 'email': Flags.boolean({ + description: 'Treat the member argument as an email address.', + default: false, + exclusive: ['id'], + }), + 'id': Flags.boolean({ + description: 'Treat the member argument as a user ID.', + default: false, + exclusive: ['email'], + }), + 'force': forceFlag(), + 'dry-run': dryRunFlag(), + } + + async run (): Promise { + const { args, flags } = await this.parse(AccountMembersDelete) + + let target + try { + target = await resolveAccountMemberTarget(args.member, flags) + } catch (err: any) { + this.style.longError('Failed to resolve account member.', err) + process.exitCode = 1 + return + } + const previewFlags: Record = { + ...flags, + email: flags.email || undefined, + id: flags.id || undefined, + } + + await this.confirmOrAbort({ + command: 'members delete', + description: 'Delete account member', + changes: [ + `Delete account member "${target.label}"`, + ], + flags: previewFlags, + args: { member: args.member }, + classification: { + readOnly: AccountMembersDelete.readOnly, + destructive: AccountMembersDelete.destructive, + idempotent: AccountMembersDelete.idempotent, + }, + }, { force: flags.force, dryRun: flags['dry-run'] }) + + try { + await api.accountMembers.delete(target.userId) + this.style.shortSuccess(`Account member "${target.label}" deleted.`) + } catch (err: any) { + this.style.longError('Failed to delete account member.', err) + process.exitCode = 1 + } + } +} diff --git a/packages/cli/src/commands/members/update.ts b/packages/cli/src/commands/members/update.ts new file mode 100644 index 000000000..21db06345 --- /dev/null +++ b/packages/cli/src/commands/members/update.ts @@ -0,0 +1,117 @@ +import { Args, Flags } from '@oclif/core' +import { AuthCommand } from '../authCommand.js' +import { dryRunFlag, forceFlag, outputFlag } from '../../helpers/flags.js' +import * as api from '../../rest/api.js' +import type { OutputFormat } from '../../formatters/render.js' +import { formatAccountMembers } from '../../formatters/account-members.js' +import type { AccountMemberUpdateRole } from '../../rest/account-members.js' +import { normalizeAccountMemberRole } from '../members.js' +import { resolveAccountMemberTarget } from '../../helpers/account-member-target.js' + +const accountMemberUpdateRoles = ['ADMIN', 'READ_WRITE', 'READ_RUN', 'READ_ONLY'] as const +const accountMemberUpdateRoleOptions = accountMemberUpdateRoles.map(role => role.toLowerCase()) + +function isAccountMemberUpdateRole (value: string): value is AccountMemberUpdateRole { + return accountMemberUpdateRoles.includes(value as AccountMemberUpdateRole) +} + +export function normalizeAccountMemberUpdateRole (value: string | undefined): AccountMemberUpdateRole | undefined { + const role = normalizeAccountMemberRole(value) + return role && isAccountMemberUpdateRole(role) ? role : undefined +} + +export default class AccountMembersUpdate extends AuthCommand { + static hidden = false + static hiddenAliases = ['account members update'] + static idempotent = true + static description = 'Update an account member role.' + + static args = { + member: Args.string({ + name: 'member', + required: true, + description: 'The account member email or user ID.', + }), + } + + static flags = { + 'role': Flags.string({ + char: 'r', + description: `New member role: ${accountMemberUpdateRoleOptions.join(', ')}.`, + required: true, + }), + 'email': Flags.boolean({ + description: 'Treat the member argument as an email address.', + default: false, + exclusive: ['id'], + }), + 'id': Flags.boolean({ + description: 'Treat the member argument as a user ID.', + default: false, + exclusive: ['email'], + }), + 'output': outputFlag({ default: 'table' }), + 'force': forceFlag(), + 'dry-run': dryRunFlag(), + } + + async run (): Promise { + const { args, flags } = await this.parse(AccountMembersUpdate) + this.style.outputFormat = flags.output + + const role = normalizeAccountMemberUpdateRole(flags.role) + if (!role) { + this.error(`Invalid --role "${flags.role}". Valid values: ${accountMemberUpdateRoleOptions.join(', ')}.`) + } + + let target + try { + target = await resolveAccountMemberTarget(args.member, flags) + } catch (err: any) { + this.style.longError('Failed to resolve account member.', err) + process.exitCode = 1 + return + } + const previewFlags: Record = { + ...flags, + email: flags.email || undefined, + id: flags.id || undefined, + } + + await this.confirmOrAbort({ + command: 'members update', + description: 'Update account member role', + changes: [ + `Update account member "${target.label}" role to ${role}`, + ], + flags: previewFlags, + args: { member: args.member }, + classification: { + readOnly: AccountMembersUpdate.readOnly, + destructive: AccountMembersUpdate.destructive, + idempotent: AccountMembersUpdate.idempotent, + }, + }, { force: flags.force, dryRun: flags['dry-run'] }) + + try { + const { data } = await api.accountMembers.updateRole(target.userId, role) + + if (flags.output === 'json') { + this.log(JSON.stringify(data, null, 2)) + return + } + + const fmt: OutputFormat = flags.output === 'md' ? 'md' : 'terminal' + if (fmt === 'md') { + this.log(formatAccountMembers([data], fmt)) + return + } + + this.style.shortSuccess(`Account member "${data.email}" updated.`) + this.log(formatAccountMembers([data], fmt)) + } catch (err: any) { + this.style.longError('Failed to update account member.', err) + process.exitCode = 1 + } + } +} diff --git a/packages/cli/src/formatters/account-members.ts b/packages/cli/src/formatters/account-members.ts index ecba2030c..f09e5c73c 100644 --- a/packages/cli/src/formatters/account-members.ts +++ b/packages/cli/src/formatters/account-members.ts @@ -22,7 +22,7 @@ export function formatCursorPaginationInfo (count: number, nextId: string | null export function formatCursorNavigationHints (nextId: string | null): string { if (!nextId) return '' - return ` ${chalk.dim('Next page:')} checkly account members --limit --next-id ${nextId}` + return ` ${chalk.dim('Next page:')} checkly members --limit --next-id ${nextId}` } function boolSymbol (value: boolean | undefined, format: OutputFormat): string { diff --git a/packages/cli/src/helpers/account-member-target.ts b/packages/cli/src/helpers/account-member-target.ts new file mode 100644 index 000000000..f08e6dc78 --- /dev/null +++ b/packages/cli/src/helpers/account-member-target.ts @@ -0,0 +1,58 @@ +import * as api from '../rest/api.js' +import type { ActiveAccountMember } from '../rest/account-members.js' + +type AccountMemberTargetKind = 'email' | 'id' + +export interface AccountMemberTargetFlags { + email?: boolean + id?: boolean +} + +export interface ResolvedAccountMemberTarget { + userId: string + label: string + member?: ActiveAccountMember + kind: AccountMemberTargetKind +} + +function inferTargetKind (target: string, flags: AccountMemberTargetFlags): AccountMemberTargetKind { + if (flags.email) return 'email' + if (flags.id) return 'id' + return target.includes('@') ? 'email' : 'id' +} + +export async function resolveAccountMemberTarget ( + target: string, + flags: AccountMemberTargetFlags = {}, +): Promise { + const kind = inferTargetKind(target, flags) + if (kind === 'id') { + return { userId: target, label: target, kind } + } + + const email = target.trim().toLowerCase() + const { data } = await api.accountMembers.getAll({ + search: target, + type: 'member', + status: 'ACTIVE', + }) + const exactMatches = data.members + .filter((member): member is ActiveAccountMember => member.type === 'member') + .filter(member => member.email.toLowerCase() === email) + + if (exactMatches.length === 0) { + throw new Error(`No active account member found with email "${target}".`) + } + + if (exactMatches.length > 1) { + throw new Error(`Multiple active account members found with email "${target}". Use --id with the member user ID instead.`) + } + + const member = exactMatches[0] + return { + userId: member.userId, + label: `${member.email} (${member.userId})`, + member, + kind, + } +} diff --git a/packages/cli/src/rest/account-members.ts b/packages/cli/src/rest/account-members.ts index 937e382b0..dc11fbdb2 100644 --- a/packages/cli/src/rest/account-members.ts +++ b/packages/cli/src/rest/account-members.ts @@ -49,6 +49,12 @@ export interface AccountMembersListParams { nextId?: string } +export type AccountMemberUpdateRole = Exclude + +export interface AccountMemberUpdateRolePayload { + role: AccountMemberUpdateRole +} + class AccountMembers { api: AxiosInstance constructor (api: AxiosInstance) { @@ -58,6 +64,15 @@ class AccountMembers { getAll (params?: AccountMembersListParams) { return this.api.get('/v1/accounts/me/members', { params }) } + + updateRole (userId: string, role: AccountMemberUpdateRole) { + const payload: AccountMemberUpdateRolePayload = { role } + return this.api.patch(`/v1/accounts/me/members/${userId}`, payload) + } + + delete (userId: string) { + return this.api.delete(`/v1/accounts/me/members/${userId}`) + } } export default AccountMembers