ffx-cli 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. ffx_cli-0.1.0/.github/workflows/ci.yml +31 -0
  2. ffx_cli-0.1.0/.github/workflows/publish.yml +44 -0
  3. ffx_cli-0.1.0/.gitignore +26 -0
  4. ffx_cli-0.1.0/CLAUDE.md +136 -0
  5. ffx_cli-0.1.0/PKG-INFO +9 -0
  6. ffx_cli-0.1.0/README.md +127 -0
  7. ffx_cli-0.1.0/ffx/__init__.py +1 -0
  8. ffx_cli-0.1.0/ffx/__main__.py +4 -0
  9. ffx_cli-0.1.0/ffx/api_client.py +261 -0
  10. ffx_cli-0.1.0/ffx/commands/__init__.py +29 -0
  11. ffx_cli-0.1.0/ffx/commands/action_items.py +74 -0
  12. ffx_cli-0.1.0/ffx/commands/auth.py +20 -0
  13. ffx_cli-0.1.0/ffx/commands/brief.py +45 -0
  14. ffx_cli-0.1.0/ffx/commands/export.py +129 -0
  15. ffx_cli-0.1.0/ffx/commands/get.py +64 -0
  16. ffx_cli-0.1.0/ffx/commands/list_cmd.py +68 -0
  17. ffx_cli-0.1.0/ffx/commands/search.py +62 -0
  18. ffx_cli-0.1.0/ffx/commands/speaker.py +84 -0
  19. ffx_cli-0.1.0/ffx/commands/summary.py +78 -0
  20. ffx_cli-0.1.0/ffx/commands/topics.py +82 -0
  21. ffx_cli-0.1.0/ffx/commands/transcript.py +91 -0
  22. ffx_cli-0.1.0/ffx/commands/week.py +121 -0
  23. ffx_cli-0.1.0/ffx/config.py +46 -0
  24. ffx_cli-0.1.0/ffx/models.py +107 -0
  25. ffx_cli-0.1.0/ffx/output.py +106 -0
  26. ffx_cli-0.1.0/pyproject.toml +30 -0
  27. ffx_cli-0.1.0/tests/__init__.py +0 -0
  28. ffx_cli-0.1.0/tests/conftest.py +10 -0
  29. ffx_cli-0.1.0/tests/test_api_client.py +108 -0
  30. ffx_cli-0.1.0/tests/test_commands/__init__.py +0 -0
  31. ffx_cli-0.1.0/tests/test_commands/test_action_items.py +64 -0
  32. ffx_cli-0.1.0/tests/test_commands/test_auth.py +33 -0
  33. ffx_cli-0.1.0/tests/test_commands/test_brief.py +58 -0
  34. ffx_cli-0.1.0/tests/test_commands/test_export.py +79 -0
  35. ffx_cli-0.1.0/tests/test_commands/test_get.py +61 -0
  36. ffx_cli-0.1.0/tests/test_commands/test_list.py +101 -0
  37. ffx_cli-0.1.0/tests/test_commands/test_search.py +72 -0
  38. ffx_cli-0.1.0/tests/test_commands/test_speaker.py +74 -0
  39. ffx_cli-0.1.0/tests/test_commands/test_summary.py +54 -0
  40. ffx_cli-0.1.0/tests/test_commands/test_topics.py +73 -0
  41. ffx_cli-0.1.0/tests/test_commands/test_transcript.py +100 -0
  42. ffx_cli-0.1.0/tests/test_commands/test_week.py +75 -0
  43. ffx_cli-0.1.0/tests/test_config.py +47 -0
  44. ffx_cli-0.1.0/uv.lock +881 -0
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ branches: ["main"]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v4
21
+ with:
22
+ version: "latest"
23
+
24
+ - name: Set up Python ${{ matrix.python-version }}
25
+ run: uv python install ${{ matrix.python-version }}
26
+
27
+ - name: Install dependencies
28
+ run: uv sync
29
+
30
+ - name: Run tests
31
+ run: uv run pytest --cov=ffx --cov-report=term-missing
@@ -0,0 +1,44 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Install uv
15
+ uses: astral-sh/setup-uv@v4
16
+ with:
17
+ version: "latest"
18
+
19
+ - name: Install dependencies
20
+ run: uv sync
21
+
22
+ - name: Run tests
23
+ run: uv run pytest
24
+
25
+ publish:
26
+ needs: test
27
+ runs-on: ubuntu-latest
28
+ environment: pypi
29
+ permissions:
30
+ id-token: write
31
+
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+
35
+ - name: Install uv
36
+ uses: astral-sh/setup-uv@v4
37
+ with:
38
+ version: "latest"
39
+
40
+ - name: Build
41
+ run: uv build
42
+
43
+ - name: Publish to PyPI
44
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,26 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .venv/
9
+ venv/
10
+ .env
11
+
12
+ # uv
13
+ .python-version
14
+
15
+ # ffx local state
16
+ .ffx/
17
+
18
+ # Planning docs (not committed)
19
+ TODOS.md
20
+ PLAN.md
21
+ *.plan.md
22
+
23
+ # Editor
24
+ .DS_Store
25
+ .vscode/
26
+ .idea/
@@ -0,0 +1,136 @@
1
+ # ffx — Fireflies CLI
2
+
3
+ ## Project
4
+
5
+ Python CLI tool for the Fireflies.ai GraphQL API. Wraps both primitive API operations
6
+ and higher-level workflow commands. Designed for developer scripting, shell pipelines,
7
+ and AI agent tooling.
8
+
9
+ ## Dev Setup
10
+
11
+ ```bash
12
+ uv sync
13
+ uv pip install -e .
14
+ ```
15
+
16
+ ## Tests
17
+
18
+ ```bash
19
+ uv run pytest
20
+ uv run pytest --cov=ffx --cov-report=term-missing # with coverage
21
+ ```
22
+
23
+ Test strategy: HTTP requests mocked with `respx`. CLI commands tested with
24
+ `typer.testing.CliRunner`. Config tests use `tmp_path` + `FFX_HOME` env override.
25
+
26
+ ## Build and Publish
27
+
28
+ ```bash
29
+ uv build
30
+ uv publish # requires PYPI_API_TOKEN env var
31
+ ```
32
+
33
+ CI/CD: GitHub Actions triggers on git tag push → tests → build → publish.
34
+
35
+ ## Locked Architecture Decisions
36
+
37
+ These were decided during design review. Do not change without a documented reason.
38
+
39
+ **CLI framework: Typer** (not Click)
40
+ - Type hint-driven argument parsing
41
+ - Auto-generates `--help` from docstrings
42
+ - Better DX for flat command tree design
43
+
44
+ **GraphQL client: gql[httpx]** via `HTTPXTransport` (not raw httpx)
45
+ - Handles GraphQL error envelope automatically
46
+ - Standard Python GraphQL client as of 2025
47
+
48
+ **API-direct architecture** (no local SQLite cache)
49
+ - All commands hit the Fireflies API directly
50
+ - No sync layer, no local DB, no cache
51
+ - Simpler, fewer failure modes, always-fresh data
52
+
53
+ ## Dropped from Scope
54
+
55
+ - `ffx objections` — pattern-matched from summary text, unreliable. Do not add back
56
+ without a structured Fireflies API field or explicit --llm flag.
57
+ - `ffx risks` — same reason as objections.
58
+ - `ffx sync` / local SQLite — dropped after review. API-direct is simpler and sufficient.
59
+ Commits exist in git history if ever needed.
60
+ - `ffx serve --webhook` — cron + `ffx` commands covers this.
61
+ - MCP adapter — Fireflies already has one.
62
+ - `ffx export notion` — OAuth complexity for niche use case.
63
+
64
+ ## Project Structure
65
+
66
+ ```
67
+ ffx/
68
+ ├── __init__.py
69
+ ├── api_client.py # Fireflies GraphQL client (gql + httpx)
70
+ ├── models.py # Dataclasses: Transcript, ActionItem, Speaker, Topic, Summary
71
+ ├── config.py # Config file (~/.ffx/config.yaml) read/write
72
+ ├── output.py # Rich terminal formatting + JSON envelope
73
+ └── commands/
74
+ ├── __init__.py
75
+ ├── auth.py # ffx auth
76
+ ├── list_cmd.py # ffx list
77
+ ├── get.py # ffx get
78
+ ├── search.py # ffx search
79
+ ├── summary.py # ffx summary
80
+ ├── export.py # ffx export
81
+ ├── brief.py # ffx brief
82
+ ├── action_items.py # ffx action-items
83
+ ├── topics.py # ffx topics
84
+ ├── speaker.py # ffx speaker
85
+ └── week.py # ffx week / ffx month
86
+ tests/
87
+ ├── conftest.py # Shared fixtures
88
+ ├── test_api_client.py
89
+ ├── test_config.py
90
+ └── test_commands/
91
+ └── test_*.py # One test file per command
92
+ ```
93
+
94
+ ## Fireflies API Notes
95
+
96
+ - Endpoint: `https://api.fireflies.ai/graphql`
97
+ - Auth: `Authorization: Bearer <api_key>`
98
+ - `participants` field is `[String!]` (emails), not objects
99
+ - Summary fields (`action_items`, `bullet_gist`, `topics_discussed`, `outline`,
100
+ `shorthand_bullet`) are **strings** (newline-delimited), not lists. Only `keywords`
101
+ is a proper list. Use `Summary.*_list` properties to split into lists.
102
+ - Speaker analytics path: `analytics.speakers` (not `speaker_talk_time_percentage`)
103
+ - Speaker fields: `name`, `speaker_id`, `duration`, `duration_pct`, `word_count`,
104
+ `words_per_minute`, `filler_words`, `questions`, `longest_monologue`, `monologues_count`
105
+
106
+ ## Error Handling
107
+
108
+ Standard exit codes:
109
+ - 0: success
110
+ - 1: user error (bad flag, missing required arg)
111
+ - 2: API error (Fireflies unreachable, rate limit exhausted)
112
+ - 3: not found
113
+
114
+ JSON error envelope (always use in `--json` mode):
115
+ ```json
116
+ {"error": "message", "code": "ERROR_CODE", "hint": "Run: ffx auth"}
117
+ ```
118
+
119
+ On AuthError: print "API key may have expired. Run: ffx auth"
120
+
121
+ ## Output Conventions
122
+
123
+ Standard JSON wrapper (all list-returning commands):
124
+ ```json
125
+ {"source": "fireflies", "generated_at": "ISO_TIMESTAMP", "filters": {}, "results": []}
126
+ ```
127
+
128
+ Global flags all commands support: `--json`
129
+ List commands also support: `--days N`, `--limit N`
130
+
131
+ Default `--limit` for `ffx list`: 10.
132
+
133
+ ## References
134
+
135
+ - Fireflies API docs: https://docs.fireflies.ai
136
+ - CEO plan: ~/.gstack/projects/fireflies-cli/ceo-plans/2026-03-27-ffx-cli.md
ffx_cli-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: ffx-cli
3
+ Version: 0.1.0
4
+ Summary: Fireflies.ai CLI
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: gql[httpx]>=3.5
7
+ Requires-Dist: pyyaml>=6.0
8
+ Requires-Dist: rich>=13.7
9
+ Requires-Dist: typer>=0.12
@@ -0,0 +1,127 @@
1
+ # ffx — Fireflies CLI
2
+
3
+ A command-line tool for the [Fireflies.ai](https://fireflies.ai) API. List meetings, read transcripts, pull action items, view speaker analytics, and export to JSON/Markdown/Obsidian/CSV.
4
+
5
+ All commands output JSON by default — pipe to `jq`, feed to scripts, or use `--table` for human-readable output.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install ffx-cli
11
+ # or
12
+ uv tool install ffx-cli
13
+ ```
14
+
15
+ ## Setup
16
+
17
+ Get your API key from [Fireflies Settings](https://app.fireflies.ai/integrations/custom/fireflies) and either:
18
+
19
+ ```bash
20
+ ffx auth # interactive prompt, stores in ~/.ffx/config.yaml
21
+ export FIREFLIES_API_KEY=your-key # env var, overrides config file
22
+ ```
23
+
24
+ ## Commands
25
+
26
+ ### Basics
27
+
28
+ ```bash
29
+ ffx list # recent meetings (JSON)
30
+ ffx list --table # rich formatted table
31
+ ffx list --days 7 --limit 20 # last 7 days, up to 20
32
+ ffx list --participant alice@co.com # filter by attendee (repeatable)
33
+
34
+ ffx get <id> # single meeting details
35
+ ffx search "quarterly review" # keyword search
36
+ ```
37
+
38
+ ### Summaries & briefs
39
+
40
+ ```bash
41
+ ffx summary <id> # AI-generated summary
42
+ ffx brief <id> # formatted brief: gist, key points, action items, topics
43
+ ffx brief <id> --table # rich formatted version
44
+ ```
45
+
46
+ ### Action items & topics
47
+
48
+ ```bash
49
+ ffx action-items # action items from last 7 days
50
+ ffx action-items --days 30 --filter alice
51
+ ffx topics # topics across recent meetings
52
+ ffx topics <id> # topics from one meeting
53
+ ```
54
+
55
+ ### Full transcript
56
+
57
+ ```bash
58
+ ffx transcript <id> # full text with speakers + timestamps (JSON)
59
+ ffx transcript <id> --table # readable format with timestamps
60
+ ffx transcript <id> --no-timestamps # just speaker: text
61
+ ffx transcript <id> --no-speakers # just text
62
+ ```
63
+
64
+ ### Speaker analytics
65
+
66
+ ```bash
67
+ ffx speaker <id> # talk time, word count, wpm, fillers, questions
68
+ ffx speaker <id> --table # visual bar chart
69
+ ```
70
+
71
+ ### Weekly & monthly rollups
72
+
73
+ ```bash
74
+ ffx week # this week: meeting count, hours, action items, top topics
75
+ ffx month # this month
76
+ ffx week --table # rich formatted
77
+ ```
78
+
79
+ ### Export
80
+
81
+ ```bash
82
+ ffx export <id> # JSON (default)
83
+ ffx export <id> --format md # Markdown
84
+ ffx export <id> --format obsidian # Markdown with YAML frontmatter
85
+ ffx export <id> --format csv # CSV
86
+ ```
87
+
88
+ ## Scripting examples
89
+
90
+ ```bash
91
+ # Get all action items from last week as JSON
92
+ ffx action-items --days 7 | jq '.results[].text'
93
+
94
+ # Export all recent meetings to markdown files
95
+ ffx list --limit 50 | jq -r '.results[].id' | while read id; do
96
+ ffx export "$id" --format md > "meeting-$id.md"
97
+ done
98
+
99
+ # Who talks the most in a meeting?
100
+ ffx speaker <id> | jq '.speakers | sort_by(.duration_pct) | reverse | .[0].name'
101
+
102
+ # Search and get briefs
103
+ ffx search "product launch" | jq -r '.results[].id' | xargs -I{} ffx brief {}
104
+ ```
105
+
106
+ ## Auth
107
+
108
+ Two methods, env var takes precedence:
109
+
110
+ | Method | How |
111
+ |--------|-----|
112
+ | Config file | `ffx auth` stores key in `~/.ffx/config.yaml` (chmod 600) |
113
+ | Env var | `export FIREFLIES_API_KEY=your-key` |
114
+
115
+ ## Development
116
+
117
+ ```bash
118
+ git clone https://github.com/bobtista/fireflies-cli.git
119
+ cd fireflies-cli
120
+ uv sync
121
+ uv pip install -e .
122
+ uv run pytest
123
+ ```
124
+
125
+ ## License
126
+
127
+ MIT
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from ffx.commands import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
@@ -0,0 +1,261 @@
1
+ from __future__ import annotations
2
+
3
+ from gql import Client, gql
4
+ from gql.transport.httpx import HTTPXTransport
5
+
6
+ from ffx.models import (
7
+ ActionItem,
8
+ Sentence,
9
+ Speaker,
10
+ Summary,
11
+ Topic,
12
+ Transcript,
13
+ )
14
+
15
+ FIREFLIES_API_URL = "https://api.fireflies.ai/graphql"
16
+
17
+ # participants is [String!] (emails), not objects
18
+ # analytics.speakers is the correct path (not speaker_talk_time_percentage)
19
+ LIST_TRANSCRIPTS_QUERY = gql("""
20
+ query Transcripts($limit: Int, $skip: Int, $keyword: String, $fromDate: DateTime, $toDate: DateTime, $participants: [String!]) {
21
+ transcripts(limit: $limit, skip: $skip, keyword: $keyword, fromDate: $fromDate, toDate: $toDate, participants: $participants) {
22
+ id
23
+ title
24
+ date
25
+ duration
26
+ organizer_email
27
+ participants
28
+ summary {
29
+ action_items keywords overview gist bullet_gist
30
+ shorthand_bullet outline short_summary short_overview
31
+ meeting_type topics_discussed
32
+ }
33
+ analytics {
34
+ speakers {
35
+ speaker_id name duration duration_pct word_count
36
+ words_per_minute filler_words questions
37
+ longest_monologue monologues_count
38
+ }
39
+ }
40
+ }
41
+ }
42
+ """)
43
+
44
+ GET_TRANSCRIPT_QUERY = gql("""
45
+ query Transcript($id: String!) {
46
+ transcript(id: $id) {
47
+ id
48
+ title
49
+ date
50
+ duration
51
+ organizer_email
52
+ participants
53
+ summary {
54
+ action_items keywords overview gist bullet_gist
55
+ shorthand_bullet outline short_summary short_overview
56
+ meeting_type topics_discussed
57
+ }
58
+ analytics {
59
+ speakers {
60
+ speaker_id name duration duration_pct word_count
61
+ words_per_minute filler_words questions
62
+ longest_monologue monologues_count
63
+ }
64
+ }
65
+ }
66
+ }
67
+ """)
68
+
69
+
70
+ GET_TRANSCRIPT_WITH_SENTENCES_QUERY = gql("""
71
+ query Transcript($id: String!) {
72
+ transcript(id: $id) {
73
+ id
74
+ title
75
+ date
76
+ duration
77
+ organizer_email
78
+ participants
79
+ summary {
80
+ action_items keywords overview gist bullet_gist
81
+ shorthand_bullet outline short_summary short_overview
82
+ meeting_type topics_discussed
83
+ }
84
+ analytics {
85
+ speakers {
86
+ speaker_id name duration duration_pct word_count
87
+ words_per_minute filler_words questions
88
+ longest_monologue monologues_count
89
+ }
90
+ }
91
+ sentences {
92
+ index
93
+ speaker_name
94
+ speaker_id
95
+ text
96
+ raw_text
97
+ start_time
98
+ end_time
99
+ }
100
+ }
101
+ }
102
+ """)
103
+
104
+
105
+ class AuthError(Exception):
106
+ pass
107
+
108
+
109
+ class ApiError(Exception):
110
+ pass
111
+
112
+
113
+ def _parse_transcript(raw: dict) -> Transcript:
114
+ summary_raw = raw.get("summary") or {}
115
+ analytics_raw = raw.get("analytics") or {}
116
+
117
+ # Most summary fields come back as strings (with newlines), not lists.
118
+ # Only keywords is a proper list. We store raw and use .action_items_list etc.
119
+ summary = Summary(
120
+ overview=summary_raw.get("overview"),
121
+ short_overview=summary_raw.get("short_overview"),
122
+ gist=summary_raw.get("gist"),
123
+ short_summary=summary_raw.get("short_summary"),
124
+ bullet_gist=summary_raw.get("bullet_gist"),
125
+ shorthand_bullet=summary_raw.get("shorthand_bullet"),
126
+ action_items=summary_raw.get("action_items"),
127
+ keywords=summary_raw.get("keywords") or [],
128
+ topics_discussed=summary_raw.get("topics_discussed"),
129
+ outline=summary_raw.get("outline"),
130
+ meeting_type=summary_raw.get("meeting_type"),
131
+ )
132
+
133
+ participants = raw.get("participants") or []
134
+
135
+ speakers = [
136
+ Speaker(
137
+ name=s.get("name", ""),
138
+ speaker_id=s.get("speaker_id"),
139
+ duration=s.get("duration", 0),
140
+ duration_pct=s.get("duration_pct", 0.0),
141
+ word_count=s.get("word_count", 0),
142
+ words_per_minute=s.get("words_per_minute", 0.0),
143
+ filler_words=s.get("filler_words", 0),
144
+ questions=s.get("questions", 0),
145
+ longest_monologue=s.get("longest_monologue", 0),
146
+ monologues_count=s.get("monologues_count", 0),
147
+ )
148
+ for s in (analytics_raw.get("speakers") or [])
149
+ ]
150
+
151
+ action_items = [
152
+ ActionItem(text=item, transcript_id=raw.get("id"))
153
+ for item in summary.action_items_list
154
+ ]
155
+
156
+ topics = [
157
+ Topic(text=t, transcript_id=raw.get("id"))
158
+ for t in summary.topics_list
159
+ ]
160
+
161
+ sentences = [
162
+ Sentence(
163
+ index=s.get("index", 0),
164
+ speaker_name=s.get("speaker_name", ""),
165
+ speaker_id=s.get("speaker_id"),
166
+ text=s.get("text", ""),
167
+ raw_text=s.get("raw_text", ""),
168
+ start_time=s.get("start_time", 0.0),
169
+ end_time=s.get("end_time", 0.0),
170
+ )
171
+ for s in (raw.get("sentences") or [])
172
+ ]
173
+
174
+ return Transcript(
175
+ id=raw["id"],
176
+ title=raw.get("title", "Untitled"),
177
+ date=str(raw.get("date", "")),
178
+ duration=raw.get("duration", 0),
179
+ organizer_email=raw.get("organizer_email"),
180
+ participants=participants,
181
+ summary=summary,
182
+ speakers=speakers,
183
+ action_items=action_items,
184
+ topics=topics,
185
+ sentences=sentences,
186
+ raw=raw,
187
+ )
188
+
189
+
190
+ class FirefliesClient:
191
+ def __init__(self, api_key: str) -> None:
192
+ transport = HTTPXTransport(
193
+ url=FIREFLIES_API_URL,
194
+ headers={"Authorization": f"Bearer {api_key}"},
195
+ )
196
+ self._client = Client(transport=transport, fetch_schema_from_transport=False)
197
+
198
+ def _execute(self, query, variables: dict | None = None) -> dict:
199
+ try:
200
+ result = self._client.execute(query, variable_values=variables or {})
201
+ return result
202
+ except Exception as e:
203
+ msg = str(e).lower()
204
+ if "429" in str(e) or "rate limit" in msg:
205
+ raise ApiError("rate limit exceeded — retry later") from e
206
+ if "unauthenticated" in msg or "unauthorized" in msg or "401" in str(e):
207
+ raise AuthError("API key missing or expired") from e
208
+ if any(code in str(e) for code in ("500", "502", "503")):
209
+ raise ApiError(f"Fireflies API error: {e}") from e
210
+ raise ApiError(str(e)) from e
211
+
212
+ def list_transcripts(
213
+ self,
214
+ limit: int = 10,
215
+ skip: int = 0,
216
+ keyword: str | None = None,
217
+ from_date: str | None = None,
218
+ to_date: str | None = None,
219
+ participants: list[str] | None = None,
220
+ ) -> list[Transcript]:
221
+ vars: dict = {"limit": limit, "skip": skip}
222
+ if keyword:
223
+ vars["keyword"] = keyword
224
+ if from_date:
225
+ vars["fromDate"] = from_date
226
+ if to_date:
227
+ vars["toDate"] = to_date
228
+ if participants:
229
+ vars["participants"] = participants
230
+ result = self._execute(LIST_TRANSCRIPTS_QUERY, vars)
231
+ return [_parse_transcript(t) for t in (result.get("transcripts") or [])]
232
+
233
+ def get_transcript(self, transcript_id: str) -> Transcript | None:
234
+ result = self._execute(GET_TRANSCRIPT_QUERY, {"id": transcript_id})
235
+ raw = result.get("transcript")
236
+ if raw is None:
237
+ return None
238
+ return _parse_transcript(raw)
239
+
240
+ def get_transcript_with_sentences(self, transcript_id: str) -> Transcript | None:
241
+ result = self._execute(GET_TRANSCRIPT_WITH_SENTENCES_QUERY, {"id": transcript_id})
242
+ raw = result.get("transcript")
243
+ if raw is None:
244
+ return None
245
+ return _parse_transcript(raw)
246
+
247
+ def search_transcripts(
248
+ self,
249
+ query: str,
250
+ limit: int = 20,
251
+ from_date: str | None = None,
252
+ to_date: str | None = None,
253
+ participants: list[str] | None = None,
254
+ ) -> list[Transcript]:
255
+ return self.list_transcripts(
256
+ limit=limit,
257
+ keyword=query,
258
+ from_date=from_date,
259
+ to_date=to_date,
260
+ participants=participants,
261
+ )
@@ -0,0 +1,29 @@
1
+ import typer
2
+ from ffx.commands.auth import auth
3
+ from ffx.commands.list_cmd import list_cmd
4
+ from ffx.commands.get import get
5
+ from ffx.commands.search import search
6
+ from ffx.commands.summary import summary
7
+ from ffx.commands.brief import brief
8
+ from ffx.commands.action_items import action_items
9
+ from ffx.commands.export import export
10
+ from ffx.commands.topics import topics
11
+ from ffx.commands.speaker import speaker
12
+ from ffx.commands.transcript import transcript
13
+ from ffx.commands.week import week, month
14
+
15
+ app = typer.Typer(name="ffx", help="Fireflies.ai CLI", no_args_is_help=True)
16
+
17
+ app.command()(auth)
18
+ app.command("list")(list_cmd)
19
+ app.command()(get)
20
+ app.command()(search)
21
+ app.command()(summary)
22
+ app.command()(brief)
23
+ app.command("action-items")(action_items)
24
+ app.command()(export)
25
+ app.command()(transcript)
26
+ app.command()(topics)
27
+ app.command()(speaker)
28
+ app.command()(week)
29
+ app.command()(month)