otterai-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.
@@ -0,0 +1,4 @@
1
+ 9a522cccf8902c8fa45b28c6a8639e2e588f9ee5
2
+ c12e0c0bcd80753eda3e3e20f5191df5ba6e9624
3
+ a44301791d7b34fc61c80d0a79c753a5e7439b99
4
+ 23c0d04d7774b8345ccdfe87445ef0340fd9a71f
@@ -0,0 +1,18 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ id-token: write
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ environment: pypi
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v7
17
+ - run: uvx hatch build
18
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,25 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - "*"
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Checkout code
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Set up uv
19
+ uses: astral-sh/setup-uv@v7
20
+
21
+ - name: Install dependencies
22
+ run: make init-dev
23
+
24
+ - name: Run tests
25
+ run: make test
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ build/
4
+ dist/
5
+ .coverage
6
+ cov.xml
7
+ .env
8
+ .venv/
9
+ tests/output/
@@ -0,0 +1,3 @@
1
+ {
2
+ "tabWidth": 4
3
+ }
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Chad Lohrli
4
+ Copyright (c) 2025 Eric Chan
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,19 @@
1
+ .PHONY: init-dev format lint test
2
+
3
+ init-dev:
4
+ uv venv || true
5
+ uv pip install .[dev]
6
+
7
+ format:
8
+ uv run ruff format .
9
+
10
+ lint:
11
+ uv run ruff check .
12
+
13
+ test:
14
+ rm -f cov.xml ||:
15
+ uv run pytest -s --cov=src/otterai \
16
+ --cov-report=lcov:lcov.info \
17
+ --cov-report=xml:cov.xml \
18
+ tests/
19
+ rm -f lcov.info .coverage ||:
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: otterai-cli
3
+ Version: 0.1.0
4
+ Summary: Unofficial Otter.ai CLI
5
+ Project-URL: Homepage, https://github.com/ericchan-su/otterai-cli
6
+ Project-URL: Repository, https://github.com/ericchan-su/otterai-cli
7
+ Project-URL: Issues, https://github.com/ericchan-su/otterai-cli/issues
8
+ Author-email: Eric Chan <eric.kahei.chan@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Utilities
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: click>=8.0
23
+ Requires-Dist: keyring>=23.0
24
+ Requires-Dist: requests
25
+ Requires-Dist: requests-toolbelt
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest; extra == 'dev'
28
+ Requires-Dist: pytest-cov; extra == 'dev'
29
+ Requires-Dist: responses; extra == 'dev'
30
+ Requires-Dist: ruff; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # otterai-cli
34
+
35
+ An unofficial command-line interface for [Otter.ai](https://otter.ai).
36
+
37
+ > **Note:** This project is not affiliated with or endorsed by Otter.ai / Aisense Inc.
38
+
39
+ ## Requirements
40
+
41
+ - Python 3.10+
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ uv tool install otterai-cli
47
+ ```
48
+
49
+ This makes the `otter` command available globally.
50
+
51
+ Or run directly without installing:
52
+
53
+ ```bash
54
+ uvx --from otterai-cli otter --help
55
+ ```
56
+
57
+ ## Setup
58
+
59
+ ```bash
60
+ otter login
61
+ ```
62
+
63
+ This prompts for your Otter.ai email and password. Credentials are saved to `~/.otterai/config.json`.
64
+
65
+ ### Alternative methods
66
+
67
+ **Environment variables** (take precedence over config file):
68
+
69
+ ```bash
70
+ export OTTERAI_USERNAME="your-email@example.com"
71
+ export OTTERAI_PASSWORD="your-password"
72
+ ```
73
+
74
+ ### Auth commands
75
+
76
+ ```bash
77
+ otter user # check current user
78
+ otter logout # remove saved credentials
79
+ ```
80
+
81
+ ## Usage
82
+
83
+ ```bash
84
+ otter speeches list # list all speeches
85
+ otter speeches list --days 7 # last 7 days
86
+ otter speeches list --folder "Work" # by folder name
87
+ otter speeches get SPEECH_ID # get speech details + transcript
88
+ otter speeches download SPEECH_ID -f txt # download as txt, pdf, mp3, docx, or srt
89
+ otter speeches search "keyword" SPEECH_ID # search within a speech
90
+ otter speakers list # list all speakers
91
+ otter folders list # list all folders
92
+ ```
93
+
94
+ Run `otter --help` or `otter <command> --help` for more options.
95
+
96
+ ### Important: Speech IDs (otid vs speech_id)
97
+
98
+ Otter.ai speeches have two identifiers:
99
+ - **`speech_id`** (e.g. `22WB27HAEBEJYFCA`) -- internal ID, does **NOT** work with API endpoints
100
+ - **`otid`** (e.g. `jqb7OHo6mrHtCuMkyLN0nUS8mxY`) -- the ID used in all API calls
101
+
102
+ All CLI commands that accept a `SPEECH_ID` argument expect the **otid** value. Use `otter speeches list` to find otids, or `otter speeches list --json | jq '.speeches[].otid'` for just the IDs.
103
+
104
+ ### Speeches
105
+
106
+ ```bash
107
+ # List all speeches
108
+ otter speeches list
109
+
110
+ # List with options
111
+ otter speeches list --page-size 10 --source owned
112
+
113
+ # List speeches from the last N days
114
+ otter speeches list --days 2
115
+
116
+ # List speeches in a specific folder (by name or ID)
117
+ otter speeches list --folder "CoverNode"
118
+
119
+ # Get a specific speech
120
+ otter speeches get SPEECH_ID
121
+
122
+ # Search within a speech
123
+ otter speeches search "search query" SPEECH_ID
124
+
125
+ # Download a speech (formats: txt, pdf, mp3, docx, srt)
126
+ otter speeches download SPEECH_ID --format txt
127
+
128
+ # Upload an audio file
129
+ otter speeches upload recording.mp4
130
+
131
+ # Move to trash
132
+ otter speeches trash SPEECH_ID
133
+
134
+ # Rename a speech
135
+ otter speeches rename SPEECH_ID "New Title"
136
+
137
+ # Move speeches to a folder (by name or ID)
138
+ otter speeches move SPEECH_ID --folder "CoverNode"
139
+ otter speeches move ID1 ID2 ID3 --folder "CoverNode"
140
+
141
+ # Move to a new folder (auto-create if it doesn't exist)
142
+ otter speeches move SPEECH_ID --folder "New Folder" --create
143
+ ```
144
+
145
+ ### Speakers
146
+
147
+ ```bash
148
+ # List all speakers
149
+ otter speakers list
150
+
151
+ # Create a new speaker
152
+ otter speakers create "Speaker Name"
153
+
154
+ # Tag a speaker on transcript segments
155
+ otter speakers tag SPEECH_ID SPEAKER_ID
156
+ otter speakers tag SPEECH_ID SPEAKER_ID --all
157
+ ```
158
+
159
+ ### Folders and Groups
160
+
161
+ ```bash
162
+ # List folders
163
+ otter folders list
164
+
165
+ # Create a folder
166
+ otter folders create "My Folder"
167
+
168
+ # Rename a folder
169
+ otter folders rename FOLDER_ID "New Name"
170
+
171
+ # List groups
172
+ otter groups list
173
+ ```
174
+
175
+ ### Configuration
176
+
177
+ ```bash
178
+ # Show current config
179
+ otter config show
180
+
181
+ # Clear saved config
182
+ otter config clear
183
+ ```
184
+
185
+ ### JSON Output
186
+
187
+ Most commands support `--json` flag for machine-readable output:
188
+
189
+ ```bash
190
+ otter speeches list --json
191
+ otter speakers list --json
192
+ ```
193
+
194
+ ## Development
195
+
196
+ ```bash
197
+ uv sync --dev # install dependencies
198
+ uv run pytest # run tests
199
+ ```
200
+
201
+ ## Acknowledgements
202
+
203
+ Based on [gmchad/otterai-api](https://github.com/gmchad/otterai-api) by Chad Lohrli, with CLI functionality from [PR #9](https://github.com/gmchad/otterai-api/pull/9) by [@andrewfurman](https://github.com/andrewfurman).
204
+
205
+ ## License
206
+
207
+ MIT
@@ -0,0 +1,175 @@
1
+ # otterai-cli
2
+
3
+ An unofficial command-line interface for [Otter.ai](https://otter.ai).
4
+
5
+ > **Note:** This project is not affiliated with or endorsed by Otter.ai / Aisense Inc.
6
+
7
+ ## Requirements
8
+
9
+ - Python 3.10+
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ uv tool install otterai-cli
15
+ ```
16
+
17
+ This makes the `otter` command available globally.
18
+
19
+ Or run directly without installing:
20
+
21
+ ```bash
22
+ uvx --from otterai-cli otter --help
23
+ ```
24
+
25
+ ## Setup
26
+
27
+ ```bash
28
+ otter login
29
+ ```
30
+
31
+ This prompts for your Otter.ai email and password. Credentials are saved to `~/.otterai/config.json`.
32
+
33
+ ### Alternative methods
34
+
35
+ **Environment variables** (take precedence over config file):
36
+
37
+ ```bash
38
+ export OTTERAI_USERNAME="your-email@example.com"
39
+ export OTTERAI_PASSWORD="your-password"
40
+ ```
41
+
42
+ ### Auth commands
43
+
44
+ ```bash
45
+ otter user # check current user
46
+ otter logout # remove saved credentials
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ```bash
52
+ otter speeches list # list all speeches
53
+ otter speeches list --days 7 # last 7 days
54
+ otter speeches list --folder "Work" # by folder name
55
+ otter speeches get SPEECH_ID # get speech details + transcript
56
+ otter speeches download SPEECH_ID -f txt # download as txt, pdf, mp3, docx, or srt
57
+ otter speeches search "keyword" SPEECH_ID # search within a speech
58
+ otter speakers list # list all speakers
59
+ otter folders list # list all folders
60
+ ```
61
+
62
+ Run `otter --help` or `otter <command> --help` for more options.
63
+
64
+ ### Important: Speech IDs (otid vs speech_id)
65
+
66
+ Otter.ai speeches have two identifiers:
67
+ - **`speech_id`** (e.g. `22WB27HAEBEJYFCA`) -- internal ID, does **NOT** work with API endpoints
68
+ - **`otid`** (e.g. `jqb7OHo6mrHtCuMkyLN0nUS8mxY`) -- the ID used in all API calls
69
+
70
+ All CLI commands that accept a `SPEECH_ID` argument expect the **otid** value. Use `otter speeches list` to find otids, or `otter speeches list --json | jq '.speeches[].otid'` for just the IDs.
71
+
72
+ ### Speeches
73
+
74
+ ```bash
75
+ # List all speeches
76
+ otter speeches list
77
+
78
+ # List with options
79
+ otter speeches list --page-size 10 --source owned
80
+
81
+ # List speeches from the last N days
82
+ otter speeches list --days 2
83
+
84
+ # List speeches in a specific folder (by name or ID)
85
+ otter speeches list --folder "CoverNode"
86
+
87
+ # Get a specific speech
88
+ otter speeches get SPEECH_ID
89
+
90
+ # Search within a speech
91
+ otter speeches search "search query" SPEECH_ID
92
+
93
+ # Download a speech (formats: txt, pdf, mp3, docx, srt)
94
+ otter speeches download SPEECH_ID --format txt
95
+
96
+ # Upload an audio file
97
+ otter speeches upload recording.mp4
98
+
99
+ # Move to trash
100
+ otter speeches trash SPEECH_ID
101
+
102
+ # Rename a speech
103
+ otter speeches rename SPEECH_ID "New Title"
104
+
105
+ # Move speeches to a folder (by name or ID)
106
+ otter speeches move SPEECH_ID --folder "CoverNode"
107
+ otter speeches move ID1 ID2 ID3 --folder "CoverNode"
108
+
109
+ # Move to a new folder (auto-create if it doesn't exist)
110
+ otter speeches move SPEECH_ID --folder "New Folder" --create
111
+ ```
112
+
113
+ ### Speakers
114
+
115
+ ```bash
116
+ # List all speakers
117
+ otter speakers list
118
+
119
+ # Create a new speaker
120
+ otter speakers create "Speaker Name"
121
+
122
+ # Tag a speaker on transcript segments
123
+ otter speakers tag SPEECH_ID SPEAKER_ID
124
+ otter speakers tag SPEECH_ID SPEAKER_ID --all
125
+ ```
126
+
127
+ ### Folders and Groups
128
+
129
+ ```bash
130
+ # List folders
131
+ otter folders list
132
+
133
+ # Create a folder
134
+ otter folders create "My Folder"
135
+
136
+ # Rename a folder
137
+ otter folders rename FOLDER_ID "New Name"
138
+
139
+ # List groups
140
+ otter groups list
141
+ ```
142
+
143
+ ### Configuration
144
+
145
+ ```bash
146
+ # Show current config
147
+ otter config show
148
+
149
+ # Clear saved config
150
+ otter config clear
151
+ ```
152
+
153
+ ### JSON Output
154
+
155
+ Most commands support `--json` flag for machine-readable output:
156
+
157
+ ```bash
158
+ otter speeches list --json
159
+ otter speakers list --json
160
+ ```
161
+
162
+ ## Development
163
+
164
+ ```bash
165
+ uv sync --dev # install dependencies
166
+ uv run pytest # run tests
167
+ ```
168
+
169
+ ## Acknowledgements
170
+
171
+ Based on [gmchad/otterai-api](https://github.com/gmchad/otterai-api) by Chad Lohrli, with CLI functionality from [PR #9](https://github.com/gmchad/otterai-api/pull/9) by [@andrewfurman](https://github.com/andrewfurman).
172
+
173
+ ## License
174
+
175
+ MIT
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "otterai-cli"
7
+ version = "0.1.0"
8
+ description = "Unofficial Otter.ai CLI"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Eric Chan", email = "eric.kahei.chan@gmail.com" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Intended Audience :: End Users/Desktop",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Utilities",
26
+ ]
27
+ dependencies = [
28
+ "click>=8.0",
29
+ "keyring>=23.0",
30
+ "requests",
31
+ "requests_toolbelt",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/ericchan-su/otterai-cli"
36
+ Repository = "https://github.com/ericchan-su/otterai-cli"
37
+ Issues = "https://github.com/ericchan-su/otterai-cli/issues"
38
+
39
+ [project.scripts]
40
+ otter = "otterai.cli:main"
41
+
42
+ [project.optional-dependencies]
43
+ dev = ["pytest", "pytest-cov", "responses", "ruff"]
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["src/otterai"]
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["tests"]
50
+ strict_markers = true
51
+
52
+ [tool.coverage.run]
53
+ source = ["src/otterai"]
54
+
55
+ [tool.coverage.report]
56
+ fail_under = 80
57
+ show_missing = true
@@ -0,0 +1,4 @@
1
+ import warnings
2
+
3
+ # Suppress urllib3 NotOpenSSLWarning on macOS systems using LibreSSL
4
+ warnings.filterwarnings("ignore", message=".*urllib3.*OpenSSL.*")
@@ -0,0 +1,26 @@
1
+ import click
2
+
3
+ from . import auth
4
+ from .speeches import speeches
5
+ from .speakers import speakers
6
+ from .folders import folders
7
+ from .groups import groups
8
+ from .config_cmd import config
9
+
10
+
11
+ @click.group()
12
+ @click.version_option(version="0.1.0", prog_name="otter")
13
+ def main():
14
+ """OtterAI CLI - Interact with Otter.ai from the command line."""
15
+ pass
16
+
17
+
18
+ # Register top-level commands (login, logout, user)
19
+ auth.register(main)
20
+
21
+ # Register subcommand groups
22
+ main.add_command(speeches)
23
+ main.add_command(speakers)
24
+ main.add_command(folders)
25
+ main.add_command(groups)
26
+ main.add_command(config)
@@ -0,0 +1,59 @@
1
+ import json
2
+ import sys
3
+
4
+ import click
5
+
6
+ from ..client import OtterAIClient
7
+ from ..config import clear_credentials, get_config_path, save_credentials
8
+ from .helpers import get_authenticated_client
9
+
10
+
11
+ def register(main: click.Group):
12
+ main.add_command(login)
13
+ main.add_command(logout)
14
+ main.add_command(user)
15
+
16
+
17
+ @click.command()
18
+ @click.option("--username", "-u", prompt=True, help="Otter.ai username (email)")
19
+ @click.option(
20
+ "--password", "-p", prompt=True, hide_input=True, help="Otter.ai password"
21
+ )
22
+ def login(username: str, password: str):
23
+ """Authenticate with Otter.ai and save credentials."""
24
+ client = OtterAIClient()
25
+ result = client.login(username, password)
26
+
27
+ if result["status"] != 200:
28
+ click.echo(f"Login failed: {result.get('data', {})}", err=True)
29
+ sys.exit(1)
30
+
31
+ backend = save_credentials(username, password)
32
+ user_data = result.get("data", {})
33
+ click.echo(f"Logged in as {user_data.get('email', username)}")
34
+ if backend == "keyring":
35
+ click.echo("Credentials saved to system keyring")
36
+ else:
37
+ click.echo(f"Credentials saved to {get_config_path()}")
38
+
39
+
40
+ @click.command()
41
+ def logout():
42
+ """Clear saved credentials."""
43
+ if clear_credentials():
44
+ click.echo("Credentials cleared.")
45
+ else:
46
+ click.echo("No saved credentials found.")
47
+
48
+
49
+ @click.command()
50
+ def user():
51
+ """Show current user information."""
52
+ client = get_authenticated_client()
53
+ result = client.get_user()
54
+
55
+ if result["status"] != 200:
56
+ click.echo(f"Failed to get user: {result}", err=True)
57
+ sys.exit(1)
58
+
59
+ click.echo(json.dumps(result["data"], indent=2))
@@ -0,0 +1,36 @@
1
+ import click
2
+
3
+ from ..config import clear_credentials, get_config_path, get_credential_backend, load_credentials
4
+
5
+
6
+ @click.group("config")
7
+ def config():
8
+ """Manage CLI configuration."""
9
+ pass
10
+
11
+
12
+ @config.command("show")
13
+ def config_show():
14
+ """Show current configuration."""
15
+ username, password = load_credentials()
16
+ config_path = get_config_path()
17
+ backend = get_credential_backend()
18
+
19
+ click.echo(f"Config file: {config_path}")
20
+ click.echo(f"Config exists: {config_path.exists()}")
21
+
22
+ if username:
23
+ click.echo(f"Backend: {backend}")
24
+ click.echo(f"Username: {username}")
25
+ click.echo(f"Password: {'*' * len(password) if password else 'Not set'}")
26
+ else:
27
+ click.echo("Not logged in.")
28
+
29
+
30
+ @config.command("clear")
31
+ def config_clear():
32
+ """Clear saved configuration."""
33
+ if clear_credentials():
34
+ click.echo("Configuration cleared.")
35
+ else:
36
+ click.echo("No configuration found.")