Field Manual ยท Common Missions
Common Use Cases
Production-ready patterns for the most common ChatOps workflows. Each example is a drop-in module you can extend for your environment.
Deployments
Trigger, monitor, and roll back deployments with a full confirmation flow and audit trail:
// modules/deploy.js
import { Command, createTextResponse, createMessageResponse } from '@devchitchat/chatopsjs'
export default async function (robot) {
robot.commands.register(new Command({
id: 'deploy',
description: 'Deploy a service to an environment',
aliases: ['ship'],
args: {
service: { type: 'string', required: true },
version: { type: 'string', required: true },
environment: { type: 'string', required: false } // default: staging
},
permissions: ['deploys:write'],
confirm: {
mode: 'yes-no',
message: '๐ Confirm deployment? (yes/no)'
},
handler: async (ctx) => {
const { service, version, environment = 'staging' } = ctx.args
const deployId = `deploy-${Date.now()}`
await ctx.storage.set('last-deploy', { deployId, service, version, environment })
return createMessageResponse({
fallbackText: `Deploying ${service} ${version} to ${environment}`,
blocks: [
{ type: 'section', text: `๐ **Deployment started** [${deployId}]` },
{ type: 'facts', items: [
{ label: 'Service', value: service },
{ label: 'Version', value: version },
{ label: 'Environment', value: environment },
{ label: 'Triggered by', value: ctx.envelope.actor.id }
]}
]
})
}
}))
robot.commands.register(new Command({
id: 'rollback',
description: 'Roll back the last deployment',
permissions: ['deploys:write'],
confirm: {
mode: 'yes-no',
message: 'โ ๏ธ Roll back the last deployment? (yes/no)'
},
handler: async (ctx) => {
const last = await ctx.storage.get('last-deploy')
if (!last) return createTextResponse('No deployment to roll back.')
return createTextResponse(
`Rolling back ${last.service} ${last.version} from ${last.environment}โฆ`
)
}
}))
robot.commands.register(new Command({
id: 'deploy.status',
description: 'Check the status of the last deployment',
handler: async (ctx) => {
const last = await ctx.storage.get('last-deploy')
if (!last) return createTextResponse('No deployments on record.')
return createMessageResponse({
fallbackText: `Last deploy: ${last.service} ${last.version}`,
blocks: [
{ type: 'section', text: '๐ **Last deployment**' },
{ type: 'facts', items: [
{ label: 'Service', value: last.service },
{ label: 'Version', value: last.version },
{ label: 'Env', value: last.environment }
]}
]
})
}
}))
}
Incident Management
Declare, acknowledge, update, and resolve incidents โ with a status board and paging integration:
// modules/incidents.js
import { Command, createTextResponse, createMessageResponse } from '@devchitchat/chatopsjs'
const SEVERITIES = ['sev1', 'sev2', 'sev3']
export default async function (robot) {
robot.commands.register(new Command({
id: 'incident.declare',
description: 'Declare a new incident',
aliases: ['inc declare', 'incident new'],
args: {
severity: { type: 'string', required: true },
title: { type: 'string', required: true }
},
permissions: ['incidents:write'],
confirm: {
mode: 'yes-no',
message: '๐จ This will page on-call. Declare incident? (yes/no)'
},
handler: async (ctx) => {
const { severity, title } = ctx.args
if (!SEVERITIES.includes(severity.toLowerCase())) {
return createTextResponse(
`Invalid severity. Use: ${SEVERITIES.join(', ')}`
)
}
const id = `INC-${Date.now()}`
const incident = {
id, severity, title,
status: 'open',
declaredAt: new Date().toISOString(),
declaredBy: ctx.envelope.actor.id,
acknowledgedBy: null,
resolvedAt: null
}
const all = (await ctx.storage.get('incidents')) ?? {}
all[id] = incident
await ctx.storage.set('incidents', all)
return createMessageResponse({
fallbackText: `๐จ ${id}: ${title}`,
blocks: [
{ type: 'section', text: `๐จ **${id}** โ ${title}` },
{ type: 'facts', items: [
{ label: 'Severity', value: severity.toUpperCase() },
{ label: 'Declared by', value: ctx.envelope.actor.id },
{ label: 'Status', value: 'OPEN' }
]}
]
})
}
}))
robot.commands.register(new Command({
id: 'incident.ack',
description: 'Acknowledge an incident',
aliases: ['inc ack'],
args: { id: { type: 'string', required: true } },
permissions: ['incidents:write'],
handler: async (ctx) => {
const all = (await ctx.storage.get('incidents')) ?? {}
const incident = all[ctx.args.id]
if (!incident) return createTextResponse(`Not found: ${ctx.args.id}`)
if (incident.status !== 'open') return createTextResponse(`${ctx.args.id} is already ${incident.status}.`)
incident.status = 'acknowledged'
incident.acknowledgedBy = ctx.envelope.actor.id
await ctx.storage.set('incidents', all)
return createTextResponse(
`โ
${ctx.args.id} acknowledged by ${ctx.envelope.actor.id}`
)
}
}))
robot.commands.register(new Command({
id: 'incident.resolve',
description: 'Resolve an incident',
aliases: ['inc resolve', 'incident close'],
args: { id: { type: 'string', required: true } },
permissions: ['incidents:write'],
confirm: { mode: 'yes-no', message: 'Resolve this incident? (yes/no)' },
handler: async (ctx) => {
const all = (await ctx.storage.get('incidents')) ?? {}
const incident = all[ctx.args.id]
if (!incident) return createTextResponse(`Not found: ${ctx.args.id}`)
incident.status = 'resolved'
incident.resolvedAt = new Date().toISOString()
await ctx.storage.set('incidents', all)
return createTextResponse(`๐ข ${ctx.args.id} resolved.`)
}
}))
robot.commands.register(new Command({
id: 'incident.list',
description: 'List open incidents',
aliases: ['incidents', 'inc list'],
handler: async (ctx) => {
const all = (await ctx.storage.get('incidents')) ?? {}
const open = Object.values(all).filter(i => i.status === 'open')
if (!open.length) return createTextResponse('No open incidents. ๐ข')
const lines = open.map(i => `โข **${i.id}** [${i.severity.toUpperCase()}] ${i.title}`)
return createTextResponse(lines.join('\n'))
}
}))
}
Ticket Tracking
// modules/tickets.js
import { Command, createTextResponse, createMessageResponse } from '@devchitchat/chatopsjs'
export default async function (robot) {
robot.commands.register(new Command({
id: 'tickets.list',
description: 'List open tickets',
aliases: ['tickets'],
handler: async (ctx) => {
const all = (await ctx.storage.get('tickets')) ?? []
const open = all.filter(t => t.status === 'open')
if (!open.length) return createTextResponse('No open tickets.')
return createTextResponse(
open.map(t => `[${t.id}] ${t.title} (priority: ${t.priority})`).join('\n')
)
}
}))
robot.commands.register(new Command({
id: 'tickets.create',
description: 'Create a new ticket',
aliases: ['ticket new', 'tc'],
args: {
title: { type: 'string', required: true },
priority: { type: 'string', required: false } // low | medium | high
},
permissions: ['tickets:write'],
confirm: {
mode: 'yes-no',
message: 'Create a new ticket? (yes/no)'
},
handler: async (ctx) => {
const { title, priority = 'medium' } = ctx.args
const all = (await ctx.storage.get('tickets')) ?? []
const id = `TICK-${(all.length + 1).toString().padStart(4, '0')}`
all.push({ id, title, priority, status: 'open', createdBy: ctx.envelope.actor.id })
await ctx.storage.set('tickets', all)
return createMessageResponse({
fallbackText: `Ticket ${id} created`,
blocks: [
{ type: 'section', text: `๐ซ **${id}** created` },
{ type: 'facts', items: [
{ label: 'Title', value: title },
{ label: 'Priority', value: priority },
{ label: 'Created by', value: ctx.envelope.actor.id }
]}
]
})
}
}))
robot.commands.register(new Command({
id: 'tickets.close',
description: 'Close a ticket',
aliases: ['ticket close'],
args: { id: { type: 'string', required: true } },
permissions: ['tickets:write'],
handler: async (ctx) => {
const all = (await ctx.storage.get('tickets')) ?? []
const ticket = all.find(t => t.id === ctx.args.id)
if (!ticket) return createTextResponse(`Ticket not found: ${ctx.args.id}`)
ticket.status = 'closed'
await ctx.storage.set('tickets', all)
return createTextResponse(`โ
${ctx.args.id} closed.`)
}
}))
}
Access Provisioning
// modules/access.js
import { Command, createTextResponse, createMessageResponse } from '@devchitchat/chatopsjs'
export default async function (robot) {
robot.commands.register(new Command({
id: 'access.grant',
description: 'Grant a user access to a service',
aliases: ['grant access'],
args: {
user: { type: 'string', required: true },
service: { type: 'string', required: true },
role: { type: 'string', required: true } // read | write | admin
},
permissions: ['access:admin'],
confirm: {
mode: 'yes-no',
message: 'Grant access? This change is audited. (yes/no)'
},
handler: async (ctx) => {
const { user, service, role } = ctx.args
const log = (await ctx.storage.get('access-log')) ?? []
log.push({
action: 'grant',
user, service, role,
by: ctx.envelope.actor.id,
timestamp: new Date().toISOString()
})
await ctx.storage.set('access-log', log)
return createMessageResponse({
fallbackText: `Access granted: ${user} โ ${service}:${role}`,
blocks: [
{ type: 'section', text: `๐ **Access granted**` },
{ type: 'facts', items: [
{ label: 'User', value: user },
{ label: 'Service', value: service },
{ label: 'Role', value: role },
{ label: 'Granted by', value: ctx.envelope.actor.id }
]}
]
})
}
}))
robot.commands.register(new Command({
id: 'access.revoke',
description: 'Revoke access from a user',
aliases: ['revoke access'],
args: {
user: { type: 'string', required: true },
service: { type: 'string', required: true }
},
permissions: ['access:admin'],
confirm: { mode: 'yes-no', message: 'Revoke access? (yes/no)' },
handler: async (ctx) => {
const { user, service } = ctx.args
return createTextResponse(`โ
Access revoked: ${user} from ${service}`)
}
}))
}
Status Checks
// modules/status.js
import { Command, createTextResponse, createMessageResponse } from '@devchitchat/chatopsjs'
const SERVICES = {
api: 'https://api.internal/health',
payments: 'https://payments.internal/health',
auth: 'https://auth.internal/health'
}
export default async function (robot) {
robot.commands.register(new Command({
id: 'status',
description: 'Check service health',
args: {
service: { type: 'string', required: false }
},
handler: async (ctx) => {
const { service } = ctx.args
if (service) {
const url = SERVICES[service]
if (!url) return createTextResponse(
`Unknown service. Available: ${Object.keys(SERVICES).join(', ')}`
)
const result = await checkService(service, url)
return createTextResponse(
`${result.ok ? '๐ข' : '๐ด'} ${service}: ${result.status}`
)
}
const results = await Promise.all(
Object.entries(SERVICES).map(([name, url]) => checkService(name, url))
)
const facts = results.map(r => ({
label: r.name,
value: `${r.ok ? '๐ข' : '๐ด'} ${r.status} (${r.latency}ms)`
}))
const allOk = results.every(r => r.ok)
return createMessageResponse({
fallbackText: allOk ? 'All systems operational' : 'Some services degraded',
blocks: [
{ type: 'section', text: allOk ? '๐ข **All systems operational**' : 'โ ๏ธ **Some services degraded**' },
{ type: 'facts', items: facts }
]
})
}
}))
}
async function checkService(name, url) {
const start = Date.now()
try {
const res = await fetch(url, { signal: AbortSignal.timeout(5000) })
return { name, ok: res.ok, status: res.ok ? 'OK' : res.statusText, latency: Date.now() - start }
} catch (err) {
return { name, ok: false, status: err.message, latency: Date.now() - start }
}
}
Secret Rotation Reminder
// modules/secrets.js
import { Command, createTextResponse, createMessageResponse } from '@devchitchat/chatopsjs'
export default async function (robot) {
robot.commands.register(new Command({
id: 'secrets.rotate',
description: 'Rotate an API key or secret',
aliases: ['rotate secret'],
args: {
service: { type: 'string', required: true },
key: { type: 'string', required: true }
},
permissions: ['secrets:admin'],
confirm: {
mode: 'yes-no',
message: '๐ Rotate this secret? Old key will be invalidated immediately. (yes/no)'
},
handler: async (ctx) => {
const { service, key } = ctx.args
const log = (await ctx.storage.get('rotation-log')) ?? []
log.push({
service, key,
rotatedBy: ctx.envelope.actor.id,
timestamp: new Date().toISOString()
})
await ctx.storage.set('rotation-log', log)
return createMessageResponse({
fallbackText: `Secret rotated: ${service}/${key}`,
blocks: [
{ type: 'section', text: `๐ **Secret rotated**` },
{ type: 'facts', items: [
{ label: 'Service', value: service },
{ label: 'Key', value: key },
{ label: 'Rotated by', value: ctx.envelope.actor.id }
]}
]
})
}
}))
robot.commands.register(new Command({
id: 'secrets.audit',
description: 'View secret rotation log',
permissions: ['secrets:admin'],
handler: async (ctx) => {
const log = (await ctx.storage.get('rotation-log')) ?? []
if (!log.length) return createTextResponse('No rotation events on record.')
const lines = log.slice(-10).map(e =>
`${e.timestamp} ยท ${e.service}/${e.key} by ${e.rotatedBy}`
)
return createTextResponse(`**Last ${lines.length} rotations:**\n${lines.join('\n')}`)
}
}))
}