tsk-cli 0.1.0__py3-none-any.whl
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.
- tsk_cli/__init__.py +0 -0
- tsk_cli/api.py +75 -0
- tsk_cli/auth.py +133 -0
- tsk_cli/config.py +73 -0
- tsk_cli/list_cmd.py +87 -0
- tsk_cli/main.py +234 -0
- tsk_cli/pull.py +432 -0
- tsk_cli/push.py +246 -0
- tsk_cli/resolve.py +40 -0
- tsk_cli/set_status.py +60 -0
- tsk_cli/show.py +59 -0
- tsk_cli-0.1.0.dist-info/METADATA +72 -0
- tsk_cli-0.1.0.dist-info/RECORD +16 -0
- tsk_cli-0.1.0.dist-info/WHEEL +5 -0
- tsk_cli-0.1.0.dist-info/entry_points.txt +2 -0
- tsk_cli-0.1.0.dist-info/top_level.txt +1 -0
tsk_cli/resolve.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared utilities for resolving thing refs to local .task/ files.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from .push import ParsedThingFile, parse_thing_markdown
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def task_root(target_dir: Path | None = None) -> Path:
|
|
14
|
+
return (target_dir or Path.cwd()) / ".task"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def find_thing_file(ref: str, target_dir: Path | None = None) -> Optional[Path]:
|
|
18
|
+
"""Find the .md file for a thing ref like COM-001."""
|
|
19
|
+
root = task_root(target_dir)
|
|
20
|
+
projects_dir = root / "projects"
|
|
21
|
+
if not projects_dir.is_dir():
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
ref_upper = ref.upper()
|
|
25
|
+
for md in sorted(projects_dir.glob("**/things/*.md")):
|
|
26
|
+
if md.stem.upper() == ref_upper:
|
|
27
|
+
return md
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def parse_thing_at(path: Path) -> ParsedThingFile:
|
|
32
|
+
return parse_thing_markdown(path.read_text())
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def snapshot_path_for(thing_path: Path, target_dir: Path | None = None) -> Path:
|
|
36
|
+
"""Return the .snapshot/ counterpart of a thing file."""
|
|
37
|
+
root = task_root(target_dir)
|
|
38
|
+
projects_dir = root / "projects"
|
|
39
|
+
rel = thing_path.relative_to(projects_dir)
|
|
40
|
+
return root / ".snapshot" / "projects" / rel
|
tsk_cli/set_status.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tsk set-status -- update a thing's status and push immediately.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from .api import api_get, api_patch
|
|
13
|
+
from .push import build_thing_markdown
|
|
14
|
+
from .resolve import find_thing_file, parse_thing_at, snapshot_path_for
|
|
15
|
+
|
|
16
|
+
VALID_STATUSES = ("not_started", "in_progress", "in_review", "done")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def set_status(ref: str, status: str, target_dir: Path | None = None) -> None:
|
|
20
|
+
if status not in VALID_STATUSES:
|
|
21
|
+
click.echo(
|
|
22
|
+
f"Invalid status: {status}. Must be one of: {', '.join(VALID_STATUSES)}",
|
|
23
|
+
err=True,
|
|
24
|
+
)
|
|
25
|
+
raise SystemExit(1)
|
|
26
|
+
|
|
27
|
+
thing_path = find_thing_file(ref, target_dir)
|
|
28
|
+
if not thing_path:
|
|
29
|
+
click.echo(f"Thing not found: {ref}. Run tsk pull first.", err=True)
|
|
30
|
+
raise SystemExit(1)
|
|
31
|
+
|
|
32
|
+
parsed = parse_thing_at(thing_path)
|
|
33
|
+
thing_id = parsed.frontmatter.get("id", "")
|
|
34
|
+
if not thing_id:
|
|
35
|
+
click.echo(f"Thing file missing id: {thing_path}", err=True)
|
|
36
|
+
raise SystemExit(1)
|
|
37
|
+
|
|
38
|
+
old_status = parsed.frontmatter.get("status", "")
|
|
39
|
+
if old_status == status:
|
|
40
|
+
click.echo(f"{ref} is already {status}")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
updated = api_patch(f"/api/things/{thing_id}/", {"status": status})
|
|
45
|
+
except Exception as e:
|
|
46
|
+
click.echo(f"Failed to update {ref}: {e}", err=True)
|
|
47
|
+
raise SystemExit(1)
|
|
48
|
+
|
|
49
|
+
parsed.frontmatter["status"] = status
|
|
50
|
+
if isinstance(updated, dict) and updated.get("updated_at"):
|
|
51
|
+
parsed.frontmatter["updated_at"] = str(updated["updated_at"])
|
|
52
|
+
|
|
53
|
+
new_raw = build_thing_markdown(parsed.frontmatter, parsed.name, parsed.body)
|
|
54
|
+
thing_path.write_text(new_raw)
|
|
55
|
+
|
|
56
|
+
snap = snapshot_path_for(thing_path, target_dir)
|
|
57
|
+
if snap.parent.is_dir():
|
|
58
|
+
snap.write_text(new_raw)
|
|
59
|
+
|
|
60
|
+
click.echo(f"{ref}: {old_status} → {status}")
|
tsk_cli/show.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tsk show -- display a thing's details in the terminal.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from .config import get_server_url
|
|
12
|
+
from .resolve import find_thing_file, parse_thing_at
|
|
13
|
+
|
|
14
|
+
_STATUS_COLORS = {
|
|
15
|
+
"not_started": "white",
|
|
16
|
+
"in_progress": "cyan",
|
|
17
|
+
"in_review": "yellow",
|
|
18
|
+
"done": "green",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def show(ref: str, target_dir: Path | None = None) -> None:
|
|
23
|
+
thing_path = find_thing_file(ref, target_dir)
|
|
24
|
+
if not thing_path:
|
|
25
|
+
click.echo(f"Thing not found: {ref}. Run tsk pull first.", err=True)
|
|
26
|
+
raise SystemExit(1)
|
|
27
|
+
|
|
28
|
+
parsed = parse_thing_at(thing_path)
|
|
29
|
+
fm = parsed.frontmatter
|
|
30
|
+
|
|
31
|
+
display_ref = fm.get("ref", ref)
|
|
32
|
+
status = fm.get("status", "not_started")
|
|
33
|
+
assigned = fm.get("assigned_to", "")
|
|
34
|
+
thing_id = fm.get("id", "")
|
|
35
|
+
created = fm.get("created_at", "")
|
|
36
|
+
updated = fm.get("updated_at", "")
|
|
37
|
+
|
|
38
|
+
color = _STATUS_COLORS.get(status, "white")
|
|
39
|
+
|
|
40
|
+
click.echo()
|
|
41
|
+
click.echo(click.style(f" {display_ref}", bold=True) + f" {parsed.name}")
|
|
42
|
+
click.echo(f" Status: {click.style(status, fg=color)}")
|
|
43
|
+
if assigned:
|
|
44
|
+
click.echo(f" Assigned to: {assigned}")
|
|
45
|
+
if created:
|
|
46
|
+
click.echo(f" Created: {created}")
|
|
47
|
+
if updated:
|
|
48
|
+
click.echo(f" Updated: {updated}")
|
|
49
|
+
|
|
50
|
+
if thing_id:
|
|
51
|
+
server = get_server_url()
|
|
52
|
+
click.echo(f" Web: {server}/projects?thing={thing_id}")
|
|
53
|
+
|
|
54
|
+
if parsed.body.strip():
|
|
55
|
+
click.echo()
|
|
56
|
+
for line in parsed.body.strip().split("\n"):
|
|
57
|
+
click.echo(f" {line}")
|
|
58
|
+
|
|
59
|
+
click.echo()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tsk-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI for TSK — pull projects and things for editor agents
|
|
5
|
+
Author: Rob Crosby
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://tsk.tools
|
|
8
|
+
Project-URL: Repository, https://github.com/rncrosby/text_plan
|
|
9
|
+
Project-URL: Issues, https://github.com/rncrosby/text_plan/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Bug Tracking
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: click>=8.1
|
|
22
|
+
Requires-Dist: requests>=2.31
|
|
23
|
+
|
|
24
|
+
# tsk-cli
|
|
25
|
+
|
|
26
|
+
Command-line interface for [TSK](https://tsk.tools) — pull projects and things into your editor so AI agents can work with them.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pipx install tsk-cli
|
|
32
|
+
# or
|
|
33
|
+
pip install tsk-cli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Requires Python 3.11+.
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
tsk login # authenticate via browser
|
|
42
|
+
tsk orgs # list your organizations
|
|
43
|
+
tsk org <name> # set the default org
|
|
44
|
+
tsk pull # sync projects and things to .task/
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
| Command | Description |
|
|
50
|
+
|---------|-------------|
|
|
51
|
+
| `tsk login [--server URL]` | Authenticate via browser OAuth |
|
|
52
|
+
| `tsk logout` | Clear stored credentials |
|
|
53
|
+
| `tsk orgs` | List your organizations |
|
|
54
|
+
| `tsk org <name-or-id>` | Set the default organization |
|
|
55
|
+
| `tsk pull` | Sync projects/things to `.task/` |
|
|
56
|
+
| `tsk push [--force]` | Upload local edits to the server |
|
|
57
|
+
| `tsk status` | Show current config and auth state |
|
|
58
|
+
| `tsk list [--status S] [--project P]` | List things with optional filters |
|
|
59
|
+
| `tsk show <ref>` | Display a thing's details |
|
|
60
|
+
| `tsk set-status <ref> <status>` | Update status and push immediately |
|
|
61
|
+
| `tsk start <ref>` | Set status to `in_progress` |
|
|
62
|
+
| `tsk done <ref>` | Set status to `done` |
|
|
63
|
+
| `tsk review <ref>` | Set status to `in_review` |
|
|
64
|
+
| `tsk open <ref>` | Open thing in browser |
|
|
65
|
+
|
|
66
|
+
## How it works
|
|
67
|
+
|
|
68
|
+
`tsk pull` writes a `.task/` directory containing your projects and things as markdown files. Editor agents (Cursor, Copilot, etc.) can read these files to understand what to work on. After making changes locally, `tsk push` syncs them back to the server.
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
tsk_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
tsk_cli/api.py,sha256=u4fmifdwWS40thNcUUfZ_QXlHpRz0Klmt-Oy-pjafwo,2243
|
|
3
|
+
tsk_cli/auth.py,sha256=xpm4kOQZnO5trRXnSoY7fUcSrpnRVHeup-SSzr6Q20M,4162
|
|
4
|
+
tsk_cli/config.py,sha256=CiDT01vclJGgFasCtuJaBWPWqDT_SoY2Hl4I6KFPN1s,1858
|
|
5
|
+
tsk_cli/list_cmd.py,sha256=fIYfIjDn47Bs0SndkOWo8ZUb3jWcfA9OJyhdv0x1Wa4,2407
|
|
6
|
+
tsk_cli/main.py,sha256=3DiAiHzr-oLQYyOJyjnRm7CPuoGE77YHXBhPiTdSeas,6789
|
|
7
|
+
tsk_cli/pull.py,sha256=HTN7fbQzf56ECNxK-oKoUIPqVIJQjjixpEfku9lLA4s,15046
|
|
8
|
+
tsk_cli/push.py,sha256=QvvYmUK-B30ev35uNaGv_BPwu8P8tfWGKZy6Y4aN7tE,7651
|
|
9
|
+
tsk_cli/resolve.py,sha256=A7dwyL8MqEg36l6MZr3g-mVj0JY5vdk9MNVQ5E3a3Ek,1170
|
|
10
|
+
tsk_cli/set_status.py,sha256=y05GnjqTsIAHoQ4moqdtCYpxRuGotjhQOKb8JmY0zhY,1877
|
|
11
|
+
tsk_cli/show.py,sha256=3pU8dCXq6Ouhv7PI1GA5VcPTNmxmLFLThoFU9xiaf3s,1585
|
|
12
|
+
tsk_cli-0.1.0.dist-info/METADATA,sha256=sL8b6Z9ix3InJf9ZqDI9XJ_XRq3oqfK8qh4LEsYvS-s,2465
|
|
13
|
+
tsk_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
14
|
+
tsk_cli-0.1.0.dist-info/entry_points.txt,sha256=OVoWHmhlhQz8FMLUY-aQsvLvKbHfRZby0nUxChtSdsE,41
|
|
15
|
+
tsk_cli-0.1.0.dist-info/top_level.txt,sha256=3fQVFC52p0S8aHWeh7itafx5rqvuv_3S0Vm0dpiBG5Q,8
|
|
16
|
+
tsk_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tsk_cli
|