tarnished-cli 0.1.7__tar.gz → 0.2.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.
- tarnished_cli-0.2.0/.gitignore +66 -0
- tarnished_cli-0.2.0/LICENSE +21 -0
- tarnished_cli-0.2.0/PKG-INFO +187 -0
- tarnished_cli-0.2.0/README.md +169 -0
- tarnished_cli-0.2.0/examples/evidence/current-meaning.json +1 -0
- tarnished_cli-0.2.0/examples/evidence/document-text.json +4 -0
- tarnished_cli-0.2.0/examples/evidence/history-correction.json +1 -0
- tarnished_cli-0.2.0/examples/evidence/insights.json +1 -0
- tarnished_cli-0.2.0/examples/evidence/report-intent-pipeline.json +5 -0
- tarnished_cli-0.2.0/examples/evidence/report-intent.json +5 -0
- tarnished_cli-0.2.0/examples/evidence/response-clear.json +1 -0
- tarnished_cli-0.2.0/examples/evidence/response-record.json +7 -0
- tarnished_cli-0.2.0/examples/evidence/transcript-edit.json +9 -0
- tarnished_cli-0.2.0/examples/evidence/transcript-paste.json +4 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/pyproject.toml +11 -3
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/auth_diagnostics.py +4 -3
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/auth_storage.py +19 -9
- tarnished_cli-0.2.0/src/tarnished_cli/client.py +355 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/admin.py +0 -19
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/analytics.py +49 -5
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/applications.py +105 -4
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/auth.py +20 -14
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/dashboard.py +18 -4
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/exports.py +2 -3
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/imports.py +44 -7
- tarnished_cli-0.2.0/src/tarnished_cli/commands/job_leads.py +211 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/preferences.py +3 -1
- tarnished_cli-0.2.0/src/tarnished_cli/commands/reports.py +230 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/rounds.py +68 -6
- tarnished_cli-0.2.0/src/tarnished_cli/commands/transcriptions.py +197 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/config.py +1 -32
- tarnished_cli-0.2.0/src/tarnished_cli/files.py +12 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/input.py +5 -1
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/main.py +31 -20
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/models/__init__.py +10 -8
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/models/requests.py +213 -4
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/output.py +11 -0
- tarnished_cli-0.2.0/src/tarnished_cli/polling.py +12 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/state.py +19 -15
- tarnished_cli-0.2.0/tests/conftest.py +56 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_admin_commands.py +83 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_auth_commands.py +23 -124
- tarnished_cli-0.2.0/tests/test_auth_storage.py +117 -0
- tarnished_cli-0.2.0/tests/test_cli_config.py +180 -0
- tarnished_cli-0.2.0/tests/test_cli_connection_docs.py +92 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_client.py +43 -0
- tarnished_cli-0.2.0/tests/test_date_requests.py +64 -0
- tarnished_cli-0.2.0/tests/test_documented_commands.py +49 -0
- tarnished_cli-0.2.0/tests/test_download_output.py +118 -0
- tarnished_cli-0.2.0/tests/test_evidence_commands.py +276 -0
- tarnished_cli-0.2.0/tests/test_evidence_requests.py +92 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_import_commands.py +5 -2
- tarnished_cli-0.2.0/tests/test_import_wait.py +176 -0
- tarnished_cli-0.2.0/tests/test_lead_capture.py +264 -0
- tarnished_cli-0.2.0/tests/test_local_file_errors.py +43 -0
- tarnished_cli-0.2.0/tests/test_report_wait.py +115 -0
- tarnished_cli-0.2.0/tests/test_reports_commands.py +187 -0
- tarnished_cli-0.2.0/tests/test_shipped_promises.py +85 -0
- tarnished_cli-0.2.0/tests/test_transcript_document_commands.py +170 -0
- tarnished_cli-0.2.0/tests/test_transcriptions_commands.py +275 -0
- tarnished_cli-0.2.0/tests/test_transport_contract.py +368 -0
- tarnished_cli-0.2.0/tests/test_user_settings_commands.py +83 -0
- tarnished_cli-0.2.0/uv.lock +655 -0
- tarnished_cli-0.1.7/.gitignore +0 -181
- tarnished_cli-0.1.7/PKG-INFO +0 -103
- tarnished_cli-0.1.7/README.md +0 -91
- tarnished_cli-0.1.7/skills/tarnished-cli/SKILL.md +0 -173
- tarnished_cli-0.1.7/skills/tarnished-cli/evals/trigger-eval.json +0 -82
- tarnished_cli-0.1.7/src/tarnished_cli/client.py +0 -224
- tarnished_cli-0.1.7/src/tarnished_cli/commands/job_leads.py +0 -140
- tarnished_cli-0.1.7/tests/conftest.py +0 -15
- tarnished_cli-0.1.7/tests/test_auth_storage.py +0 -53
- tarnished_cli-0.1.7/tests/test_cli_config.py +0 -9
- tarnished_cli-0.1.7/tests/test_repo_skill.py +0 -76
- tarnished_cli-0.1.7/tests/test_skill_eval_fixture.py +0 -31
- tarnished_cli-0.1.7/tests/test_user_settings_commands.py +0 -53
- tarnished_cli-0.1.7/uv.lock +0 -614
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/__init__.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/__init__.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/profile.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/round_types.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/statuses.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/commands/user_settings.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/src/tarnished_cli/models/auth.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_analytics_commands.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_applications_commands.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_dashboard_commands.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_export_commands.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_help_surfaces.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_job_leads_commands.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_main.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_output_contract.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_profile_commands.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_round_types_commands.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_rounds_commands.py +0 -0
- {tarnished_cli-0.1.7 → tarnished_cli-0.2.0}/tests/test_statuses_commands.py +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Dependencies and build output
|
|
2
|
+
.venv/
|
|
3
|
+
node_modules/
|
|
4
|
+
.yarn/
|
|
5
|
+
.pnp.*
|
|
6
|
+
.uv/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
*.tsbuildinfo
|
|
12
|
+
|
|
13
|
+
# Local configuration and data
|
|
14
|
+
.env
|
|
15
|
+
.env.*
|
|
16
|
+
!.env.example
|
|
17
|
+
.envrc.local
|
|
18
|
+
.direnv/
|
|
19
|
+
data/
|
|
20
|
+
uploads/
|
|
21
|
+
postgres_data/
|
|
22
|
+
*.db
|
|
23
|
+
*.sqlite
|
|
24
|
+
*.sqlite3
|
|
25
|
+
docker-compose.override.yml
|
|
26
|
+
docker-compose.override.yaml
|
|
27
|
+
backend/requirements.txt
|
|
28
|
+
|
|
29
|
+
# Caches and test output
|
|
30
|
+
__pycache__/
|
|
31
|
+
*.py[cod]
|
|
32
|
+
.ruff_cache/
|
|
33
|
+
.pytest_cache/
|
|
34
|
+
.mypy_cache/
|
|
35
|
+
.dmypy.json
|
|
36
|
+
.tox/
|
|
37
|
+
.vite/
|
|
38
|
+
.eslintcache
|
|
39
|
+
.cache/
|
|
40
|
+
.npm/
|
|
41
|
+
.coverage
|
|
42
|
+
coverage/
|
|
43
|
+
htmlcov/
|
|
44
|
+
*.cover
|
|
45
|
+
*.lcov
|
|
46
|
+
*.log
|
|
47
|
+
|
|
48
|
+
# Editor and operating system files
|
|
49
|
+
.vscode/
|
|
50
|
+
.idea/
|
|
51
|
+
.DS_Store
|
|
52
|
+
Thumbs.db
|
|
53
|
+
*.swp
|
|
54
|
+
*.swo
|
|
55
|
+
*~
|
|
56
|
+
*.tmp
|
|
57
|
+
*.bak
|
|
58
|
+
/core
|
|
59
|
+
core.*
|
|
60
|
+
tmp/
|
|
61
|
+
|
|
62
|
+
# Browser packages
|
|
63
|
+
*.xpi
|
|
64
|
+
extension/tarnished-chrome.zip
|
|
65
|
+
extension/tarnished-firefox.zip
|
|
66
|
+
extension/package-lock.json
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Marko Onakic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tarnished-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Command-line interface for Tarnished
|
|
5
|
+
Project-URL: Homepage, https://markoonakic.github.io/tarnished/
|
|
6
|
+
Project-URL: Repository, https://github.com/markoonakic/tarnished
|
|
7
|
+
Project-URL: Documentation, https://markoonakic.github.io/tarnished/how-to/use-the-cli/
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Requires-Dist: email-validator>=2.0.0
|
|
12
|
+
Requires-Dist: httpx>=0.28.1
|
|
13
|
+
Requires-Dist: keyring>=25.7.0
|
|
14
|
+
Requires-Dist: pydantic>=2.12.0
|
|
15
|
+
Requires-Dist: typer>=0.23.1
|
|
16
|
+
Requires-Dist: tzlocal>=5.3.1
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Tarnished CLI
|
|
20
|
+
|
|
21
|
+
Command-line interface for Tarnished, version **0.2.0**. Use a matching Tarnished backend.
|
|
22
|
+
|
|
23
|
+
**Documentation:** https://markoonakic.github.io/tarnished/
|
|
24
|
+
|
|
25
|
+
For the user-facing CLI guide, see:
|
|
26
|
+
- https://markoonakic.github.io/tarnished/how-to/use-the-cli/
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
Install version 0.2.0 from PyPI once that release is available:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv tool install tarnished-cli==0.2.0
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
To install this checkout, including before a release is published, run from the repository root:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv tool install ./cli
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Homebrew convenience path (the tap can lag the PyPI release):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
brew tap markoonakic/tap
|
|
46
|
+
brew install tarnished-cli
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
To install a downloaded wheel:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv tool install ./dist/tarnished_cli-<version>-py3-none-any.whl
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Authentication
|
|
56
|
+
|
|
57
|
+
Tarnished CLI is API-key-first.
|
|
58
|
+
|
|
59
|
+
1. Create or rotate the API key in the Tarnished web app.
|
|
60
|
+
2. Prefer the **CLI** preset so the key includes the scopes the CLI expects.
|
|
61
|
+
3. Validate and store the key locally:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
tarnished auth init --api-key '...'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
4. Run the auth doctor to confirm the stored key, live auth check, and required
|
|
68
|
+
CLI scopes all pass:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
tarnished auth doctor
|
|
72
|
+
tarnished auth whoami
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
5. Clear the locally stored key when needed:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
tarnished auth api-key clear
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The web app remains the source of truth for API keys. The CLI does **not**
|
|
82
|
+
create, rotate, revoke, or otherwise manage remote API keys.
|
|
83
|
+
|
|
84
|
+
The CLI uses the system keyring when available. Otherwise, it stores the key in
|
|
85
|
+
a private file in the CLI config directory. `TARNISHED_API_KEY` overrides stored
|
|
86
|
+
credentials. Clearing a stored key does not unset this environment variable.
|
|
87
|
+
|
|
88
|
+
## Usage
|
|
89
|
+
|
|
90
|
+
Global options go before the command:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
tarnished --base-url https://tarnished.example.com auth status
|
|
94
|
+
tarnished --profile work --json applications list
|
|
95
|
+
tarnished applications --help
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Create `config.json` in `~/.config/tarnished` (or
|
|
99
|
+
`$XDG_CONFIG_HOME/tarnished`) to set profiles:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"default_profile": "default",
|
|
104
|
+
"profiles": {
|
|
105
|
+
"default": { "base_url": "http://127.0.0.1:5577", "output": "json" },
|
|
106
|
+
"work": { "base_url": "https://tarnished.example.com", "output": "json" }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`--config-dir` and `TARNISHED_CONFIG_DIR` override this directory. Server URL
|
|
112
|
+
precedence is `--base-url`, then `TARNISHED_BASE_URL`, then the selected profile.
|
|
113
|
+
Use a trusted server: URL overrides send the selected profile's key to that server.
|
|
114
|
+
The CLI follows same-origin redirects but rejects cross-origin or
|
|
115
|
+
credential-bearing URLs. Use the final server URL where possible.
|
|
116
|
+
|
|
117
|
+
JSON is the default output. Set the profile's output to `text` or use
|
|
118
|
+
`TARNISHED_OUTPUT=text` for text output; `--json` overrides both. API and local
|
|
119
|
+
configuration errors exit with code 1. JSON errors go to stdout; text errors go
|
|
120
|
+
to stderr. Invalid command arguments use Typer's standard error output.
|
|
121
|
+
|
|
122
|
+
Imports, reports, and transcriptions support `--wait`, `--poll-interval`, and
|
|
123
|
+
`--timeout-seconds`. A local wait timeout does not cancel server work. Report
|
|
124
|
+
waits stop if another job replaces the submitted job. Review the saved state
|
|
125
|
+
before retrying an AI or speech request, which can incur provider charges.
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
cd cli
|
|
131
|
+
uv sync
|
|
132
|
+
uv run tarnished --help
|
|
133
|
+
uv run pytest -q
|
|
134
|
+
uv run ruff check .
|
|
135
|
+
uv run ruff format --check .
|
|
136
|
+
uv run pyright
|
|
137
|
+
uv build
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Release
|
|
141
|
+
|
|
142
|
+
The repository release workflow builds CLI distributions, uploads them to the
|
|
143
|
+
GitHub release, and publishes the CLI to PyPI by default. Homebrew tap updates
|
|
144
|
+
run in the separate `homebrew-tap.yml` workflow after Homebrew's 24-hour PyPI
|
|
145
|
+
resource-resolution cooldown window has elapsed.
|
|
146
|
+
|
|
147
|
+
### One-Time PyPI Trusted Publishing Setup
|
|
148
|
+
|
|
149
|
+
1. Create the `tarnished-cli` project on PyPI.
|
|
150
|
+
2. In the PyPI project settings, add a Trusted Publisher for this GitHub repository.
|
|
151
|
+
3. Use these values:
|
|
152
|
+
- Owner: `markoonakic`
|
|
153
|
+
- Repository: `tarnished`
|
|
154
|
+
- Workflow name: `release.yml`
|
|
155
|
+
- Environment name: `pypi`
|
|
156
|
+
|
|
157
|
+
### One-Time Homebrew Tap Automation Setup
|
|
158
|
+
|
|
159
|
+
1. Create a write-enabled deploy key for `markoonakic/homebrew-tap`.
|
|
160
|
+
2. Add the private key to this repository as the `HOMEBREW_TAP_DEPLOY_KEY` secret.
|
|
161
|
+
3. The dedicated `homebrew-tap.yml` workflow will:
|
|
162
|
+
- wait until the target PyPI release is older than Homebrew's 24-hour Python resource cooldown window
|
|
163
|
+
- update `Formula/tarnished-cli.rb` from PyPI metadata
|
|
164
|
+
- refresh Python resource blocks with `brew update-python-resources`
|
|
165
|
+
- build the formula from source and run `brew test tarnished-cli`
|
|
166
|
+
- push the tap update to `markoonakic/homebrew-tap`
|
|
167
|
+
|
|
168
|
+
Why separate from the main release workflow?
|
|
169
|
+
|
|
170
|
+
- Homebrew's Python dependency resolver intentionally excludes PyPI uploads from the last 24 hours when refreshing resource blocks.
|
|
171
|
+
- Keeping tap sync separate avoids false-negative release failures while still following Homebrew's documented Python-formula workflow.
|
|
172
|
+
|
|
173
|
+
### Release Outputs
|
|
174
|
+
|
|
175
|
+
`release.yml` publishes:
|
|
176
|
+
|
|
177
|
+
- `cli/dist/*.whl`
|
|
178
|
+
- `cli/dist/*.tar.gz`
|
|
179
|
+
|
|
180
|
+
to the GitHub release and publishes the package to PyPI.
|
|
181
|
+
|
|
182
|
+
`homebrew-tap.yml` then updates the Homebrew tap separately once the published
|
|
183
|
+
sdist is old enough for `brew update-python-resources` to resolve safely.
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT. The wheel and source distribution include the LICENSE file.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Tarnished CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for Tarnished, version **0.2.0**. Use a matching Tarnished backend.
|
|
4
|
+
|
|
5
|
+
**Documentation:** https://markoonakic.github.io/tarnished/
|
|
6
|
+
|
|
7
|
+
For the user-facing CLI guide, see:
|
|
8
|
+
- https://markoonakic.github.io/tarnished/how-to/use-the-cli/
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Install version 0.2.0 from PyPI once that release is available:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uv tool install tarnished-cli==0.2.0
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
To install this checkout, including before a release is published, run from the repository root:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uv tool install ./cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Homebrew convenience path (the tap can lag the PyPI release):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
brew tap markoonakic/tap
|
|
28
|
+
brew install tarnished-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To install a downloaded wheel:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv tool install ./dist/tarnished_cli-<version>-py3-none-any.whl
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Authentication
|
|
38
|
+
|
|
39
|
+
Tarnished CLI is API-key-first.
|
|
40
|
+
|
|
41
|
+
1. Create or rotate the API key in the Tarnished web app.
|
|
42
|
+
2. Prefer the **CLI** preset so the key includes the scopes the CLI expects.
|
|
43
|
+
3. Validate and store the key locally:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
tarnished auth init --api-key '...'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
4. Run the auth doctor to confirm the stored key, live auth check, and required
|
|
50
|
+
CLI scopes all pass:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
tarnished auth doctor
|
|
54
|
+
tarnished auth whoami
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
5. Clear the locally stored key when needed:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
tarnished auth api-key clear
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The web app remains the source of truth for API keys. The CLI does **not**
|
|
64
|
+
create, rotate, revoke, or otherwise manage remote API keys.
|
|
65
|
+
|
|
66
|
+
The CLI uses the system keyring when available. Otherwise, it stores the key in
|
|
67
|
+
a private file in the CLI config directory. `TARNISHED_API_KEY` overrides stored
|
|
68
|
+
credentials. Clearing a stored key does not unset this environment variable.
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
Global options go before the command:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
tarnished --base-url https://tarnished.example.com auth status
|
|
76
|
+
tarnished --profile work --json applications list
|
|
77
|
+
tarnished applications --help
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Create `config.json` in `~/.config/tarnished` (or
|
|
81
|
+
`$XDG_CONFIG_HOME/tarnished`) to set profiles:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"default_profile": "default",
|
|
86
|
+
"profiles": {
|
|
87
|
+
"default": { "base_url": "http://127.0.0.1:5577", "output": "json" },
|
|
88
|
+
"work": { "base_url": "https://tarnished.example.com", "output": "json" }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`--config-dir` and `TARNISHED_CONFIG_DIR` override this directory. Server URL
|
|
94
|
+
precedence is `--base-url`, then `TARNISHED_BASE_URL`, then the selected profile.
|
|
95
|
+
Use a trusted server: URL overrides send the selected profile's key to that server.
|
|
96
|
+
The CLI follows same-origin redirects but rejects cross-origin or
|
|
97
|
+
credential-bearing URLs. Use the final server URL where possible.
|
|
98
|
+
|
|
99
|
+
JSON is the default output. Set the profile's output to `text` or use
|
|
100
|
+
`TARNISHED_OUTPUT=text` for text output; `--json` overrides both. API and local
|
|
101
|
+
configuration errors exit with code 1. JSON errors go to stdout; text errors go
|
|
102
|
+
to stderr. Invalid command arguments use Typer's standard error output.
|
|
103
|
+
|
|
104
|
+
Imports, reports, and transcriptions support `--wait`, `--poll-interval`, and
|
|
105
|
+
`--timeout-seconds`. A local wait timeout does not cancel server work. Report
|
|
106
|
+
waits stop if another job replaces the submitted job. Review the saved state
|
|
107
|
+
before retrying an AI or speech request, which can incur provider charges.
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
cd cli
|
|
113
|
+
uv sync
|
|
114
|
+
uv run tarnished --help
|
|
115
|
+
uv run pytest -q
|
|
116
|
+
uv run ruff check .
|
|
117
|
+
uv run ruff format --check .
|
|
118
|
+
uv run pyright
|
|
119
|
+
uv build
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Release
|
|
123
|
+
|
|
124
|
+
The repository release workflow builds CLI distributions, uploads them to the
|
|
125
|
+
GitHub release, and publishes the CLI to PyPI by default. Homebrew tap updates
|
|
126
|
+
run in the separate `homebrew-tap.yml` workflow after Homebrew's 24-hour PyPI
|
|
127
|
+
resource-resolution cooldown window has elapsed.
|
|
128
|
+
|
|
129
|
+
### One-Time PyPI Trusted Publishing Setup
|
|
130
|
+
|
|
131
|
+
1. Create the `tarnished-cli` project on PyPI.
|
|
132
|
+
2. In the PyPI project settings, add a Trusted Publisher for this GitHub repository.
|
|
133
|
+
3. Use these values:
|
|
134
|
+
- Owner: `markoonakic`
|
|
135
|
+
- Repository: `tarnished`
|
|
136
|
+
- Workflow name: `release.yml`
|
|
137
|
+
- Environment name: `pypi`
|
|
138
|
+
|
|
139
|
+
### One-Time Homebrew Tap Automation Setup
|
|
140
|
+
|
|
141
|
+
1. Create a write-enabled deploy key for `markoonakic/homebrew-tap`.
|
|
142
|
+
2. Add the private key to this repository as the `HOMEBREW_TAP_DEPLOY_KEY` secret.
|
|
143
|
+
3. The dedicated `homebrew-tap.yml` workflow will:
|
|
144
|
+
- wait until the target PyPI release is older than Homebrew's 24-hour Python resource cooldown window
|
|
145
|
+
- update `Formula/tarnished-cli.rb` from PyPI metadata
|
|
146
|
+
- refresh Python resource blocks with `brew update-python-resources`
|
|
147
|
+
- build the formula from source and run `brew test tarnished-cli`
|
|
148
|
+
- push the tap update to `markoonakic/homebrew-tap`
|
|
149
|
+
|
|
150
|
+
Why separate from the main release workflow?
|
|
151
|
+
|
|
152
|
+
- Homebrew's Python dependency resolver intentionally excludes PyPI uploads from the last 24 hours when refreshing resource blocks.
|
|
153
|
+
- Keeping tap sync separate avoids false-negative release failures while still following Homebrew's documented Python-formula workflow.
|
|
154
|
+
|
|
155
|
+
### Release Outputs
|
|
156
|
+
|
|
157
|
+
`release.yml` publishes:
|
|
158
|
+
|
|
159
|
+
- `cli/dist/*.whl`
|
|
160
|
+
- `cli/dist/*.tar.gz`
|
|
161
|
+
|
|
162
|
+
to the GitHub release and publishes the package to PyPI.
|
|
163
|
+
|
|
164
|
+
`homebrew-tap.yml` then updates the Homebrew tap separately once the published
|
|
165
|
+
sdist is old enough for `brew update-python-resources` to resolve safely.
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT. The wheel and source distribution include the LICENSE file.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "meaning": "rejected", "expected_revision": 0 }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "to_meaning": "interviewing", "expected_revision": 0 }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "period": "all", "as_of": "2026-01-07T09:00:00Z" }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "response_evidence": null, "expected_revision": 0 }
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tarnished-cli"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Command-line interface for Tarnished"
|
|
5
5
|
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
6
8
|
requires-python = ">=3.12"
|
|
7
9
|
dependencies = [
|
|
8
10
|
"httpx>=0.28.1",
|
|
@@ -10,13 +12,19 @@ dependencies = [
|
|
|
10
12
|
"pydantic>=2.12.0",
|
|
11
13
|
"email-validator>=2.0.0",
|
|
12
14
|
"typer>=0.23.1",
|
|
15
|
+
"tzlocal>=5.3.1",
|
|
13
16
|
]
|
|
14
17
|
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://markoonakic.github.io/tarnished/"
|
|
20
|
+
Repository = "https://github.com/markoonakic/tarnished"
|
|
21
|
+
Documentation = "https://markoonakic.github.io/tarnished/how-to/use-the-cli/"
|
|
22
|
+
|
|
15
23
|
[project.scripts]
|
|
16
24
|
tarnished = "tarnished_cli.main:app"
|
|
17
25
|
|
|
18
26
|
[build-system]
|
|
19
|
-
requires = ["hatchling"]
|
|
27
|
+
requires = ["hatchling>=1.27"]
|
|
20
28
|
build-backend = "hatchling.build"
|
|
21
29
|
|
|
22
30
|
[tool.hatch.build.targets.wheel]
|
|
@@ -6,7 +6,7 @@ from typing import Any
|
|
|
6
6
|
from pydantic import ValidationError
|
|
7
7
|
|
|
8
8
|
from tarnished_cli.auth_storage import StoredAuth
|
|
9
|
-
from tarnished_cli.client import CLIError
|
|
9
|
+
from tarnished_cli.client import CLIError, redact_credentials
|
|
10
10
|
from tarnished_cli.models.auth import AuthDiagnostics, LiveAuthIdentity
|
|
11
11
|
|
|
12
12
|
API_KEY_PREFIX_LENGTH = 8
|
|
@@ -26,6 +26,7 @@ CLI_REQUIRED_SCOPES = (
|
|
|
26
26
|
"round_types:write",
|
|
27
27
|
"dashboard:read",
|
|
28
28
|
"analytics:read",
|
|
29
|
+
"analytics:generate",
|
|
29
30
|
"streak:read",
|
|
30
31
|
"streak:write",
|
|
31
32
|
"preferences:read",
|
|
@@ -49,7 +50,7 @@ def parse_live_identity(payload: object) -> LiveAuthIdentity:
|
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
def stored_api_key_prefix(api_key: str | None) -> str | None:
|
|
52
|
-
if not api_key:
|
|
53
|
+
if not api_key or len(api_key) <= API_KEY_PREFIX_LENGTH:
|
|
53
54
|
return None
|
|
54
55
|
return api_key[:API_KEY_PREFIX_LENGTH]
|
|
55
56
|
|
|
@@ -63,7 +64,7 @@ def build_auth_diagnostics(
|
|
|
63
64
|
) -> AuthDiagnostics:
|
|
64
65
|
return AuthDiagnostics(
|
|
65
66
|
profile=profile,
|
|
66
|
-
base_url=base_url,
|
|
67
|
+
base_url=redact_credentials(base_url, stored_auth.api_key),
|
|
67
68
|
has_stored_api_key=bool(stored_auth.api_key),
|
|
68
69
|
stored_api_key_prefix=stored_api_key_prefix(stored_auth.api_key),
|
|
69
70
|
live_identity=_coerce_live_identity(live_identity),
|
|
@@ -9,7 +9,9 @@ import keyring
|
|
|
9
9
|
from keyring.errors import KeyringError, PasswordDeleteError
|
|
10
10
|
from pydantic import BaseModel
|
|
11
11
|
|
|
12
|
+
from tarnished_cli.client import CLIError
|
|
12
13
|
from tarnished_cli.config import resolve_config_dir
|
|
14
|
+
from tarnished_cli.files import write_private_file
|
|
13
15
|
|
|
14
16
|
API_KEY_ENV = "TARNISHED_API_KEY"
|
|
15
17
|
KEYRING_SERVICE = "tarnished-cli"
|
|
@@ -41,21 +43,29 @@ def _env_auth() -> StoredAuth | None:
|
|
|
41
43
|
def _read_auth_file(path: Path) -> StoredAuth:
|
|
42
44
|
if not path.exists():
|
|
43
45
|
return StoredAuth()
|
|
44
|
-
return StoredAuth.model_validate(json.loads(path.read_text()))
|
|
46
|
+
return StoredAuth.model_validate(json.loads(path.read_text(encoding="utf-8")))
|
|
45
47
|
|
|
46
48
|
|
|
47
49
|
def _write_auth_file(path: Path, auth: StoredAuth) -> None:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
path.
|
|
50
|
+
try:
|
|
51
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
with contextlib.suppress(OSError):
|
|
53
|
+
path.parent.chmod(0o700)
|
|
54
|
+
content = json.dumps(auth.model_dump(mode="json"), sort_keys=True) + "\n"
|
|
55
|
+
write_private_file(path, content.encode("utf-8"))
|
|
56
|
+
except OSError:
|
|
57
|
+
raise CLIError(
|
|
58
|
+
"Could not save API key. Check config directory permissions and disk space."
|
|
59
|
+
) from None
|
|
54
60
|
|
|
55
61
|
|
|
56
62
|
def _remove_auth_file(path: Path) -> None:
|
|
57
|
-
|
|
58
|
-
path.unlink()
|
|
63
|
+
try:
|
|
64
|
+
path.unlink(missing_ok=True)
|
|
65
|
+
except OSError:
|
|
66
|
+
raise CLIError(
|
|
67
|
+
"Could not remove stored API key. Check config directory permissions."
|
|
68
|
+
) from None
|
|
59
69
|
|
|
60
70
|
|
|
61
71
|
def load_auth(
|