Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
"message": "<%= config.name %> update available from <%= chalk.greenBright(config.version) %> to <%= chalk.greenBright(latest) %>. To update, run `npm install -D checkly@latest`"
},
"topics": {
"checks": {
"description": "Manage and inspect Checkly checks."
},
"env": {
"description": "Manage Checkly environment variables."
},
Expand Down
113 changes: 113 additions & 0 deletions packages/cli/src/commands/checks/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Flags } from '@oclif/core'
import * as api from '../../rest/api'
import { AuthCommand } from '../authCommand'
import type { CheckDTO } from '../../rest/checks'

export default class ChecksList extends AuthCommand {
static hidden = false
static description = 'List all checks in your Checkly account.'

static examples = [
'$ npx checkly checks list',
'$ npx checkly checks list --check-type BROWSER',
'$ npx checkly checks list --tag production --activated',
'$ npx checkly checks list --json',
]

static flags = {
'check-type': Flags.string({
description: 'Filter by check type (API, BROWSER, HEARTBEAT, MULTI_STEP, TCP, PLAYWRIGHT, URL, DNS, ICMP)',
options: ['API', 'BROWSER', 'HEARTBEAT', 'ICMP', 'MULTI_STEP', 'TCP', 'PLAYWRIGHT', 'URL', 'DNS'],
}),
'tag': Flags.string({
description: 'Filter by tag name',
}),
'activated': Flags.boolean({
description: 'Show only activated checks',
allowNo: true,
}),
'muted': Flags.boolean({
description: 'Show only muted checks',
allowNo: true,
}),
'json': Flags.boolean({
description: 'Output as JSON',
default: false,
}),
}

async run (): Promise<void> {
const { flags } = await this.parse(ChecksList)

const checks = await api.checks.listAll({
checkType: flags['check-type'],
tag: flags.tag,
activated: flags.activated,
muted: flags.muted,
})

if (checks.length === 0) {
this.log('No checks found.')
return
}

if (flags.json) {
this.log(JSON.stringify(checks, null, 2))
return
}

this.log(`Found ${checks.length} check${checks.length === 1 ? '' : 's'}:\n`)

// Column widths
const nameWidth = Math.min(40, Math.max(4, ...checks.map(c => c.name.length)))

// Header
const header = [
pad('NAME', nameWidth),
pad('TYPE', 12),
pad('STATUS', 10),
pad('FREQ', 6),
pad('LOCATIONS', 20),
'TAGS',
].join(' ')
this.log(header)
this.log('─'.repeat(header.length))

for (const check of checks) {
const status = formatStatus(check)
const locations = formatLocations(check)
const tags = check.tags.length > 0 ? check.tags.join(', ') : '-'

this.log([
pad(truncate(check.name, nameWidth), nameWidth),
pad(check.checkType, 12),
pad(status, 10),
pad(`${check.frequency}m`, 6),
pad(locations, 20),
tags,
].join(' '))
}
}
}

function formatStatus (check: CheckDTO): string {
if (!check.activated) return 'disabled'
if (check.muted) return 'muted'
return 'active'
}

function formatLocations (check: CheckDTO): string {
const count = check.locations.length + check.privateLocations.length
if (count === 0) return '-'
if (count <= 2) return [...check.locations, ...check.privateLocations].join(', ')
return `${count} locations`
}

function truncate (str: string, len: number): string {
if (str.length <= len) return str
return str.slice(0, len - 1) + '…'
}

function pad (str: string, len: number): string {
return str.padEnd(len)
}
13 changes: 13 additions & 0 deletions packages/cli/src/rest/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@ import Locations from './locations'
import TestSessions from './test-sessions'
import EnvironmentVariables from './environment-variables'
import HeartbeatChecks from './heartbeat-checks'
import Checks from './checks'
import ChecklyStorage from './checkly-storage'
import { handleErrorResponse, UnauthorizedError } from './errors'

/**
* The API version date for the new versioned API surface (/api/).
* This is pinned per CLI release — clients opt in to new versions explicitly.
*/
export const CHECKLY_API_VERSION = '2026-02-10'

export function getDefaults () {
const apiKey = config.getApiKey()
const accountId = config.getAccountId()
Expand Down Expand Up @@ -66,6 +73,11 @@ export function requestInterceptor (config: InternalAxiosRequestConfig) {

config.headers['x-checkly-ci-name'] = CIname

// Pin API version header for all /api/ requests
if (config.url?.startsWith('/api/')) {
config.headers['x-checkly-api-version'] = CHECKLY_API_VERSION
}

return config
}

Expand Down Expand Up @@ -101,4 +113,5 @@ export const privateLocations = new PrivateLocations(api)
export const testSessions = new TestSessions(api)
export const environmentVariables = new EnvironmentVariables(api)
export const heartbeatCheck = new HeartbeatChecks(api)
export const checks = new Checks(api)
export const checklyStorage = new ChecklyStorage(api)
80 changes: 80 additions & 0 deletions packages/cli/src/rest/checks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { AxiosInstance } from 'axios'

export interface CheckDTO {
id: string
name: string
checkType: string
activated: boolean
muted: boolean
frequency: number
locations: string[]
privateLocations: string[]
tags: string[]
groupId: string | null
runtimeId: string | null
doubleCheck: boolean
degradedResponseTime: number | null
maxResponseTime: number | null
createdAt: string
updatedAt: string
}

export interface Pagination {
nextCursor: string | null
hasMore: boolean
}

export interface ResponseMeta {
apiVersion: string
stability: string
requestId: string
}

export interface ChecksListResponse {
data: CheckDTO[]
pagination: Pagination
meta: ResponseMeta
}

export interface ChecksListParams {
limit?: number
cursor?: string
checkType?: string
tag?: string
muted?: boolean
activated?: boolean
}

class Checks {
api: AxiosInstance
constructor (api: AxiosInstance) {
this.api = api
}

/**
* List all checks via the versioned API.
* Automatically paginates through all results when no cursor is provided.
*/
async list (params: ChecksListParams = {}) {
const { data } = await this.api.get<ChecksListResponse>('/api/checks', { params })
return data
}

/**
* List all checks, automatically paginating through all pages.
*/
async listAll (params: Omit<ChecksListParams, 'cursor'> = {}): Promise<CheckDTO[]> {
const allChecks: CheckDTO[] = []
let cursor: string | undefined

do {
const response = await this.list({ ...params, limit: 100, cursor })
allChecks.push(...response.data)
cursor = response.pagination.nextCursor ?? undefined
} while (cursor)

return allChecks
}
}

export default Checks
Loading