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.
- clinear-0.2.0/.gitignore +50 -0
- clinear-0.2.0/.python-version +1 -0
- clinear-0.2.0/AGENTS.md +317 -0
- clinear-0.2.0/CHANGELOG.md +59 -0
- clinear-0.2.0/LICENSE +21 -0
- clinear-0.2.0/PKG-INFO +234 -0
- clinear-0.2.0/README.md +197 -0
- clinear-0.2.0/VERSION +1 -0
- clinear-0.2.0/clinear/__init__.py +22 -0
- clinear-0.2.0/clinear/__main__.py +6 -0
- clinear-0.2.0/clinear/auth.py +43 -0
- clinear-0.2.0/clinear/cli.py +122 -0
- clinear-0.2.0/clinear/cli_state.py +50 -0
- clinear-0.2.0/clinear/client.py +265 -0
- clinear-0.2.0/clinear/commands/__init__.py +4 -0
- clinear-0.2.0/clinear/commands/auth_cmd.py +42 -0
- clinear-0.2.0/clinear/commands/comment.py +149 -0
- clinear-0.2.0/clinear/commands/cycle.py +75 -0
- clinear-0.2.0/clinear/commands/init.py +84 -0
- clinear-0.2.0/clinear/commands/issue.py +366 -0
- clinear-0.2.0/clinear/commands/label.py +163 -0
- clinear-0.2.0/clinear/commands/project.py +57 -0
- clinear-0.2.0/clinear/commands/raw.py +50 -0
- clinear-0.2.0/clinear/commands/team.py +102 -0
- clinear-0.2.0/clinear/config.py +131 -0
- clinear-0.2.0/clinear/errors.py +107 -0
- clinear-0.2.0/clinear/filters.py +150 -0
- clinear-0.2.0/clinear/graphql/__init__.py +5 -0
- clinear-0.2.0/clinear/graphql/fragments.py +137 -0
- clinear-0.2.0/clinear/graphql/mutations.py +160 -0
- clinear-0.2.0/clinear/graphql/queries.py +245 -0
- clinear-0.2.0/clinear/models/__init__.py +37 -0
- clinear-0.2.0/clinear/models/base.py +64 -0
- clinear-0.2.0/clinear/models/cycle.py +32 -0
- clinear-0.2.0/clinear/models/enums.py +62 -0
- clinear-0.2.0/clinear/models/issue.py +99 -0
- clinear-0.2.0/clinear/models/label.py +17 -0
- clinear-0.2.0/clinear/models/project.py +60 -0
- clinear-0.2.0/clinear/models/team.py +30 -0
- clinear-0.2.0/clinear/models/user.py +32 -0
- clinear-0.2.0/clinear/models/workflow.py +36 -0
- clinear-0.2.0/clinear/output.py +412 -0
- clinear-0.2.0/docs/DESIGN.md +431 -0
- clinear-0.2.0/pyproject.toml +79 -0
- clinear-0.2.0/schema/schema-summary.json +1498 -0
- clinear-0.2.0/scripts/e2e-test.sh +120 -0
- clinear-0.2.0/scripts/install-hooks.sh +13 -0
- clinear-0.2.0/scripts/pre-commit.sh +99 -0
- clinear-0.2.0/scripts/ship.sh +116 -0
clinear-0.2.0/.gitignore
ADDED
|
@@ -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
|
clinear-0.2.0/AGENTS.md
ADDED
|
@@ -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).
|