Build Fireflies.ai

KINDAreplaces $18/mosaves $216/yrback to the verdict

0%0 of 21 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A recorder that joins a meeting as a clearly labelled participant, announces itself, captures audio, transcribes with speaker labels and word timings, produces summaries and action items where every line cites a timestamp, and deletes recordings on a schedule you configured.

estimated effort multi-daythe files for this build are in the project pack

JoiningA headless browser participant, or the platform's own recording interface where one existsRuntimeNode 22 with PostgreSQLTranscriptionwhisper.cpp with word timings, run locallySpeakersDiarisation refined by hand, seeded from the participant listStorageRecordings encrypted with an expiry, transcripts kept separately

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the bot, the pipeline and the interface.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. decidean hour of advice, and it is a prerequisite rather than a formality

    Why It decides whether the bot may record at all and what it must say first. Some jurisdictions require every participant's agreement, and a meeting with attendees in different places takes the strictest applicable rule.

    Get it Identify where participants typically join from, take the strictest rule, and confirm with someone qualified if this is for a business. Record what you concluded and when.

    Verify A written note names the jurisdictions, the rule applied and the date

  3. decidefree

    Why Some platforms restrict or prohibit bots joining, and building against one that does means a tool you cannot use.

    Get it Read the terms for the platform you use, and prefer its own recording interface where one exists, since that route is sanctioned.

    Verify The position is recorded with a date and the chosen joining method

  4. installfree, but transcription takes real machine time

    Why Citations and playback both need word-level timings, and running locally keeps colleagues' conversations off a third party.

    Get it Build whisper.cpp and download a model sized for your machine. open ↗

    Verify Output contains per-word start and end times

  5. have readyfree

    Why Diarisation, audio levels and summary quality are all only judgeable against real conversation with more than two people talking over each other.

    Get it Ask colleagues, explain what you are testing, and record with their agreement. One meeting should have at least four participants.

    Verify Three recordings exist with documented consent

  6. decidefree

    Why Recordings, transcripts, summaries and consent records may need different periods, and automatic deletion has to be built rather than remembered.

    Get it Write it before recording anything real: what is kept, how long, and why.

    Verify The policy is written and the deletion job implements it

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- meetings(id, external_id, title, platform, started_at, ended_at, host_email, consent_state, announced_at)
- participants(id, meeting_id, name, email, joined_at, left_at, objected_at)
- recordings(meeting_id, storage_key, encrypted, expires_at, deleted_at)
- words(meeting_id, ordinal, text, start_ms, end_ms, speaker_label) · the spine everything cites
- segments(id, meeting_id, start_ms, end_ms, speaker_label, text)
- outputs(id, meeting_id, kind, body, cited_segment_ids_json, validated) · kind is summary, actions or decisions
- A meeting where a participant objected has no recording and no transcript. The objection is the record.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://user:pass@localhost:5432/meetingsMeetings, transcripts and consent records. Personal data with obligations attached.
RECORDINGS_BUCKETrequiredmeeting-recordingsEncrypted at rest with lifecycle expiry as a second line of defence.
BOT_DISPLAY_NAMErequiredNotetaker (recording)How the bot appears in the participant list. It should need no explanation.
ANNOUNCEMENT_TEXTrequired./config/announcement.mdWhat the bot says on joining. Configuration so it can differ by jurisdiction.
RECORDING_RETENTION_DAYSrequired30Per your written policy. Deletion is automatic and logged.
TRANSCRIPT_RETENTION_DAYSrequired365Usually longer than the recording, and a separate decision rather than an inherited one.
CONSENT_RETENTION_DAYSrequired2555Longest, because it is the evidence the recording was disclosed.

The build, in order

  1. Joining visibly and announcing

    The bot joins under a name that says what it is and tells the room.

    1. Something nobody has to ask about. This is the cheapest trust you will ever buy.

      Files src/join.ts

    2. In the first seconds, with the time stored on the meeting.

      Files src/announce.ts

    3. Recording ends, file removed. Logged and ignored is the failure mode to avoid.

      Files src/consent.ts

    done when · tick each as it passes
    watch out
    • Joining silently under an innocuous name is the behaviour that makes this category distrusted, and in several jurisdictions it is also unlawful. Name the participant so nobody has to ask, announce in the first seconds, and make objecting stop the recording rather than being logged and ignored.
  2. Audio worth transcribing

    The recording captures everyone clearly enough to transcribe.

    1. Loudness across participants, since people join at wildly different levels.

      Files src/capture.ts

    2. Anyone who never exceeds the noise floor. Report rather than transcribe a partial meeting.

      Files src/audio-check.ts

    3. Not later. Retention set when the file exists is retention that happens.

      Files src/storage.ts

    done when · tick each as it passes
    watch out
    • A recording that captured only the loudest participant transcribes into a one-sided conversation, and nobody notices until they read the notes. Check the audio for participants who never appear above the noise floor, and report that rather than transcribing a partial meeting as though it were complete.
  3. Transcript with speakers you can trust

    Every word has a time and an attribution you can correct.

    1. Requested explicitly. Segment level cannot support citation later.

      Files src/transcribe.ts

    2. Where available. Guessing from audio alone is much worse.

      Files src/speakers.ts

    3. Correcting one segment corrects that speaker throughout. Otherwise nobody corrects.

      Files src/speakers.ts

    done when · tick each as it passes
    watch out
    • Automatic speaker assignment is confidently wrong often enough that a summary attributing a commitment to the wrong person will happen, and that is a harder conversation than a missing note. Seed from the participant list, show low confidence visibly, and make correcting one speaker propagate to their other segments.
  4. Notes that cite the recording

    Every line in the summary points at a moment you can play.

    1. Structured output so parsing is not a regular expression.

      Files src/summarise.ts

    2. A pure function, unit tested with a crafted bad citation rather than waiting for a model to produce one.

      Files src/validate.tssrc/validate.test.ts

    3. Named in the cited text, or the action stays unassigned.

      Files src/actions.ts

    done when · tick each as it passes
    watch out
    • A model summarising a meeting will produce a decision that was discussed but not made, and an action item assigned to whoever was mentioned nearby. Both read plausibly and both cause real problems. Require citations, validate them, and require an explicit attribution in the cited text before naming an owner.
  5. Search across meetings

    You can find the meeting where something was said.

    1. So a result links to a moment rather than a meeting.

      Files src/search.ts

    2. Enforced in the query. Sharing beyond that is a deliberate action.

      Files src/access.ts

    3. As a non-attendee. The interface is not the test.

      Files test/access.test.ts

    done when · tick each as it passes
    watch out
    • Making every transcript searchable by everyone in an organisation turns a note-taking tool into a surveillance archive, and people change what they say in meetings once they know. Default to the attendees, make sharing deliberate, and enforce it in the query rather than the interface.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more meeting notes.