import { log } from "@clack/prompts" import { defineCommand } from "../lib/command" import { globalOptions, globalOptionsSchema } from "../lib/global-options" import { z } from "zod" import { apiClient } from "../lib/api-client" import { renderDetailBlock } from "../lib/detail-block" import { accent, dimmed } from "../lib/io" import { clearSpinner, startSpinner } from "../lib/spinner" import { ORG_OPT, output, PAGINATION_OPTS, paginationOptsSchema, PROJECT_OPT, pluralize, requireSession, sessionIntro, useProjectScope, ORG_OPTION_DESCRIPTIONS, PROJECT_OPTION_DESCRIPTIONS, PAGINATION_OPTION_DESCRIPTIONS, } from "../utils" const AUTOMATION_LIST_OPTS_SCHEMA = paginationOptsSchema.extend({ org: z.string().optional(), project: z.string().optional(), query: z.string().optional(), state: z.enum(["enabled", "disabled"]).optional(), }) export const automationListCommand = defineCommand({ name: "list", description: "List active automations", options: { ...globalOptions, ...ORG_OPT, ...PAGINATION_OPTS, ...PROJECT_OPT, query: { type: "string", short: "q", }, state: { type: "string", }, }, optionDescriptions: { ...ORG_OPTION_DESCRIPTIONS, ...PAGINATION_OPTION_DESCRIPTIONS, ...PROJECT_OPTION_DESCRIPTIONS, org: "Limit project selection to an organization name or ID", query: "Filter by automation, project, or description", state: "Filter by enabled or disabled state", }, schema: globalOptionsSchema.extend(AUTOMATION_LIST_OPTS_SCHEMA.shape), run: listAutomations, }) /** * Lists active automations through the public management contract. * * @param opts - Resource filters, search query, and page controls. */ export async function listAutomations( opts: z.infer, ) { const session = await requireSession("list automations") output.normal(() => sessionIntro(session)) const { organization, project } = await useProjectScope(opts) startSpinner("Fetching automations") const page = await apiClient.automation.list({ cursor: opts.cursor, enabled: opts.state === undefined ? undefined : opts.state === "enabled", limit: opts.limit, organizationId: organization.id, projectId: project.id, query: opts.query, }) clearSpinner() output .normal(() => { if (page.items.length === 0) { log.warn("No active automations found.") return } log.message( `${accent(String(page.items.length))} ${pluralize(page.items.length, "automation")}`, ) for (const automation of page.items) { log.info(renderAutomationDetails(automation)) } if (page.pageInfo.nextCursor) { log.info(`More results: --cursor ${page.pageInfo.nextCursor}`) } log.message() }) .json(page) } /** * Formats one automation for list and detail commands. * * @param automation - Public automation metadata to render. */ export function renderAutomationDetails( automation: Awaited>, ) { return renderDetailBlock( { [dimmed`Automation`]: `${automation.description} ${dimmed(automation.id)}`, [dimmed`Source`]: automation.identityKey, [dimmed`Project`]: `${automation.project.organization.name} › ${automation.project.name}`, [dimmed`State`]: automation.enabled ? "enabled" : "disabled", [dimmed`Deployment`]: dimmed(automation.activeDeploymentId), [dimmed`Triggers`]: automation.triggers.length === 0 ? "None" : automation.triggers .map((trigger) => [ trigger.name, trigger.primary?.value, ...trigger.details.map( ({ label, value }) => `${label}: ${value}`, ), ] .filter((value) => value !== undefined) .join(" · "), ) .join("\n"), [dimmed`Accounts`]: automation.accountBindings.length === 0 ? "None" : automation.accountBindings .map( ({ account, binding, serviceId }) => `${serviceId}/${binding}: ${account?.label ?? "Unbound"}`, ) .join("\n"), }, { keyAlignment: "right" }, ) }