Skip to content
92 changes: 61 additions & 31 deletions components/grain/actions/get-recording/get-recording.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import {
INTELLIGENCE_NOTES_FORMAT_OPTIONS,
TRANSCRIPT_FORMAT_OPTIONS,
} from "../../common/constants.mjs";
import { parseObject } from "../../common/utils.mjs";
import grain from "../../grain.app.mjs";

export default {
key: "grain-get-recording",
name: "Get Recording",
description: "Fetches a specific recording by its ID from Grain, optionally including the transcript and intelligence notes. [See the documentation](https://grainhq.notion.site/grain-public-api-877184aa82b54c77a875083c1b560de9)",
version: "0.0.2",
description: "Fetches a specific recording by its ID from Grain, returning its metadata (title, times, URL, tags, teams, meeting type)."
+ " Enable the optional include props to add highlights, participants, AI action items, AI summary, calendar event, HubSpot data, or screenshares to the response."
+ " Use **List Recordings** to find recording IDs, and **Get Transcript** to fetch the full transcript."
+ " [See the documentation](https://developers.grain.com/#get-recording)",
version: "1.0.0",
annotations: {
destructiveHint: false,
openWorldHint: true,
Expand All @@ -18,45 +16,77 @@ export default {
type: "action",
props: {
grain,
recordId: {
recordingId: {
propDefinition: [
grain,
"recordId",
"recordingId",
],
},
transcriptFormat: {
type: "string",
label: "Transcript Format",
description: "Format for the transcript",
options: TRANSCRIPT_FORMAT_OPTIONS,
optional: true,
highlights: {
propDefinition: [
grain,
"highlights",
],
},
intelligenceNotesFormat: {
type: "string",
label: "Intelligence Notes Format",
description: "Format for the intelligence notes",
options: INTELLIGENCE_NOTES_FORMAT_OPTIONS,
optional: true,
participants: {
propDefinition: [
grain,
"participants",
],
},
aiActionItems: {
propDefinition: [
grain,
"aiActionItems",
],
},
allowedIntelligenceNotes: {
type: "string[]",
label: "Allowed Intelligence Notes",
description: "Whitelist of intelligence notes section titles",
aiSummary: {
propDefinition: [
grain,
"aiSummary",
],
},
calendarEvent: {
propDefinition: [
grain,
"calendarEvent",
],
},
hubspot: {
propDefinition: [
grain,
"hubspot",
],
},
screenshares: {
type: "boolean",
label: "Include Screenshares",
description: "Include the recording's screenshare ranges in the response",
optional: true,
},
},
async run({ $ }) {
const include = {
highlights: this.highlights,
participants: this.participants,
ai_action_items: this.aiActionItems,
ai_summary: this.aiSummary,
calendar_event: this.calendarEvent,
hubspot: this.hubspot,
screenshares: this.screenshares,
};

const response = await this.grain.fetchRecording({
$,
recordId: this.recordId,
params: {
transcript_format: this.transcriptFormat,
intelligence_notes_format: this.intelligenceNotesFormat,
allowed_intelligence_notes: parseObject(this.allowedIntelligenceNotes),
recordingId: this.recordingId,
data: {
include: Object.fromEntries(Object.entries(include).filter(([
, value,
]) => value)),
},
});

$.export("$summary", `Successfully fetched recording with ID ${this.recordId}`);
$.export("$summary", `Successfully fetched recording with ID ${this.recordingId}`);
return response;
},
};
45 changes: 45 additions & 0 deletions components/grain/actions/get-transcript/get-transcript.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { TRANSCRIPT_FORMAT_OPTIONS } from "../../common/constants.mjs";
import grain from "../../grain.app.mjs";

export default {
key: "grain-get-transcript",
name: "Get Transcript",
description: "Fetches the full transcript of a Grain recording."
+ " The `json` format returns structured segments with speaker, participant ID, start/end times in milliseconds, and text;"
+ " `txt`, `vtt`, and `srt` return plain text or subtitle formats."
+ " Use **List Recordings** to find recording IDs; use **Get Recording** for the recording's metadata instead of its transcript."
+ " [See the documentation](https://developers.grain.com/#get-recording-transcript-json)",
version: "0.0.1",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: true,
},
type: "action",
props: {
grain,
recordingId: {
propDefinition: [
grain,
"recordingId",
],
},
format: {
type: "string",
label: "Format",
description: "Format for the transcript",
options: TRANSCRIPT_FORMAT_OPTIONS,
default: "json",
},
},
async run({ $ }) {
const response = await this.grain.fetchTranscript({
$,
recordingId: this.recordingId,
format: this.format,
});

$.export("$summary", `Successfully fetched transcript for recording ${this.recordingId}`);
return response;
},
};
89 changes: 89 additions & 0 deletions components/grain/actions/list-recordings/list-recordings.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import grain from "../../grain.app.mjs";

export default {
key: "grain-list-recordings",
name: "List Recordings",
description: "Lists Grain recordings, optionally filtered by start datetime range (ISO8601), title search, or participant scope."
+ " Automatically paginates and returns up to Max Results recordings."
+ " Use this to find recording IDs for **Get Recording** and **Get Transcript**."
+ " [See the documentation](https://developers.grain.com/#list-recordings)",
version: "0.0.1",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: true,
},
type: "action",
props: {
grain,
beforeDatetime: {
type: "string",
label: "Before Datetime",
description: "Only return recordings that started before this ISO8601 datetime. E.g. `2025-01-01T00:00:00Z`",
optional: true,
},
afterDatetime: {
type: "string",
label: "After Datetime",
description: "Only return recordings that started after this ISO8601 datetime. E.g. `2025-01-01T00:00:00Z`",
optional: true,
},
titleSearch: {
type: "string",
label: "Title Search",
description: "Only return recordings whose title matches this search string",
optional: true,
},
participantScope: {
type: "string",
label: "Participant Scope",
description: "Only return recordings whose participants are all internal, or that include external participants",
options: [
"internal",
"external",
],
optional: true,
},
maxResults: {
type: "integer",
label: "Max Results",
description: "Maximum number of recordings to return. Must be a positive integer.",
optional: true,
default: 100,
min: 1,
},
},
async run({ $ }) {
const filter = {
before_datetime: this.beforeDatetime,
after_datetime: this.afterDatetime,
title_search: this.titleSearch,
participant_scope: this.participantScope,
};

const recordings = [];
let cursor;
do {
const {
recordings: page, cursor: nextCursor,
} = await this.grain.listRecordings({
$,
data: {
cursor,
filter,
},
});
recordings.push(...page);
cursor = nextCursor;
} while (cursor && recordings.length < this.maxResults);

if (recordings.length > this.maxResults) {
recordings.length = this.maxResults;
}

$.export("$summary", `Successfully fetched ${recordings.length} recording${recordings.length === 1
? ""
: "s"}`);
return recordings;
},
};
19 changes: 6 additions & 13 deletions components/grain/common/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,15 @@ export const TRANSCRIPT_FORMAT_OPTIONS = [
value: "json",
},
{
label: "VTT",
value: "vtt",
},
];

export const INTELLIGENCE_NOTES_FORMAT_OPTIONS = [
{
label: "JSON",
value: "json",
label: "Text",
value: "txt",
},
{
label: "Markdown",
value: "md",
label: "VTT",
value: "vtt",
},
{
label: "Text",
value: "text",
label: "SRT",
value: "srt",
},
];
24 changes: 0 additions & 24 deletions components/grain/common/utils.mjs

This file was deleted.

Loading
Loading