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')}`)
    }
  }))
}