Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 60 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,79 @@
# Task CLI - Test Target for ai-gitops
# Task CLI

A minimal Python CLI task manager used to test the [ai-gitops](https://github.com/scooke11/ai-gitops) workflow.
A simple task manager for the command line.

## What is this?
## Usage

This is a **test target repository** - not a real project. It exists solely to validate that our AI-assisted bounty hunting workflow looks professional before we use it on real open-source projects.
### Add a task
```bash
python task.py add "Buy groceries"
# Added task 1: Buy groceries
```

## Installation
### List tasks
```bash
python task.py list
# [ ] 1. Buy groceries
# [✓] 2. Write docs
```

### Mark task as done
```bash
python task.py --help
python task.py done 1
# Marked task 1 as done: Buy groceries
```

## Usage
## JSON Output

```bash
# Add a task
python task.py add "Buy groceries"
All commands support a `--json` flag for scripting and automation:

# List tasks
python task.py list
### Add (JSON)
```bash
python task.py --json add "Deploy v2"
```
```json
{"status": "added", "task": {"id": 1, "description": "Deploy v2", "done": false}}
```

# Complete a task
python task.py done 1
### List (JSON)
```bash
python task.py --json list
```
```json
{"tasks": [{"id": 1, "description": "Deploy v2", "done": false}], "count": 1}
```

## Testing
### Done (JSON)
```bash
python task.py --json done 1
```
```json
{"status": "done", "task": {"id": 1, "description": "Deploy v2", "done": true}}
```

### Scripting example
```bash
python -m pytest test_task.py
# Add a task and capture the ID
TASK_ID=$(python task.py --json add "Run tests" | python -c "import sys,json; print(json.load(sys.stdin)['task']['id'])")

# Mark it done
python task.py --json done $TASK_ID

# List as JSON and pipe to jq
python task.py --json list | jq '.tasks[] | select(.done == false)'
```

## Configuration

Copy `config.yaml.example` to `~/.config/task-cli/config.yaml` and customize.
Copy the example config:
```bash
mkdir -p ~/.config/task-cli
cp config.yaml.example ~/.config/task-cli/config.yaml
```

## Tests

```bash
pip install pytest
pytest test_task.py -v
```
21 changes: 16 additions & 5 deletions commands/add.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Add task command."""

import json
import sys
from pathlib import Path


Expand All @@ -11,16 +12,20 @@ def get_tasks_file():

def validate_description(description):
"""Validate task description."""
# NOTE: Validation logic scattered here - should be in utils (refactor bounty)
if not description:
raise ValueError("Description cannot be empty")
if len(description) > 200:
raise ValueError("Description too long (max 200 chars)")
return description.strip()


def add_task(description):
"""Add a new task."""
def add_task(description, *, json_output=False):
"""Add a new task.

Args:
description: Task description text.
json_output: If True, output result as JSON instead of plain text.
"""
description = validate_description(description)

tasks_file = get_tasks_file()
Expand All @@ -31,7 +36,13 @@ def add_task(description):
tasks = json.loads(tasks_file.read_text())

task_id = len(tasks) + 1
tasks.append({"id": task_id, "description": description, "done": False})
task = {"id": task_id, "description": description, "done": False}
tasks.append(task)

tasks_file.write_text(json.dumps(tasks, indent=2))
print(f"Added task {task_id}: {description}")

if json_output:
json.dump({"status": "added", "task": task}, sys.stdout)
print() # trailing newline
else:
print(f"Added task {task_id}: {description}")
29 changes: 23 additions & 6 deletions commands/done.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Mark task done command."""

import json
import sys
from pathlib import Path


Expand All @@ -11,17 +12,25 @@ def get_tasks_file():

def validate_task_id(tasks, task_id):
"""Validate task ID exists."""
# NOTE: Validation logic scattered here - should be in utils (refactor bounty)
if task_id < 1 or task_id > len(tasks):
raise ValueError(f"Invalid task ID: {task_id}")
return task_id


def mark_done(task_id):
"""Mark a task as complete."""
def mark_done(task_id, *, json_output=False):
"""Mark a task as complete.

Args:
task_id: ID of the task to mark as done.
json_output: If True, output result as JSON instead of plain text.
"""
tasks_file = get_tasks_file()
if not tasks_file.exists():
print("No tasks found!")
if json_output:
json.dump({"status": "error", "message": "No tasks found"}, sys.stdout)
print()
else:
print("No tasks found!")
return

tasks = json.loads(tasks_file.read_text())
Expand All @@ -31,7 +40,15 @@ def mark_done(task_id):
if task["id"] == task_id:
task["done"] = True
tasks_file.write_text(json.dumps(tasks, indent=2))
print(f"Marked task {task_id} as done: {task['description']}")
if json_output:
json.dump({"status": "done", "task": task}, sys.stdout)
print()
else:
print(f"Marked task {task_id} as done: {task['description']}")
return

print(f"Task {task_id} not found")
if json_output:
json.dump({"status": "error", "message": f"Task {task_id} not found"}, sys.stdout)
print()
else:
print(f"Task {task_id} not found")
34 changes: 25 additions & 9 deletions commands/list.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""List tasks command."""

import json
import sys
from pathlib import Path


Expand All @@ -11,27 +12,42 @@ def get_tasks_file():

def validate_task_file():
"""Validate tasks file exists."""
# NOTE: Validation logic scattered here - should be in utils (refactor bounty)
tasks_file = get_tasks_file()
if not tasks_file.exists():
return []
return tasks_file


def list_tasks():
"""List all tasks."""
# NOTE: No --json flag support yet (feature bounty)
def list_tasks(*, json_output=False):
"""List all tasks.

Args:
json_output: If True, output result as JSON instead of plain text.
"""
tasks_file = validate_task_file()

if not tasks_file:
print("No tasks yet!")
if json_output:
json.dump({"tasks": [], "count": 0}, sys.stdout)
print()
else:
print("No tasks yet!")
return

tasks = json.loads(tasks_file.read_text())

if not tasks:
print("No tasks yet!")
if json_output:
json.dump({"tasks": [], "count": 0}, sys.stdout)
print()
else:
print("No tasks yet!")
return

for task in tasks:
status = "✓" if task["done"] else " "
print(f"[{status}] {task['id']}. {task['description']}")
if json_output:
json.dump({"tasks": tasks, "count": len(tasks)}, sys.stdout)
print()
else:
for task in tasks:
status = "✓" if task["done"] else " "
print(f"[{status}] {task['id']}. {task['description']}")
14 changes: 11 additions & 3 deletions task.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ def load_config():

def main():
parser = argparse.ArgumentParser(description="Simple task manager")
parser.add_argument(
"--json",
action="store_true",
default=False,
help="Output in JSON format for scripting and automation",
)
subparsers = parser.add_subparsers(dest="command", help="Command to run")

# Add command
Expand All @@ -35,12 +41,14 @@ def main():

args = parser.parse_args()

json_output = args.json

if args.command == "add":
add_task(args.description)
add_task(args.description, json_output=json_output)
elif args.command == "list":
list_tasks()
list_tasks(json_output=json_output)
elif args.command == "done":
mark_done(args.task_id)
mark_done(args.task_id, json_output=json_output)
else:
parser.print_help()

Expand Down
Loading