tarnished-cli 0.1.2__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 (49) hide show
  1. tarnished_cli-0.1.2/.gitignore +180 -0
  2. tarnished_cli-0.1.2/PKG-INFO +91 -0
  3. tarnished_cli-0.1.2/README.md +79 -0
  4. tarnished_cli-0.1.2/pyproject.toml +71 -0
  5. tarnished_cli-0.1.2/src/tarnished_cli/__init__.py +1 -0
  6. tarnished_cli-0.1.2/src/tarnished_cli/auth_storage.py +135 -0
  7. tarnished_cli-0.1.2/src/tarnished_cli/client.py +313 -0
  8. tarnished_cli-0.1.2/src/tarnished_cli/commands/__init__.py +1 -0
  9. tarnished_cli-0.1.2/src/tarnished_cli/commands/admin.py +182 -0
  10. tarnished_cli-0.1.2/src/tarnished_cli/commands/analytics.py +117 -0
  11. tarnished_cli-0.1.2/src/tarnished_cli/commands/applications.py +381 -0
  12. tarnished_cli-0.1.2/src/tarnished_cli/commands/auth.py +152 -0
  13. tarnished_cli-0.1.2/src/tarnished_cli/commands/dashboard.py +37 -0
  14. tarnished_cli-0.1.2/src/tarnished_cli/commands/exports.py +48 -0
  15. tarnished_cli-0.1.2/src/tarnished_cli/commands/imports.py +78 -0
  16. tarnished_cli-0.1.2/src/tarnished_cli/commands/job_leads.py +131 -0
  17. tarnished_cli-0.1.2/src/tarnished_cli/commands/preferences.py +45 -0
  18. tarnished_cli-0.1.2/src/tarnished_cli/commands/profile.py +35 -0
  19. tarnished_cli-0.1.2/src/tarnished_cli/commands/round_types.py +77 -0
  20. tarnished_cli-0.1.2/src/tarnished_cli/commands/rounds.py +232 -0
  21. tarnished_cli-0.1.2/src/tarnished_cli/commands/statuses.py +73 -0
  22. tarnished_cli-0.1.2/src/tarnished_cli/commands/user_settings.py +42 -0
  23. tarnished_cli-0.1.2/src/tarnished_cli/config.py +93 -0
  24. tarnished_cli-0.1.2/src/tarnished_cli/input.py +34 -0
  25. tarnished_cli-0.1.2/src/tarnished_cli/main.py +79 -0
  26. tarnished_cli-0.1.2/src/tarnished_cli/models/__init__.py +39 -0
  27. tarnished_cli-0.1.2/src/tarnished_cli/models/requests.py +254 -0
  28. tarnished_cli-0.1.2/src/tarnished_cli/output.py +58 -0
  29. tarnished_cli-0.1.2/src/tarnished_cli/state.py +95 -0
  30. tarnished_cli-0.1.2/tests/conftest.py +15 -0
  31. tarnished_cli-0.1.2/tests/test_admin_commands.py +94 -0
  32. tarnished_cli-0.1.2/tests/test_analytics_commands.py +77 -0
  33. tarnished_cli-0.1.2/tests/test_applications_commands.py +227 -0
  34. tarnished_cli-0.1.2/tests/test_auth_commands.py +220 -0
  35. tarnished_cli-0.1.2/tests/test_auth_storage.py +56 -0
  36. tarnished_cli-0.1.2/tests/test_cli_config.py +9 -0
  37. tarnished_cli-0.1.2/tests/test_client.py +105 -0
  38. tarnished_cli-0.1.2/tests/test_dashboard_commands.py +26 -0
  39. tarnished_cli-0.1.2/tests/test_export_commands.py +23 -0
  40. tarnished_cli-0.1.2/tests/test_import_commands.py +85 -0
  41. tarnished_cli-0.1.2/tests/test_job_leads_commands.py +79 -0
  42. tarnished_cli-0.1.2/tests/test_main.py +30 -0
  43. tarnished_cli-0.1.2/tests/test_output_contract.py +17 -0
  44. tarnished_cli-0.1.2/tests/test_profile_commands.py +36 -0
  45. tarnished_cli-0.1.2/tests/test_round_types_commands.py +28 -0
  46. tarnished_cli-0.1.2/tests/test_rounds_commands.py +124 -0
  47. tarnished_cli-0.1.2/tests/test_statuses_commands.py +62 -0
  48. tarnished_cli-0.1.2/tests/test_user_settings_commands.py +53 -0
  49. tarnished_cli-0.1.2/uv.lock +614 -0
@@ -0,0 +1,180 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ node_modules/
5
+ .env
6
+ data/
7
+ uploads/
8
+ *.db
9
+ .uv/
10
+ dist/
11
+ build/
12
+ *.egg-info/
13
+
14
+
15
+ .worktrees/
16
+ mise.toml
17
+ docs/
18
+ scripts/
19
+ .claude/
20
+ .devcontainer/
21
+ .planning/
22
+ .serena/
23
+ CLAUDE.md
24
+
25
+ .firecrawl/
26
+
27
+ # Ralph Loop (autonomous implementation)
28
+ ralph.yml
29
+ ralph-loop.sh
30
+ PROMPT.md
31
+ specs/
32
+ .ralph/
33
+ .claude/handoff*.md
34
+
35
+ # Design guidelines - development notes, not source code
36
+ frontend/DESIGN_GUIDELINES.md
37
+ backend/DESIGN_GUIDELINES.md
38
+
39
+ # Seed scripts - development only
40
+ backend/seed_*.py
41
+
42
+ # Generated requirements.txt
43
+ backend/requirements.txt
44
+
45
+ # Pre-commit framework (using Husky instead)
46
+ .pre-commit-config.yaml
47
+
48
+ # Playwright MCP screenshots and snapshots
49
+ .playwright-mcp/
50
+
51
+ # macOS
52
+ .DS_Store
53
+ .DS_Store?
54
+ ._*
55
+ .Spotlight-V100
56
+ .Trashes
57
+ ehthumbs.db
58
+ Thumbs.db
59
+
60
+ # Temporary directories and files
61
+ temp/
62
+ temp2/
63
+ tmp/
64
+ *.tmp
65
+ *~
66
+
67
+ # Core dumps
68
+ /core
69
+ core.*
70
+
71
+ # Editor swap files
72
+ *.swp
73
+ *.swo
74
+ *.swn
75
+
76
+ # Session backup files
77
+ *session-is-being-continued*.txt
78
+ *.bak
79
+
80
+ # ============================================================================
81
+ # Frontend (Node.js/Yarn/Vite)
82
+ # ============================================================================
83
+
84
+ # Yarn
85
+ .yarn/
86
+ .pnp.*
87
+
88
+ # Vite
89
+ .vite/
90
+
91
+ # Environment variants
92
+ .env.local
93
+ .env.*.local
94
+
95
+ # Backend environment files
96
+ backend/.env.postgresql
97
+ backend/.env.*
98
+
99
+ # TypeScript
100
+ *.tsbuildinfo
101
+
102
+ # ESLint
103
+ .eslintcache
104
+
105
+ # Test coverage
106
+ coverage/
107
+ *.lcov
108
+
109
+ # Logs
110
+ *.log
111
+ npm-debug.log*
112
+ yarn-debug.log*
113
+ yarn-error.log*
114
+
115
+ # ============================================================================
116
+ # Backend (Python/Testing)
117
+ # ============================================================================
118
+
119
+ # Python artifacts
120
+ *.pyo
121
+ *.so
122
+ *.py[cod]
123
+ *.egg
124
+
125
+ # Database files
126
+ *.sqlite
127
+ *.sqlite3
128
+
129
+ # IDE configs
130
+ .vscode/
131
+ .idea/
132
+
133
+ # Test coverage
134
+ .coverage
135
+ htmlcov/
136
+ *.cover
137
+ .pytest_cache/
138
+ .hypothesis/
139
+ .tox/
140
+
141
+ # MyPy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Alembic cache (not migration files)
147
+ alembic/versions/__pycache__/
148
+
149
+ # ============================================================================
150
+ # Project Level (Docker/Cache)
151
+ # ============================================================================
152
+
153
+ # Docker overrides
154
+ docker-compose.override.yml
155
+ docker-compose.override.yaml
156
+
157
+ # Docker build artifacts
158
+ .docker/
159
+
160
+ # CI/CD local files
161
+ .github/workflows/*.local.yml
162
+ .github/workflows/*.local.yaml
163
+
164
+ # Cache directories
165
+ .cache/
166
+ .npm/
167
+
168
+ # ============================================================================
169
+ # Browser Extension
170
+ # ============================================================================
171
+
172
+ # Extension build artifacts
173
+ *.xpi
174
+
175
+ # Extension test/dev files
176
+ extension/test-form.html
177
+
178
+ # Don't mix npm and yarn in extension
179
+ extension/package-lock.json
180
+ .playwright/
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: tarnished-cli
3
+ Version: 0.1.2
4
+ Summary: Agent-first CLI for Tarnished
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: email-validator>=2.0.0
7
+ Requires-Dist: httpx>=0.28.1
8
+ Requires-Dist: keyring>=25.7.0
9
+ Requires-Dist: pydantic>=2.12.0
10
+ Requires-Dist: typer>=0.23.1
11
+ Description-Content-Type: text/markdown
12
+
13
+ # Tarnished CLI
14
+
15
+ Agent-first command-line interface for Tarnished.
16
+
17
+ ## Install
18
+
19
+ Preferred install path:
20
+
21
+ ```bash
22
+ uv tool install tarnished-cli
23
+ ```
24
+
25
+ Alternative:
26
+
27
+ ```bash
28
+ pipx install tarnished-cli
29
+ ```
30
+
31
+ Install from a built wheel before PyPI publication:
32
+
33
+ ```bash
34
+ uv tool install ./dist/tarnished_cli-0.1.2-py3-none-any.whl
35
+ ```
36
+
37
+ ## OpenClaw / Agent Install
38
+
39
+ Recommended:
40
+
41
+ ```bash
42
+ uv tool install tarnished-cli
43
+ ```
44
+
45
+ Version-pinned:
46
+
47
+ ```bash
48
+ uv tool install 'tarnished-cli==0.1.2'
49
+ ```
50
+
51
+ Then run:
52
+
53
+ ```bash
54
+ tarnished --help
55
+ ```
56
+
57
+ ## Development
58
+
59
+ ```bash
60
+ cd cli
61
+ uv sync
62
+ uv run tarnished --help
63
+ uv run pytest -q
64
+ ```
65
+
66
+ ## Release
67
+
68
+ The repository release workflow builds CLI distributions and uploads them to the GitHub release.
69
+
70
+ PyPI publication is optional and is controlled by the `publish_cli_package` workflow input.
71
+
72
+ ### One-Time PyPI Trusted Publishing Setup
73
+
74
+ 1. Create the `tarnished-cli` project on PyPI.
75
+ 2. In the PyPI project settings, add a Trusted Publisher for this GitHub repository.
76
+ 3. Use these values:
77
+ - Owner: `markoonakic`
78
+ - Repository: `tarnished`
79
+ - Workflow name: `release.yml`
80
+ - Environment name: `pypi`
81
+ 4. After that, run the GitHub release workflow with:
82
+ - `publish_cli_package=true`
83
+
84
+ ### Release Outputs
85
+
86
+ The release workflow publishes:
87
+
88
+ - `cli/dist/*.whl`
89
+ - `cli/dist/*.tar.gz`
90
+
91
+ to the GitHub release, and optionally to PyPI.
@@ -0,0 +1,79 @@
1
+ # Tarnished CLI
2
+
3
+ Agent-first command-line interface for Tarnished.
4
+
5
+ ## Install
6
+
7
+ Preferred install path:
8
+
9
+ ```bash
10
+ uv tool install tarnished-cli
11
+ ```
12
+
13
+ Alternative:
14
+
15
+ ```bash
16
+ pipx install tarnished-cli
17
+ ```
18
+
19
+ Install from a built wheel before PyPI publication:
20
+
21
+ ```bash
22
+ uv tool install ./dist/tarnished_cli-0.1.2-py3-none-any.whl
23
+ ```
24
+
25
+ ## OpenClaw / Agent Install
26
+
27
+ Recommended:
28
+
29
+ ```bash
30
+ uv tool install tarnished-cli
31
+ ```
32
+
33
+ Version-pinned:
34
+
35
+ ```bash
36
+ uv tool install 'tarnished-cli==0.1.2'
37
+ ```
38
+
39
+ Then run:
40
+
41
+ ```bash
42
+ tarnished --help
43
+ ```
44
+
45
+ ## Development
46
+
47
+ ```bash
48
+ cd cli
49
+ uv sync
50
+ uv run tarnished --help
51
+ uv run pytest -q
52
+ ```
53
+
54
+ ## Release
55
+
56
+ The repository release workflow builds CLI distributions and uploads them to the GitHub release.
57
+
58
+ PyPI publication is optional and is controlled by the `publish_cli_package` workflow input.
59
+
60
+ ### One-Time PyPI Trusted Publishing Setup
61
+
62
+ 1. Create the `tarnished-cli` project on PyPI.
63
+ 2. In the PyPI project settings, add a Trusted Publisher for this GitHub repository.
64
+ 3. Use these values:
65
+ - Owner: `markoonakic`
66
+ - Repository: `tarnished`
67
+ - Workflow name: `release.yml`
68
+ - Environment name: `pypi`
69
+ 4. After that, run the GitHub release workflow with:
70
+ - `publish_cli_package=true`
71
+
72
+ ### Release Outputs
73
+
74
+ The release workflow publishes:
75
+
76
+ - `cli/dist/*.whl`
77
+ - `cli/dist/*.tar.gz`
78
+
79
+ to the GitHub release, and optionally to PyPI.
@@ -0,0 +1,71 @@
1
+ [project]
2
+ name = "tarnished-cli"
3
+ version = "0.1.2"
4
+ description = "Agent-first CLI for Tarnished"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "httpx>=0.28.1",
9
+ "keyring>=25.7.0",
10
+ "pydantic>=2.12.0",
11
+ "email-validator>=2.0.0",
12
+ "typer>=0.23.1",
13
+ ]
14
+
15
+ [project.scripts]
16
+ tarnished = "tarnished_cli.main:app"
17
+
18
+ [build-system]
19
+ requires = ["hatchling"]
20
+ build-backend = "hatchling.build"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["src/tarnished_cli"]
24
+
25
+ [tool.pytest.ini_options]
26
+ testpaths = ["tests"]
27
+
28
+ [dependency-groups]
29
+ dev = [
30
+ "pyright>=1.1.408",
31
+ "pytest>=9.0.2",
32
+ "ruff>=0.15.1",
33
+ ]
34
+
35
+ [tool.ruff]
36
+ target-version = "py312"
37
+ line-length = 88
38
+ src = ["src", "tests"]
39
+
40
+ [tool.ruff.lint]
41
+ select = [
42
+ "E",
43
+ "W",
44
+ "F",
45
+ "I",
46
+ "B",
47
+ "C4",
48
+ "UP",
49
+ "SIM",
50
+ ]
51
+ ignore = [
52
+ "E501",
53
+ "B008",
54
+ ]
55
+
56
+ [tool.ruff.lint.per-file-ignores]
57
+ "tests/**/*.py" = [
58
+ "B017",
59
+ ]
60
+
61
+ [tool.ruff.format]
62
+ quote-style = "double"
63
+ indent-style = "space"
64
+
65
+ [tool.pyright]
66
+ pythonVersion = "3.12"
67
+ typeCheckingMode = "basic"
68
+ reportMissingImports = true
69
+ reportMissingTypeStubs = false
70
+ venvPath = "."
71
+ venv = ".venv"
@@ -0,0 +1 @@
1
+ """Tarnished CLI package."""
@@ -0,0 +1,135 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+
8
+ import keyring
9
+ from keyring.errors import KeyringError, PasswordDeleteError
10
+ from pydantic import BaseModel
11
+
12
+ from tarnished_cli.config import resolve_config_dir
13
+
14
+ ACCESS_TOKEN_ENV = "TARNISHED_ACCESS_TOKEN"
15
+ REFRESH_TOKEN_ENV = "TARNISHED_REFRESH_TOKEN"
16
+ API_KEY_ENV = "TARNISHED_API_KEY"
17
+ KEYRING_SERVICE = "tarnished-cli"
18
+
19
+
20
+ class StoredAuth(BaseModel):
21
+ access_token: str | None = None
22
+ refresh_token: str | None = None
23
+ api_key: str | None = None
24
+
25
+ def is_empty(self) -> bool:
26
+ return not any([self.access_token, self.refresh_token, self.api_key])
27
+
28
+
29
+ def resolve_auth_path(profile: str = "default", config_dir: Path | None = None) -> Path:
30
+ return resolve_config_dir(config_dir) / f"auth-{profile}.json"
31
+
32
+
33
+ def _keyring_account(profile: str = "default", config_dir: Path | None = None) -> str:
34
+ resolved_dir = resolve_config_dir(config_dir)
35
+ return f"{resolved_dir}:{profile}"
36
+
37
+
38
+ def _env_auth() -> StoredAuth | None:
39
+ access_token = os.getenv(ACCESS_TOKEN_ENV)
40
+ refresh_token = os.getenv(REFRESH_TOKEN_ENV)
41
+ api_key = os.getenv(API_KEY_ENV)
42
+ if not any([access_token, refresh_token, api_key]):
43
+ return None
44
+ return StoredAuth(
45
+ access_token=access_token,
46
+ refresh_token=refresh_token,
47
+ api_key=api_key,
48
+ )
49
+
50
+
51
+ def _read_auth_file(path: Path) -> StoredAuth:
52
+ if not path.exists():
53
+ return StoredAuth()
54
+ return StoredAuth.model_validate(json.loads(path.read_text()))
55
+
56
+
57
+ def _write_auth_file(path: Path, auth: StoredAuth) -> None:
58
+ path.parent.mkdir(parents=True, exist_ok=True)
59
+ with contextlib.suppress(OSError):
60
+ path.parent.chmod(0o700)
61
+ path.write_text(json.dumps(auth.model_dump(mode="json"), sort_keys=True) + "\n")
62
+ with contextlib.suppress(OSError):
63
+ path.chmod(0o600)
64
+
65
+
66
+ def _remove_auth_file(path: Path) -> None:
67
+ if path.exists():
68
+ path.unlink()
69
+
70
+
71
+ def load_auth(
72
+ profile: str = "default",
73
+ *,
74
+ config_dir: Path | None = None,
75
+ prefer_keyring: bool = True,
76
+ ) -> StoredAuth:
77
+ env_auth = _env_auth()
78
+ if env_auth is not None:
79
+ return env_auth
80
+
81
+ if prefer_keyring:
82
+ account = _keyring_account(profile, config_dir)
83
+ try:
84
+ payload = keyring.get_password(KEYRING_SERVICE, account)
85
+ except (KeyringError, RuntimeError):
86
+ payload = None
87
+ if payload:
88
+ return StoredAuth.model_validate_json(payload)
89
+
90
+ return _read_auth_file(resolve_auth_path(profile, config_dir))
91
+
92
+
93
+ def save_auth(
94
+ auth: StoredAuth,
95
+ profile: str = "default",
96
+ *,
97
+ config_dir: Path | None = None,
98
+ prefer_keyring: bool = True,
99
+ ) -> Path:
100
+ path = resolve_auth_path(profile, config_dir)
101
+
102
+ if prefer_keyring:
103
+ account = _keyring_account(profile, config_dir)
104
+ try:
105
+ if auth.is_empty():
106
+ with contextlib.suppress(PasswordDeleteError):
107
+ keyring.delete_password(KEYRING_SERVICE, account)
108
+ else:
109
+ keyring.set_password(
110
+ KEYRING_SERVICE, account, auth.model_dump_json(exclude_none=False)
111
+ )
112
+ _remove_auth_file(path)
113
+ return path
114
+ except (KeyringError, RuntimeError):
115
+ pass
116
+
117
+ _write_auth_file(path, auth)
118
+ return path
119
+
120
+
121
+ def clear_auth(
122
+ profile: str = "default",
123
+ *,
124
+ config_dir: Path | None = None,
125
+ prefer_keyring: bool = True,
126
+ ) -> None:
127
+ path = resolve_auth_path(profile, config_dir)
128
+ if prefer_keyring:
129
+ account = _keyring_account(profile, config_dir)
130
+ try:
131
+ with contextlib.suppress(PasswordDeleteError):
132
+ keyring.delete_password(KEYRING_SERVICE, account)
133
+ except (KeyringError, RuntimeError):
134
+ pass
135
+ _remove_auth_file(path)