clinear 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.
Files changed (49) hide show
  1. clinear-0.2.0/.gitignore +50 -0
  2. clinear-0.2.0/.python-version +1 -0
  3. clinear-0.2.0/AGENTS.md +317 -0
  4. clinear-0.2.0/CHANGELOG.md +59 -0
  5. clinear-0.2.0/LICENSE +21 -0
  6. clinear-0.2.0/PKG-INFO +234 -0
  7. clinear-0.2.0/README.md +197 -0
  8. clinear-0.2.0/VERSION +1 -0
  9. clinear-0.2.0/clinear/__init__.py +22 -0
  10. clinear-0.2.0/clinear/__main__.py +6 -0
  11. clinear-0.2.0/clinear/auth.py +43 -0
  12. clinear-0.2.0/clinear/cli.py +122 -0
  13. clinear-0.2.0/clinear/cli_state.py +50 -0
  14. clinear-0.2.0/clinear/client.py +265 -0
  15. clinear-0.2.0/clinear/commands/__init__.py +4 -0
  16. clinear-0.2.0/clinear/commands/auth_cmd.py +42 -0
  17. clinear-0.2.0/clinear/commands/comment.py +149 -0
  18. clinear-0.2.0/clinear/commands/cycle.py +75 -0
  19. clinear-0.2.0/clinear/commands/init.py +84 -0
  20. clinear-0.2.0/clinear/commands/issue.py +366 -0
  21. clinear-0.2.0/clinear/commands/label.py +163 -0
  22. clinear-0.2.0/clinear/commands/project.py +57 -0
  23. clinear-0.2.0/clinear/commands/raw.py +50 -0
  24. clinear-0.2.0/clinear/commands/team.py +102 -0
  25. clinear-0.2.0/clinear/config.py +131 -0
  26. clinear-0.2.0/clinear/errors.py +107 -0
  27. clinear-0.2.0/clinear/filters.py +150 -0
  28. clinear-0.2.0/clinear/graphql/__init__.py +5 -0
  29. clinear-0.2.0/clinear/graphql/fragments.py +137 -0
  30. clinear-0.2.0/clinear/graphql/mutations.py +160 -0
  31. clinear-0.2.0/clinear/graphql/queries.py +245 -0
  32. clinear-0.2.0/clinear/models/__init__.py +37 -0
  33. clinear-0.2.0/clinear/models/base.py +64 -0
  34. clinear-0.2.0/clinear/models/cycle.py +32 -0
  35. clinear-0.2.0/clinear/models/enums.py +62 -0
  36. clinear-0.2.0/clinear/models/issue.py +99 -0
  37. clinear-0.2.0/clinear/models/label.py +17 -0
  38. clinear-0.2.0/clinear/models/project.py +60 -0
  39. clinear-0.2.0/clinear/models/team.py +30 -0
  40. clinear-0.2.0/clinear/models/user.py +32 -0
  41. clinear-0.2.0/clinear/models/workflow.py +36 -0
  42. clinear-0.2.0/clinear/output.py +412 -0
  43. clinear-0.2.0/docs/DESIGN.md +431 -0
  44. clinear-0.2.0/pyproject.toml +79 -0
  45. clinear-0.2.0/schema/schema-summary.json +1498 -0
  46. clinear-0.2.0/scripts/e2e-test.sh +120 -0
  47. clinear-0.2.0/scripts/install-hooks.sh +13 -0
  48. clinear-0.2.0/scripts/pre-commit.sh +99 -0
  49. clinear-0.2.0/scripts/ship.sh +116 -0
@@ -0,0 +1,50 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .eggs/
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # Testing
18
+ .pytest_cache/
19
+ .coverage
20
+ .coverage.*
21
+ htmlcov/
22
+ .tox/
23
+ .mypy_cache/
24
+ .ruff_cache/
25
+
26
+ # IDE
27
+ .vscode/
28
+ .idea/
29
+ *.swp
30
+ *.swo
31
+
32
+ # OS
33
+ .DS_Store
34
+ Thumbs.db
35
+
36
+ # SECURITY — these are forbidden everywhere (also enforced by pre-commit hook)
37
+ *.log
38
+ .env
39
+ .env.*
40
+ .secrets/
41
+ vault/
42
+ secrets.toml
43
+ *.pem
44
+ *.key
45
+ id_rsa*
46
+ id_ed25519*
47
+
48
+ # Project-specific
49
+ schema/linear-schema.json
50
+ clinear-debug.log
@@ -0,0 +1 @@
1
+ 3.10
@@ -0,0 +1,317 @@
1
+ # AGENTS.md — Contributing to clinear
2
+
3
+ > Guide for humans and AI agents who modify this codebase.
4
+
5
+ ---
6
+
7
+ ## TL;DR (the rules)
8
+
9
+ 1. **Every change bumps the version.** Patch for bugfixes (0.2.0 → 0.2.1), minor for features (0.2.x → 0.3.0), major for breaking changes.
10
+ 2. **Every change updates `CHANGELOG.md`** under a new version heading.
11
+ 3. **Every change is tested.** Run `bash scripts/e2e-test.sh` before commit. All 36+ tests must pass.
12
+ 4. **Every commit goes through the pre-commit hook.** No secrets, no `.log`, no `.env`, no `schema/linear-schema.json`.
13
+ 5. **Every release gets a git tag (`vX.Y.Z`) and a GitHub release.** No exceptions.
14
+
15
+ ---
16
+
17
+ ## Architecture
18
+
19
+ ```
20
+ clinear/
21
+ ├── VERSION Source of truth for __version__
22
+ ├── pyproject.toml Build metadata (must match VERSION)
23
+ ├── CHANGELOG.md Keep-a-Changelog format
24
+
25
+ ├── clinear/ Package
26
+ │ ├── __init__.py Reads VERSION
27
+ │ ├── cli.py Root Typer app, global flags, error wrapper
28
+ │ ├── cli_state.py Per-invocation state container
29
+ │ ├── config.py TOML config + token resolution
30
+ │ ├── client.py Async httpx GraphQL client
31
+ │ ├── auth.py Viewer caching
32
+ │ ├── filters.py Filter DSL → IssueFilter GraphQL input
33
+ │ ├── output.py Six output formatters
34
+ │ ├── errors.py Typed exit codes
35
+ │ ├── models/ Pydantic v2 models (User, Team, Issue, ...)
36
+ │ ├── graphql/ Query/mutation strings + fragments
37
+ │ └── commands/ One file per command group
38
+
39
+ ├── schema/
40
+ │ ├── linear-schema.json 2.3 MB introspection result (GITIGNORED)
41
+ │ └── schema-summary.json Categorized summary (committed)
42
+
43
+ ├── scripts/
44
+ │ ├── e2e-test.sh Live API E2E test suite
45
+ │ ├── pre-commit.sh Secret-blocking pre-commit hook
46
+ │ └── install-hooks.sh Install pre-commit hook
47
+
48
+ └── tests/ (TODO: pytest unit tests for v0.3)
49
+ ```
50
+
51
+ ### Data flow for any command
52
+
53
+ ```
54
+ User CLI input
55
+ └─► clinear.cli.app (Typer)
56
+ └─► global flags resolved → cli_state.CLIState
57
+ └─► subcommand handler (clinear/commands/*.py)
58
+ └─► clinear.client.LinearClient.execute_as()
59
+ ├─► httpx POST to api.linear.app/graphql
60
+ ├─► JSON → dict
61
+ └─► dict → Pydantic model (validation)
62
+ └─► clinear.output.render(model, fmt)
63
+ └─► stdout
64
+ ```
65
+
66
+ ### Adding a new command
67
+
68
+ 1. Pick the right file in `clinear/commands/` (or create one).
69
+ 2. Add a Typer command with rich `--help` text.
70
+ 3. Write the GraphQL string in `clinear/graphql/queries.py` or `mutations.py`. Use fragments from `fragments.py`.
71
+ 4. If the response shape is new, add or extend a Pydantic model in `clinear/models/`.
72
+ 5. Register the new command group in `clinear/cli.py` via `app.add_typer(...)`.
73
+ 6. Add an E2E test case in `scripts/e2e-test.sh`.
74
+ 7. Bump version. Update CHANGELOG.
75
+
76
+ ### Adding a new model
77
+
78
+ ```python
79
+ # clinear/models/your_entity.py
80
+ from clinear.models.base import Timestamped
81
+
82
+ class YourEntity(Timestamped):
83
+ id: str
84
+ name: str
85
+ # use Field(alias="camelCase") for GraphQL → snake_case mapping
86
+ ```
87
+
88
+ ### Adding GraphQL fragments
89
+
90
+ Keep fragments **DRY** and **small**. One fragment per entity "view":
91
+ - `YourEntityCore` → fields included in lists
92
+ - `YourEntityFull` → extra fields included on detail views
93
+
94
+ ```python
95
+ # clinear/graphql/fragments.py
96
+ YOUR_ENTITY_CORE = """
97
+ fragment YourEntityCore on YourEntity {
98
+ id
99
+ name
100
+ createdAt
101
+ }
102
+ """
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Development Workflow
108
+
109
+ ### Setup
110
+
111
+ ```bash
112
+ git clone https://github.com/rinadelph/clinear.git
113
+ cd clinear
114
+ bash scripts/install-hooks.sh # MANDATORY — installs the secret-blocking pre-commit hook
115
+ uv venv
116
+ source .venv/bin/activate
117
+ uv pip install -e ".[dev]"
118
+ ```
119
+
120
+ ### Day-to-day
121
+
122
+ ```bash
123
+ # 1. Set your token (one-time per shell)
124
+ export LINEAR_TOKEN="lin_api_..."
125
+
126
+ # 2. Edit code
127
+
128
+ # 3. Smoke test live API
129
+ .venv/bin/clinear me
130
+
131
+ # 4. Full E2E suite (takes ~30s)
132
+ bash scripts/e2e-test.sh
133
+
134
+ # 5. Lint + type check
135
+ uv run ruff check clinear/
136
+ uv run mypy clinear/
137
+
138
+ # 6. Bump version (see "Versioning" below)
139
+
140
+ # 7. Commit (hook will refuse if you touch secrets)
141
+ git add -A
142
+ git commit -m "fix(issue): handle null state in IssueSearchResult"
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Testing
148
+
149
+ ### E2E (live API — primary suite)
150
+
151
+ `scripts/e2e-test.sh` exercises every command against the real Linear API.
152
+
153
+ ```bash
154
+ export LINEAR_TOKEN="lin_api_..."
155
+ bash scripts/e2e-test.sh
156
+ ```
157
+
158
+ The script reads `LINEAR_TOKEN` from your env. **It does not contain any hardcoded tokens.** If you find one, that's a bug — fix it immediately and rotate the leaked credential.
159
+
160
+ Pass condition: `SUMMARY: N passed, 0 failed`.
161
+
162
+ ### Unit tests (TODO — v0.3)
163
+
164
+ `tests/` is empty as of v0.2.0. Planned with `pytest` + `respx` (mocks httpx) + Pydantic fixture data.
165
+
166
+ ---
167
+
168
+ ## Versioning
169
+
170
+ We follow [Semantic Versioning](https://semver.org/):
171
+
172
+ | Bump | When |
173
+ |------|------|
174
+ | **Patch** `0.2.0 → 0.2.1` | Bugfixes, typo corrections, internal refactors with no behavior change. |
175
+ | **Minor** `0.2.x → 0.3.0` | New commands, new flags, new output formats. Backward-compatible. |
176
+ | **Major** `0.x → 1.0` (then `1 → 2`) | Removed/renamed commands, changed exit codes, changed default output. |
177
+
178
+ ### Version bump checklist (every change)
179
+
180
+ 1. Edit `VERSION` to the new number.
181
+ 2. Edit `pyproject.toml` → `version = "X.Y.Z"` (must match `VERSION`).
182
+ 3. Add a new section to `CHANGELOG.md`:
183
+ ```markdown
184
+ ## [X.Y.Z] — YYYY-MM-DD
185
+
186
+ ### Added
187
+ - ...
188
+
189
+ ### Changed
190
+ - ...
191
+
192
+ ### Fixed
193
+ - ...
194
+ ```
195
+ 4. Run the E2E suite. Must pass.
196
+ 5. Commit with message: `release: vX.Y.Z`.
197
+ 6. Tag: `git tag -a vX.Y.Z -m "vX.Y.Z"` and push: `git push origin vX.Y.Z`.
198
+ 7. Create GitHub release (see Release Process).
199
+ 8. Publish to PyPI (see Release Process).
200
+
201
+ ---
202
+
203
+ ## Release Process
204
+
205
+ ### 1. Pre-flight
206
+
207
+ ```bash
208
+ # Tests pass?
209
+ bash scripts/e2e-test.sh
210
+
211
+ # Lint passes?
212
+ uv run ruff check clinear/
213
+
214
+ # Version is bumped in BOTH places?
215
+ cat VERSION
216
+ grep '^version =' pyproject.toml
217
+
218
+ # CHANGELOG has the new section?
219
+ head -20 CHANGELOG.md
220
+ ```
221
+
222
+ ### 2. Tag and push
223
+
224
+ ```bash
225
+ git add -A
226
+ git commit -m "release: v0.3.0"
227
+ git push origin main
228
+
229
+ git tag -a v0.3.0 -m "v0.3.0"
230
+ git push origin v0.3.0
231
+ ```
232
+
233
+ ### 3. Build with uv
234
+
235
+ ```bash
236
+ rm -rf dist/
237
+ uv build
238
+ # → dist/clinear-0.3.0-py3-none-any.whl
239
+ # → dist/clinear-0.3.0.tar.gz
240
+ ```
241
+
242
+ ### 4. Publish to PyPI
243
+
244
+ ```bash
245
+ # Token MUST come from environment — never paste into a shell that gets logged.
246
+ export UV_PUBLISH_TOKEN="pypi-..."
247
+ uv publish
248
+ ```
249
+
250
+ ### 5. Create GitHub release
251
+
252
+ ```bash
253
+ gh release create v0.3.0 \
254
+ --title "v0.3.0" \
255
+ --notes-file <(awk '/^## \[0.3.0\]/,/^## \[/{if (!/^## \[/) print; if (/^## \[/ && !found) found=1; else if (/^## \[/) exit}' CHANGELOG.md) \
256
+ dist/clinear-0.3.0-py3-none-any.whl \
257
+ dist/clinear-0.3.0.tar.gz
258
+ ```
259
+
260
+ ### 6. Verify
261
+
262
+ ```bash
263
+ # Wait ~60s for PyPI to propagate, then:
264
+ uv tool install --refresh clinear
265
+ clinear --version # should print the new version
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Pre-commit Hook
271
+
272
+ `scripts/pre-commit.sh` is installed via `bash scripts/install-hooks.sh`. It runs on `git commit` and **refuses** to commit any staged file that contains:
273
+
274
+ - Linear API tokens (`lin_api_...`)
275
+ - PyPI API tokens (`pypi-...`)
276
+ - AWS access keys (`AKIA...`)
277
+ - GitHub PATs (`ghp_`, `ghs_`, `gho_`, `ghu_`, `ghr_`)
278
+ - Google service account JSON
279
+ - SSH/PGP private keys
280
+ - Slack tokens
281
+
282
+ It also blocks these file types entirely:
283
+ - `*.log`
284
+ - `.env`, `.env.*`
285
+ - `schema/linear-schema.json` (regenerable; it's 2 MB)
286
+ - Anything under `.secrets/` or `vault/`
287
+
288
+ **Bypassing:** `git commit --no-verify` (use only for vetted false positives; audit first).
289
+
290
+ ---
291
+
292
+ ## House Rules
293
+
294
+ 1. **No new runtime dependencies without a security review.** Every dep is a supply-chain attack vector. The dependency tree is currently: `pydantic + pydantic-core (Rust binary) + httpx + httpcore + h11 + idna + certifi + sniffio + anyio + typer + click + shellingham + rich + markdown-it-py + mdurl + pygments + typing-extensions + annotated-types + annotated-doc + exceptiongroup + tomli (py<3.11)`. Don't add to it without justification.
295
+ 2. **No `dict[str, Any]` leaks** out of `clinear.client`. Everything that hits a command handler must be a Pydantic model.
296
+ 3. **No silent failures.** If something can't be done, raise a `ClinearError` subclass with a helpful `hint`.
297
+ 4. **No print statements** outside `clinear/output.py` and a couple of init-related scripts. All output goes through the formatter.
298
+ 5. **No `--insecure` flag, ever.** TLS verification is non-negotiable.
299
+
300
+ ---
301
+
302
+ ## When something breaks
303
+
304
+ 1. Re-run `bash scripts/e2e-test.sh -v` (verbose flag prints full output).
305
+ 2. Linear API errors will be in the error message verbatim — look for `GRAPHQL_VALIDATION_FAILED` (your query is wrong) vs. `AUTHENTICATION_ERROR` (token issue).
306
+ 3. Pydantic validation errors mean the response shape changed or the model is wrong. Run with `--verbose` to see the raw response.
307
+ 4. `clinear raw query 'query { ... }'` is your friend — bypass our models, talk directly to Linear's GraphQL.
308
+ 5. Re-fetch the schema: `curl -X POST https://api.linear.app/graphql -H "Authorization: $LINEAR_TOKEN" -d '<introspection query>'`. Update fragments/models as needed.
309
+
310
+ ---
311
+
312
+ ## Roadmap
313
+
314
+ - v0.3 — pytest unit tests, hash-pinned `requirements.txt`, `docs/COMMANDS.md`, `clinear completions` for shell completion install
315
+ - v0.4 — `clinear initiative`, `clinear document`, `clinear customer` (next-tier domains)
316
+ - v0.5 — config-defined views (`--view my-bugs`), aliases
317
+ - v1.0 — stable command surface, full integration with the 8 priority domains
@@ -0,0 +1,59 @@
1
+ # Changelog
2
+
3
+ All notable changes to clinear will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [0.2.0] — 2026-05-14
11
+
12
+ ### Added
13
+ - `clinear init` — scaffold the config file at `~/.config/clinear/config.toml`.
14
+ - `clinear comment` group: `list`, `add`, `edit`, `delete`.
15
+ - `clinear label` group: `list`, `create`, `delete`.
16
+ - Label resolution in `issue create` and `issue update` — accept label names
17
+ (comma-separated) and resolve to UUIDs server-side per team.
18
+ - `VERSION` file at the repo root; `__version__` now reads from it.
19
+ - `CHANGELOG.md`.
20
+
21
+ ### Changed
22
+ - `cycle current` now exits 0 with `{ "active_cycle": null }` (JSON) or
23
+ a friendly message (human) instead of crashing with NotFoundError when
24
+ a team has no active cycle.
25
+ - `issue search` results now render a full table with priority, state,
26
+ assignee, and identifier columns.
27
+ - Bumped version to 0.2.0.
28
+
29
+ ### Fixed
30
+ - 4 issues from the v0.1 smoke test:
31
+ - `Issue.labels` / `Issue.subscribers` now flatten the GraphQL
32
+ `{nodes: [...]}` connection wrapper automatically.
33
+ - `searchIssues` no longer spreads the `Issue` fragment on
34
+ `IssueSearchResult` (different GraphQL type).
35
+ - YAML formatter no longer collapses nested object indentation.
36
+ - Error exit codes propagate correctly through the typer entrypoint.
37
+
38
+ ---
39
+
40
+ ## [0.1.0] — 2026-05-14
41
+
42
+ Initial release.
43
+
44
+ ### Added
45
+ - Core commands: `me`, `auth status/whoami`, `team list/get/states/members`,
46
+ `issue list/get/create/update/state/assign/prio/url/search`,
47
+ `project list/get`, `cycle current/list`, `raw query`.
48
+ - Pydantic v2 models for User, Team, Issue, Project, Cycle, WorkflowState.
49
+ - Async httpx GraphQL client with auth, retry, error handling, rate-limit
50
+ awareness, and pagination helper.
51
+ - Six output formats: `human` (Rich tables), `json`, `yaml`, `md` (markdown),
52
+ `plain` (TSV), `ids` (one identifier per line).
53
+ - Filter DSL for `issue list` covering team/state/assignee/project/cycle/
54
+ label/priority/free-text/date filters.
55
+ - Token resolution from `--token` flag, `LINEAR_TOKEN` env var, or
56
+ `config.toml`.
57
+ - Typed exit codes (0–8) for scriptable error handling.
58
+ - `--dry-run` for safe mutation previews.
59
+ - 28/29 passing E2E tests against the live Linear API.
clinear-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Clover
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.
clinear-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,234 @@
1
+ Metadata-Version: 2.4
2
+ Name: clinear
3
+ Version: 0.2.0
4
+ Summary: Type-safe Linear CLI built on Pydantic v2
5
+ Project-URL: Homepage, https://github.com/Clover/clinear
6
+ Project-URL: Documentation, https://github.com/Clover/clinear/blob/main/docs/DESIGN.md
7
+ Project-URL: Issues, https://github.com/Clover/clinear/issues
8
+ Author-email: Clover <rnadales@cloverve.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: cli,graphql,issue-tracker,linear,pydantic
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx<1.0,>=0.27
25
+ Requires-Dist: pydantic<3.0,>=2.10
26
+ Requires-Dist: rich<15.0,>=13.9
27
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
28
+ Requires-Dist: typer<1.0,>=0.15
29
+ Provides-Extra: dev
30
+ Requires-Dist: mypy>=1.13; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
32
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
33
+ Requires-Dist: pytest>=8.0; extra == 'dev'
34
+ Requires-Dist: respx>=0.21; extra == 'dev'
35
+ Requires-Dist: ruff>=0.7; extra == 'dev'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # clinear
39
+
40
+ > Type-safe Linear CLI built on Pydantic v2 + httpx + Typer.
41
+
42
+ A Linear command-line interface designed for **humans, agents, and CI/CD pipelines**. Every API response is a validated Pydantic model. Every command works in shell pipelines. JSON output is the canonical contract; the pretty human tables sit on top of it.
43
+
44
+ ```
45
+ clinear me # who am I?
46
+ clinear team list # all teams in workspace
47
+ clinear issue list --assignee me # my issues
48
+ clinear issue create --team ENG --title "Fix login bug" --priority 1
49
+ clinear -o json issue list | jq '.[].title' # pipe into anything
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Why clinear?
55
+
56
+ - **Type-safe.** Every response validated through Pydantic v2. No silent schema drift.
57
+ - **Agent-first.** Stable JSON contracts; pipe-friendly `-o ids` / `-o md` / `-o yaml`.
58
+ - **Tiny attack surface.** Only Pydantic + httpx + Typer + Rich. No npm chaos, no `postinstall` hooks.
59
+ - **Honest errors.** Linear API errors surfaced verbatim with proper POSIX exit codes.
60
+ - **Built for automation.** `--dry-run` for safe mutation previews, `raw query` escape hatch for any GraphQL.
61
+
62
+ ---
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ # From PyPI (recommended)
68
+ pip install clinear
69
+ # or
70
+ uv tool install clinear
71
+ ```
72
+
73
+ ```bash
74
+ # From source
75
+ git clone https://github.com/rinadelph/clinear.git
76
+ cd clinear
77
+ uv venv && source .venv/bin/activate
78
+ uv pip install -e .
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Quick Start
84
+
85
+ ### 1. Get a token
86
+
87
+ Generate a personal API key at <https://linear.app/settings/api>.
88
+
89
+ ### 2. Set it up
90
+
91
+ ```bash
92
+ export LINEAR_TOKEN="lin_api_..."
93
+ # Or persist a config:
94
+ clinear init
95
+ ```
96
+
97
+ ### 3. Verify
98
+
99
+ ```bash
100
+ clinear me
101
+ ```
102
+
103
+ ### 4. Use it
104
+
105
+ ```bash
106
+ # Read
107
+ clinear team list
108
+ clinear issue get ENG-123
109
+ clinear issue list --assignee me --state Todo
110
+
111
+ # Write
112
+ clinear issue create --team ENG --title "Fix login bug" --priority 1
113
+ clinear issue assign ENG-123 me
114
+ clinear issue state ENG-123 "In Progress"
115
+ clinear issue prio ENG-123 1
116
+ clinear comment add ENG-123 "Started on this — investigating now"
117
+
118
+ # Pipe
119
+ clinear -o ids issue list --assignee me | xargs -I{} clinear issue url {}
120
+ clinear -o json issue list --state Todo | jq '.[] | "\(.identifier): \(.title)"'
121
+
122
+ # Safety net
123
+ clinear --dry-run issue update ENG-123 --priority 2
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Output Formats
129
+
130
+ Set with `-o` / `--output` **before** the subcommand:
131
+
132
+ | Format | Flag | What you get |
133
+ |---|---|---|
134
+ | **human** | default | Pretty Rich tables with colors |
135
+ | **json** | `-o json` | Compact JSON, null-pruned |
136
+ | **yaml** | `-o yaml` | Hand-rolled minimal YAML |
137
+ | **md** | `-o md` | Markdown tables for PRs / reports |
138
+ | **plain** | `-o plain` | TSV — `id<TAB>state<TAB>...` |
139
+ | **ids** | `-o ids` | Just identifiers, one per line — great for `xargs` |
140
+
141
+ ---
142
+
143
+ ## Command Reference
144
+
145
+ ```
146
+ clinear
147
+ ├── me / auth status / auth whoami
148
+ ├── init Create ~/.config/clinear/config.toml
149
+ ├── team list / get / states / members
150
+ ├── issue
151
+ │ ├── list --team --state --assignee --label --priority --contains ...
152
+ │ ├── get <id>
153
+ │ ├── create --team --title [--description --priority --assignee --label ...]
154
+ │ ├── update <id> [--title --state --assignee --priority --label ...]
155
+ │ ├── state <id> <state-name>
156
+ │ ├── assign <id> <user>
157
+ │ ├── prio <id> <0-4>
158
+ │ ├── url <id>
159
+ │ └── search <query>
160
+ ├── project list / get
161
+ ├── cycle current <team> / list <team>
162
+ ├── comment list / add / edit / delete
163
+ ├── label list / create / delete
164
+ └── raw query <graphql> Escape hatch — arbitrary GraphQL
165
+ ```
166
+
167
+ Run `clinear <command> --help` for full flags on any subcommand.
168
+
169
+ ---
170
+
171
+ ## Configuration
172
+
173
+ Default location: `~/.config/clinear/config.toml`. Override with `$CLINEAR_CONFIG`.
174
+
175
+ ```toml
176
+ [auth]
177
+ token_env = "LINEAR_TOKEN" # read from this env var
178
+
179
+ [defaults]
180
+ team = "ENG"
181
+ output = "human"
182
+
183
+ [display]
184
+ color = true
185
+ table_max_width = 120
186
+ ```
187
+
188
+ Run `clinear init` to scaffold the file.
189
+
190
+ ---
191
+
192
+ ## Exit Codes (stable across versions)
193
+
194
+ | Code | Meaning |
195
+ |---|---|
196
+ | 0 | Success |
197
+ | 1 | Generic error |
198
+ | 2 | Usage error (bad flags) |
199
+ | 3 | Auth error (missing/invalid token) |
200
+ | 4 | Not found |
201
+ | 5 | Validation error (response didn't match model) |
202
+ | 6 | API error (Linear returned errors) |
203
+ | 7 | Network error (timeout, DNS, TLS) |
204
+ | 8 | Rate limited |
205
+
206
+ ---
207
+
208
+ ## Security
209
+
210
+ - Token read from `$LINEAR_TOKEN`, `--token` flag, or `config.toml`. Never logged in plaintext.
211
+ - HTTPS-only. TLS verification mandatory.
212
+ - No telemetry. Zero outbound calls except to `api.linear.app`.
213
+ - Pre-commit hook blocks committing tokens, `.log` files, `.env` files. See `scripts/pre-commit.sh`.
214
+
215
+ ---
216
+
217
+ ## Development
218
+
219
+ See [AGENTS.md](./AGENTS.md) for the contributor guide — architecture, testing, version bumping, and release process.
220
+
221
+ ```bash
222
+ git clone https://github.com/rinadelph/clinear.git
223
+ cd clinear
224
+ bash scripts/install-hooks.sh # install pre-commit hook
225
+ uv venv && uv pip install -e ".[dev]"
226
+ export LINEAR_TOKEN="lin_api_..."
227
+ bash scripts/e2e-test.sh # 36/36 should pass
228
+ ```
229
+
230
+ ---
231
+
232
+ ## License
233
+
234
+ MIT — see [LICENSE](./LICENSE).