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/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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tsk = tsk_cli.main:cli
@@ -0,0 +1 @@
1
+ tsk_cli