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 5sNote
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:
| Field | Type | Description |
|---|---|---|
text | string | The transcribed text |
speaker | string | Speaker identity (email) |
displayName | string | Speaker display name |
isFinal | boolean | Whether this is a final transcription (vs interim) |
timestamp | string | ISO 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.isFinalbefore processing. Interim segments are partial and will be replaced. - Use
@refreshto periodically re-render with the latest transcript data. - Combine transcript access with other output types —
tabsfor an overview and detail view,kvfor summary stats.