Transcript Access

Access real-time meeting transcripts in your plugins for keyword tracking, action items, and more.

Plugins can access real-time meeting transcripts by declaring @events transcript:segment in their header. The transcript segments are passed into context.transcriptSegments as an array.

Declaring transcript access

Add @events transcript:segment to your plugin header:

// @stoa-plugin
// @name Live Keywords
// @type widget
// @icon Activity
// @events transcript:segment
// @refresh 5s

Note

Only plugins with @events transcript:segment receive transcript data. This avoids serialization overhead for plugins that don't need it.

Segment shape

Each transcript segment has:

FieldTypeDescription
textstringThe transcribed text
speakerstringSpeaker identity (email)
displayNamestringSpeaker display name
isFinalbooleanWhether this is a final transcription (vs interim)
timestampstringISO timestamp

Example: keyword frequency

// @stoa-plugin
// @name Live Keywords
// @type widget
// @icon Activity
// @events transcript:segment
// @refresh 5s

module.exports = {
  render(context) {
    const segments = context.transcriptSegments || []

    // Count word frequency from final segments only
    const words = {}
    for (const seg of segments) {
      if (!seg.isFinal) continue
      for (const word of seg.text.split(/\s+/)) {
        const w = word.toLowerCase().replace(/[^a-z]/g, '')
        if (w.length > 3) words[w] = (words[w] || 0) + 1
      }
    }

    const sorted = Object.entries(words)
      .sort((a, b) => b[1] - a[1])
      .slice(0, 15)

    return {
      type: 'list',
      items: sorted.map(([word, count]) => ({
        label: word,
        subtitle: count + 'x',
        iconColor: count > 5 ? 'orange' : 'gray'
      }))
    }
  }
}

Example: speaker tracker

// @stoa-plugin
// @name Speaker Time
// @type widget
// @icon Users
// @events transcript:segment
// @refresh 10s

module.exports = {
  render(context) {
    const segments = context.transcriptSegments || []
    const speakers = {}

    for (const seg of segments) {
      if (!seg.isFinal) continue
      const name = seg.displayName || seg.speaker
      speakers[name] = (speakers[name] || 0) + 1
    }

    const sorted = Object.entries(speakers)
      .sort((a, b) => b[1] - a[1])

    const total = sorted.reduce((sum, [, count]) => sum + count, 0)

    return {
      type: 'list',
      items: sorted.map(([name, count]) => ({
        label: name,
        subtitle: Math.round((count / total) * 100) + '% of segments',
        iconColor: 'blue'
      }))
    }
  }
}

Tips

  • Always check seg.isFinal before processing. Interim segments are partial and will be replaced.
  • Use @refresh to periodically re-render with the latest transcript data.
  • Combine transcript access with other output types — tabs for an overview and detail view, kv for summary stats.