diff --git a/src/client/endpoints/applications.ts b/src/client/endpoints/applications.ts index 69410f7..034a2ca 100644 --- a/src/client/endpoints/applications.ts +++ b/src/client/endpoints/applications.ts @@ -581,6 +581,29 @@ export async function getProcessInstanceCount( ); } +export interface ExitCodePoint { + time: string; + value: string; + description: string | null; +} + +export interface ExitCodesQuery { + from: string; + to: string; +} + +export async function getProcessExitCodes( + client: ApiClient, + id: string, + processId: string, + query: ExitCodesQuery, +): Promise { + return client.get( + `${BASE}/${id}/processes/${processId}/metrics/exit-codes`, + query as unknown as QueryParams, + ); +} + // --------------------------------------------------------------------------- // Environment variables // --------------------------------------------------------------------------- diff --git a/src/client/endpoints/object-storage.ts b/src/client/endpoints/object-storage.ts index 4219688..1dbb120 100644 --- a/src/client/endpoints/object-storage.ts +++ b/src/client/endpoints/object-storage.ts @@ -172,3 +172,19 @@ export async function deleteObjects( ): Promise { return client.delete(`/object-storage/${id}/objects`, body); } + +// --------------------------------------------------------------------------- +// Credentials +// --------------------------------------------------------------------------- + +export interface RotateCredentialsBody { + old_keys_ttl_hours?: number; +} + +export async function rotateObjectStorageCredentials( + client: ApiClient, + id: string, + body?: RotateCredentialsBody, +): Promise { + return client.post(`/object-storage/${id}/rotate-credentials`, body ?? {}); +} diff --git a/src/client/endpoints/resources.ts b/src/client/endpoints/resources.ts index 018bc79..77d108f 100644 --- a/src/client/endpoints/resources.ts +++ b/src/client/endpoints/resources.ts @@ -31,6 +31,21 @@ export interface ProcessResourceType { is_available: boolean; } +export interface ApiKeyPermission { + id: string; + name: string; + description: string; + resource: string; + action: string; +} + +export interface ApiKeyRole { + id: string; + name: string; + description: string; + permissions: string[]; +} + // --------------------------------------------------------------------------- // Endpoints // --------------------------------------------------------------------------- @@ -48,3 +63,11 @@ export async function listDatabaseResourceTypes( export async function listProcessResourceTypes(client: ApiClient): Promise { return client.get('/resources/process-resource-types'); } + +export async function listApiKeyPermissions(client: ApiClient): Promise { + return client.get('/resources/rbac/api-key-permissions'); +} + +export async function listApiKeyRoles(client: ApiClient): Promise { + return client.get('/resources/rbac/api-key-roles'); +} diff --git a/src/client/endpoints/static-sites.ts b/src/client/endpoints/static-sites.ts index 27a60f4..a9c52d9 100644 --- a/src/client/endpoints/static-sites.ts +++ b/src/client/endpoints/static-sites.ts @@ -432,3 +432,18 @@ export async function getStaticSiteTopPages( export async function purgeStaticSiteCache(client: ApiClient, id: string): Promise { return client.post(`/static-sites/${id}/purge-cache`); } + +// --------------------------------------------------------------------------- +// Pretty URLs +// --------------------------------------------------------------------------- + +export interface PrettyUrlStatus { + is_turned_on: boolean; +} + +export async function toggleStaticSitePrettyUrl( + client: ApiClient, + id: string, +): Promise { + return client.post(`/static-sites/${id}/pretty-url/toggle`); +} diff --git a/src/client/endpoints/usage-alerts.ts b/src/client/endpoints/usage-alerts.ts new file mode 100644 index 0000000..a5a4be4 --- /dev/null +++ b/src/client/endpoints/usage-alerts.ts @@ -0,0 +1,77 @@ +import type { ApiClient } from '../api-client.ts'; +import type { PaginatedResponse, PaginationQuery, QueryParams } from '../types.ts'; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export interface UsageAlertTrigger { + id: string; + percentage: number; + last_fired_at: string | null; +} + +export interface UsageAlertConfig { + id: string; + company_id: string; + project_id: string | null; + limit_usd: number; + emails: string[]; + triggers: UsageAlertTrigger[]; + created_at: string; + updated_at: string; +} + +export interface CreateUsageAlertBody { + limit_usd: number; + emails: string[]; + triggers: Array<{ percentage: number }>; + project_id?: string | null; +} + +export interface UpdateUsageAlertBody { + limit_usd?: number; + emails?: string[]; + triggers?: Array<{ percentage: number }>; +} + +export interface ListUsageAlertsQuery extends PaginationQuery { + project_id?: string; +} + +// --------------------------------------------------------------------------- +// Endpoints +// --------------------------------------------------------------------------- + +export async function listUsageAlerts( + client: ApiClient, + query?: ListUsageAlertsQuery, +): Promise> { + return client.get>( + '/usage-alerts', + query as QueryParams | undefined, + ); +} + +export async function createUsageAlert( + client: ApiClient, + body: CreateUsageAlertBody, +): Promise { + return client.post('/usage-alerts', body); +} + +export async function getUsageAlert(client: ApiClient, id: string): Promise { + return client.get(`/usage-alerts/${id}`); +} + +export async function updateUsageAlert( + client: ApiClient, + id: string, + body: UpdateUsageAlertBody, +): Promise { + return client.patch(`/usage-alerts/${id}`, body); +} + +export async function deleteUsageAlert(client: ApiClient, id: string): Promise { + return client.delete(`/usage-alerts/${id}`); +} diff --git a/src/commands/apps/processes/metrics/exit-codes.ts b/src/commands/apps/processes/metrics/exit-codes.ts new file mode 100644 index 0000000..aa2ae36 --- /dev/null +++ b/src/commands/apps/processes/metrics/exit-codes.ts @@ -0,0 +1,57 @@ +import { Command } from 'commander'; +import { createClient } from '../../../../client/api-client.ts'; +import { handleError } from '../../../../errors/handler.ts'; +import { printJson, printTable } from '../../../../output/formatter.ts'; +import { startSpinner, stopSpinner } from '../../../../output/spinner.ts'; +import { jsonOption, resolveJsonMode } from '../../../../helpers/flags.ts'; +import { validateId } from '../../../../helpers/validate.ts'; +import { Option } from 'commander'; +import type { QueryParams } from '../../../../client/types.ts'; + +interface ExitCodePoint { + time: string; + value: string; + description: string | null; +} + +export const exitCodesCommand = new Command('exit-codes') + .description('Get process exit codes within a time range') + .argument('', 'app-id ID') + .argument('', 'process-id ID') + .addOption(jsonOption()) + .addOption(new Option('--from ', 'Start date (ISO 8601, default: 24h ago)')) + .addOption(new Option('--to ', 'End date (ISO 8601, default: now)')) + .action(async (appId: string, processId: string, opts: Record) => { + const client = createClient({ apiUrl: opts['apiUrl'] as string | undefined }); + const json = resolveJsonMode(opts); + try { + validateId(appId, 'app-id'); + validateId(processId, 'process-id'); + + const now = new Date(); + const dayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000); + const query: QueryParams = { + from: (opts['from'] as string | undefined) ?? dayAgo.toISOString(), + to: (opts['to'] as string | undefined) ?? now.toISOString(), + }; + + if (!json) startSpinner('Fetching exit codes...'); + const result = await client.get( + `/applications/${appId}/processes/${processId}/metrics/exit-codes`, + query, + ); + stopSpinner(); + if (json) { + printJson(result); + } else { + printTable(result, [ + { header: 'Time', key: 'time' }, + { header: 'Exit code', key: 'value' }, + { header: 'Description', key: 'description' }, + ]); + } + } catch (error) { + stopSpinner(); + handleError(error, json); + } + }); diff --git a/src/commands/apps/processes/metrics/index.ts b/src/commands/apps/processes/metrics/index.ts index 9ee063a..17a8fb3 100644 --- a/src/commands/apps/processes/metrics/index.ts +++ b/src/commands/apps/processes/metrics/index.ts @@ -4,6 +4,7 @@ import { cpuLimitCommand } from './cpu-limit.ts'; import { memoryUsageCommand } from './memory-usage.ts'; import { memoryLimitCommand } from './memory-limit.ts'; import { instanceCountCommand } from './instance-count.ts'; +import { exitCodesCommand } from './exit-codes.ts'; export function makeProcessMetricsCommands(): Command { const cmd = new Command('metrics').description('Process metrics'); @@ -12,5 +13,6 @@ export function makeProcessMetricsCommands(): Command { cmd.addCommand(memoryUsageCommand); cmd.addCommand(memoryLimitCommand); cmd.addCommand(instanceCountCommand); + cmd.addCommand(exitCodesCommand); return cmd; } diff --git a/src/commands/object-storage/index.ts b/src/commands/object-storage/index.ts index abecd16..1bc5c66 100644 --- a/src/commands/object-storage/index.ts +++ b/src/commands/object-storage/index.ts @@ -4,6 +4,7 @@ import { objectStorageGetCommand } from './get.ts'; import { objectStorageCreateCommand } from './create.ts'; import { objectStorageUpdateCommand } from './update.ts'; import { objectStorageDeleteCommand } from './delete.ts'; +import { objectStorageRotateCredentialsCommand } from './rotate-credentials.ts'; import { makeCdnDomainCommands } from './cdn-domain/index.ts'; import { makeCorsPoliciesCommands } from './cors-policies/index.ts'; import { makeObjectsCommands } from './objects/index.ts'; @@ -15,6 +16,7 @@ export function makeObjectStorageCommand(): Command { cmd.addCommand(objectStorageCreateCommand); cmd.addCommand(objectStorageUpdateCommand); cmd.addCommand(objectStorageDeleteCommand); + cmd.addCommand(objectStorageRotateCredentialsCommand); cmd.addCommand(makeCdnDomainCommands()); cmd.addCommand(makeCorsPoliciesCommands()); cmd.addCommand(makeObjectsCommands()); diff --git a/src/commands/object-storage/rotate-credentials.ts b/src/commands/object-storage/rotate-credentials.ts new file mode 100644 index 0000000..a8fc3ad --- /dev/null +++ b/src/commands/object-storage/rotate-credentials.ts @@ -0,0 +1,32 @@ +import { makeActionCommand } from '../../helpers/command-factory.ts'; + +export const objectStorageRotateCredentialsCommand = makeActionCommand>({ + name: 'rotate-credentials', + description: 'Rotate access and secret keys for an object storage bucket', + options: [ + { + flags: '--old-keys-ttl-hours ', + description: + 'Keep previous keys valid for this many hours (0-168). 0 invalidates immediately. Defaults to 0.', + }, + ], + apiCall: (client, id, opts) => { + const body: Record = {}; + if (opts['oldKeysTtlHours'] !== undefined) { + body['old_keys_ttl_hours'] = Number(opts['oldKeysTtlHours']); + } + return client.post(`/object-storage/${id}/rotate-credentials`, body); + }, + successMessage: + 'Credentials rotated. Capture the new access_key and secret_key from the response — they are not retrievable later.', + spinnerText: 'Rotating credentials...', + displayFields: (item) => ({ + ID: item['id'], + 'Display Name': item['display_name'], + 'New Access Key': item['access_key'], + 'New Secret Key': item['secret_key'], + 'Previous Access Key': item['old_access_key'], + 'Previous Secret Key': item['old_secret_key'], + 'Previous Keys Expire At': item['old_keys_expired_at'], + }), +}); diff --git a/src/commands/resources/index.ts b/src/commands/resources/index.ts index e7c731f..9860678 100644 --- a/src/commands/resources/index.ts +++ b/src/commands/resources/index.ts @@ -2,11 +2,15 @@ import { Command } from 'commander'; import { resourcesClustersCommand } from './clusters.ts'; import { resourcesDbTypesCommand } from './db-types.ts'; import { resourcesProcessTypesCommand } from './process-types.ts'; +import { resourcesRbacPermissionsCommand } from './rbac-permissions.ts'; +import { resourcesRbacRolesCommand } from './rbac-roles.ts'; export function makeResourcesCommand(): Command { const cmd = new Command('resources').description('List available resources'); cmd.addCommand(resourcesClustersCommand); cmd.addCommand(resourcesDbTypesCommand); cmd.addCommand(resourcesProcessTypesCommand); + cmd.addCommand(resourcesRbacPermissionsCommand); + cmd.addCommand(resourcesRbacRolesCommand); return cmd; } diff --git a/src/commands/resources/rbac-permissions.ts b/src/commands/resources/rbac-permissions.ts new file mode 100644 index 0000000..a394b8b --- /dev/null +++ b/src/commands/resources/rbac-permissions.ts @@ -0,0 +1,14 @@ +import { makeListCommand } from '../../helpers/command-factory.ts'; + +export const resourcesRbacPermissionsCommand = makeListCommand({ + name: 'rbac-permissions', + description: 'List available API key permissions', + columns: [ + { header: 'ID', key: 'id' }, + { header: 'Name', key: 'name' }, + { header: 'Resource', key: 'resource' }, + { header: 'Action', key: 'action' }, + { header: 'Description', key: 'description' }, + ], + apiCall: (client) => client.get('/resources/rbac/api-key-permissions'), +}); diff --git a/src/commands/resources/rbac-roles.ts b/src/commands/resources/rbac-roles.ts new file mode 100644 index 0000000..cd140d2 --- /dev/null +++ b/src/commands/resources/rbac-roles.ts @@ -0,0 +1,12 @@ +import { makeListCommand } from '../../helpers/command-factory.ts'; + +export const resourcesRbacRolesCommand = makeListCommand({ + name: 'rbac-roles', + description: 'List available API key roles', + columns: [ + { header: 'ID', key: 'id' }, + { header: 'Name', key: 'name' }, + { header: 'Description', key: 'description' }, + ], + apiCall: (client) => client.get('/resources/rbac/api-key-roles'), +}); diff --git a/src/commands/static-sites/index.ts b/src/commands/static-sites/index.ts index 2a3dfe4..be41c91 100644 --- a/src/commands/static-sites/index.ts +++ b/src/commands/static-sites/index.ts @@ -5,6 +5,7 @@ import { staticSitesCreateCommand } from './create.ts'; import { staticSitesUpdateCommand } from './update.ts'; import { staticSitesDeleteCommand } from './delete.ts'; import { staticSitesPurgeCacheCommand } from './purge-cache.ts'; +import { staticSitesPrettyUrlToggleCommand } from './pretty-url-toggle.ts'; import { makeDeploymentsCommands } from './deployments/index.ts'; import { makeDomainsCommands } from './domains/index.ts'; import { makeEnvVarsCommands } from './env-vars/index.ts'; @@ -19,6 +20,7 @@ export function makeStaticSitesCommand(): Command { cmd.addCommand(staticSitesUpdateCommand); cmd.addCommand(staticSitesDeleteCommand); cmd.addCommand(staticSitesPurgeCacheCommand); + cmd.addCommand(staticSitesPrettyUrlToggleCommand); cmd.addCommand(makeDeploymentsCommands()); cmd.addCommand(makeDomainsCommands()); cmd.addCommand(makeEnvVarsCommands()); diff --git a/src/commands/static-sites/pretty-url-toggle.ts b/src/commands/static-sites/pretty-url-toggle.ts new file mode 100644 index 0000000..4881176 --- /dev/null +++ b/src/commands/static-sites/pretty-url-toggle.ts @@ -0,0 +1,13 @@ +import { makeActionCommand } from '../../helpers/command-factory.ts'; + +interface PrettyUrlStatus { + is_turned_on: boolean; +} + +export const staticSitesPrettyUrlToggleCommand = makeActionCommand({ + name: 'pretty-url-toggle', + description: 'Toggle pretty URL redirects for a static site', + apiCall: (client, id) => client.post(`/static-sites/${id}/pretty-url/toggle`), + successMessage: (result) => `Pretty URLs ${result.is_turned_on ? 'enabled' : 'disabled'}.`, + spinnerText: 'Toggling pretty URLs...', +}); diff --git a/src/commands/usage-alerts/create.ts b/src/commands/usage-alerts/create.ts new file mode 100644 index 0000000..0741202 --- /dev/null +++ b/src/commands/usage-alerts/create.ts @@ -0,0 +1,67 @@ +import { makeCreateCommand } from '../../helpers/command-factory.ts'; + +function parseList(raw: unknown): string[] { + if (raw === undefined || raw === null) return []; + const value = String(raw).trim(); + if (!value) return []; + return value + .split(',') + .map((s) => s.trim()) + .filter(Boolean); +} + +function parsePercentages(raw: unknown): Array<{ percentage: number }> { + return parseList(raw).map((s) => { + const n = Number(s.replace(/%$/, '')); + if (!Number.isFinite(n)) { + throw new Error(`Invalid trigger percentage: ${s}`); + } + return { percentage: n }; + }); +} + +export const usageAlertsCreateCommand = makeCreateCommand({ + name: 'create', + description: 'Create a usage alert config', + apiPath: '/usage-alerts', + options: [ + { + flags: '--limit-usd ', + description: 'Spending limit in USD (triggers fire as percentages of this value)', + required: true, + }, + { + flags: '--emails ', + description: 'Comma-separated email recipients', + required: true, + }, + { + flags: '--triggers ', + description: 'Comma-separated trigger percentages, e.g. "50,80,100"', + required: true, + }, + { + flags: '--project-id ', + description: 'Scope to a project. Omit for company-wide config.', + }, + ], + displayFields: (item: Record) => ({ + ID: item['id'], + 'Project ID': item['project_id'], + 'Limit (USD)': item['limit_usd'], + Emails: ((item['emails'] as string[] | undefined) ?? []).join(', '), + Triggers: ((item['triggers'] as Array<{ percentage: number }> | undefined) ?? []) + .map((t) => `${t.percentage}%`) + .join(', '), + }), + apiCall: (client, opts) => { + const body: Record = { + limit_usd: Number(opts['limitUsd']), + emails: parseList(opts['emails']), + triggers: parsePercentages(opts['triggers']), + }; + if (opts['projectId']) body['project_id'] = opts['projectId']; + return client.post('/usage-alerts', body); + }, + successMessage: 'Usage alert created.', +}); diff --git a/src/commands/usage-alerts/delete.ts b/src/commands/usage-alerts/delete.ts new file mode 100644 index 0000000..5e342ae --- /dev/null +++ b/src/commands/usage-alerts/delete.ts @@ -0,0 +1,8 @@ +import { makeDeleteCommand } from '../../helpers/command-factory.ts'; + +export const usageAlertsDeleteCommand = makeDeleteCommand({ + name: 'delete', + description: 'Delete a usage alert config', + apiCall: (client, id) => client.delete(`/usage-alerts/${id}`), + successMessage: 'Usage alert deleted.', +}); diff --git a/src/commands/usage-alerts/get.ts b/src/commands/usage-alerts/get.ts new file mode 100644 index 0000000..9700397 --- /dev/null +++ b/src/commands/usage-alerts/get.ts @@ -0,0 +1,19 @@ +import { makeGetCommand } from '../../helpers/command-factory.ts'; + +export const usageAlertsGetCommand = makeGetCommand({ + name: 'get', + description: 'Get a usage alert config', + displayFields: (item: Record) => ({ + ID: item['id'], + 'Company ID': item['company_id'], + 'Project ID': item['project_id'], + 'Limit (USD)': item['limit_usd'], + Emails: ((item['emails'] as string[] | undefined) ?? []).join(', '), + Triggers: ((item['triggers'] as Array<{ percentage: number }> | undefined) ?? []) + .map((t) => `${t.percentage}%`) + .join(', '), + 'Created At': item['created_at'], + 'Updated At': item['updated_at'], + }), + apiCall: (client, id) => client.get(`/usage-alerts/${id}`), +}); diff --git a/src/commands/usage-alerts/index.ts b/src/commands/usage-alerts/index.ts new file mode 100644 index 0000000..6f7b178 --- /dev/null +++ b/src/commands/usage-alerts/index.ts @@ -0,0 +1,16 @@ +import { Command } from 'commander'; +import { usageAlertsListCommand } from './list.ts'; +import { usageAlertsGetCommand } from './get.ts'; +import { usageAlertsCreateCommand } from './create.ts'; +import { usageAlertsUpdateCommand } from './update.ts'; +import { usageAlertsDeleteCommand } from './delete.ts'; + +export function makeUsageAlertsCommand(): Command { + const cmd = new Command('usage-alerts').description('Manage spending usage alert configs'); + cmd.addCommand(usageAlertsListCommand); + cmd.addCommand(usageAlertsGetCommand); + cmd.addCommand(usageAlertsCreateCommand); + cmd.addCommand(usageAlertsUpdateCommand); + cmd.addCommand(usageAlertsDeleteCommand); + return cmd; +} diff --git a/src/commands/usage-alerts/list.ts b/src/commands/usage-alerts/list.ts new file mode 100644 index 0000000..2f3f93b --- /dev/null +++ b/src/commands/usage-alerts/list.ts @@ -0,0 +1,68 @@ +import { Command, Option } from 'commander'; +import { createClient } from '../../client/api-client.ts'; +import { handleError } from '../../errors/handler.ts'; +import { printJson, printTable } from '../../output/formatter.ts'; +import { startSpinner, stopSpinner } from '../../output/spinner.ts'; +import { jsonOption, paginationOptions, resolveJsonMode } from '../../helpers/flags.ts'; +import type { PaginatedResponse, QueryParams } from '../../client/types.ts'; +import { validateId } from '../../helpers/validate.ts'; + +interface UsageAlertRow { + id: string; + project_id: string | null; + limit_usd: number; + emails: string[]; + triggers: Array<{ percentage: number }>; +} + +export const usageAlertsListCommand = (() => { + const cmd = new Command('list').description('List usage alert configs'); + cmd.addOption(jsonOption()); + cmd.addOption( + new Option('--project-id ', 'Filter to a specific project. Omit to list company-wide.'), + ); + for (const opt of paginationOptions()) { + cmd.addOption(opt); + } + + cmd.action(async (opts: Record) => { + const client = createClient({ apiUrl: opts['apiUrl'] as string | undefined }); + const json = resolveJsonMode(opts); + try { + if (opts['projectId']) { + validateId(opts['projectId'] as string, 'project-id'); + } + if (!json) startSpinner('Fetching...'); + + const perPage = opts['perPage'] as number; + const page = opts['page'] as number; + const query: QueryParams = { + limit: perPage, + offset: (page - 1) * perPage, + }; + if (opts['projectId']) query['project_id'] = opts['projectId'] as string; + + const result = await client.get>('/usage-alerts', query); + stopSpinner(); + if (json) { + printJson(result.data); + } else { + printTable(result, [ + { header: 'ID', key: 'id' }, + { header: 'Project', key: 'project_id' }, + { header: 'Limit (USD)', key: 'limit_usd' }, + { header: 'Emails', get: (item) => item.emails.join(', ') }, + { + header: 'Triggers', + get: (item) => item.triggers.map((t) => `${t.percentage}%`).join(', '), + }, + ]); + } + } catch (error) { + stopSpinner(); + handleError(error, json); + } + }); + + return cmd; +})(); diff --git a/src/commands/usage-alerts/update.ts b/src/commands/usage-alerts/update.ts new file mode 100644 index 0000000..6fd43f3 --- /dev/null +++ b/src/commands/usage-alerts/update.ts @@ -0,0 +1,51 @@ +import { makeUpdateCommand } from '../../helpers/command-factory.ts'; + +function parseList(raw: unknown): string[] { + if (raw === undefined || raw === null) return []; + const value = String(raw).trim(); + if (!value) return []; + return value + .split(',') + .map((s) => s.trim()) + .filter(Boolean); +} + +function parsePercentages(raw: unknown): Array<{ percentage: number }> { + return parseList(raw).map((s) => { + const n = Number(s.replace(/%$/, '')); + if (!Number.isFinite(n)) { + throw new Error(`Invalid trigger percentage: ${s}`); + } + return { percentage: n }; + }); +} + +export const usageAlertsUpdateCommand = makeUpdateCommand({ + name: 'update', + description: 'Update a usage alert config', + apiPath: '/usage-alerts', + options: [ + { flags: '--limit-usd ', description: 'New spending limit in USD' }, + { flags: '--emails ', description: 'Comma-separated email recipients' }, + { + flags: '--triggers ', + description: + 'Comma-separated trigger percentages, e.g. "50,80,100". Replaces all existing triggers.', + }, + ], + displayFields: (item: Record) => ({ + ID: item['id'], + 'Limit (USD)': item['limit_usd'], + Emails: ((item['emails'] as string[] | undefined) ?? []).join(', '), + Triggers: ((item['triggers'] as Array<{ percentage: number }> | undefined) ?? []) + .map((t) => `${t.percentage}%`) + .join(', '), + }), + apiCall: (client, id, opts) => { + const body: Record = {}; + if (opts['limitUsd'] !== undefined) body['limit_usd'] = Number(opts['limitUsd']); + if (opts['emails'] !== undefined) body['emails'] = parseList(opts['emails']); + if (opts['triggers'] !== undefined) body['triggers'] = parsePercentages(opts['triggers']); + return client.patch(`/usage-alerts/${id}`, body); + }, +}); diff --git a/src/program.ts b/src/program.ts index 67ffd88..ac00541 100644 --- a/src/program.ts +++ b/src/program.ts @@ -18,6 +18,7 @@ import { makeResourcesCommand } from './commands/resources/index.ts'; import { makeGlobalEnvVarsCommand } from './commands/global-env-vars/index.ts'; import { makeGitCommand } from './commands/git/index.ts'; import { makeUsersCommand } from './commands/users/index.ts'; +import { makeUsageAlertsCommand } from './commands/usage-alerts/index.ts'; import { makeSchemaCommand } from './commands/schema.ts'; import { makeCompletionCommand, makeCompleteCommand } from './commands/completion.ts'; @@ -51,6 +52,7 @@ export function createProgram(): Command { program.addCommand(makeGlobalEnvVarsCommand()); program.addCommand(makeGitCommand()); program.addCommand(makeUsersCommand()); + program.addCommand(makeUsageAlertsCommand()); // Completion program.addCommand(makeCompletionCommand());