generated from coulomb/repo-seed
Add create-workstream: MCP tool, CLI commands, dashboard hint
MCP server: add create_workstream(topic_id, title, slug?, owner?, description?, due_date?) — auto-generates slug from title if omitted; emits workstream_created progress event. Now 12 tools total. CLI: add two new subcommands — custodian create-workstream --domain DOMAIN --title TITLE [--slug] [--owner] [--description] custodian create-task --workstream ID_OR_SLUG --title TITLE [--priority] [--assignee] create-task accepts workstream UUID or slug (resolves via API). Dashboard: hint box below "Open Workstreams by Domain" chart listing registered domains that have zero workstreams, with the exact custodian create-workstream command to run. TOOLS.md: updated tool count (11 → 12) and added create_workstream row. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
102
custodian_cli.py
102
custodian_cli.py
@@ -161,6 +161,88 @@ def cmd_register(args: argparse.Namespace) -> None:
|
||||
print("Next: restart Claude Code for the MCP server to be active in this project.")
|
||||
|
||||
|
||||
def cmd_create_workstream(args: argparse.Namespace) -> None:
|
||||
"""Create a workstream under a domain's topic."""
|
||||
_api_get("/state/health")
|
||||
|
||||
# Resolve topic_id from domain
|
||||
topics = _api_get("/topics/?status=active")
|
||||
match = next((t for t in topics if t.get("domain") == args.domain), None)
|
||||
if not match:
|
||||
print(f"ERROR: No active topic for domain '{args.domain}'.")
|
||||
sys.exit(1)
|
||||
topic_id = match["id"]
|
||||
|
||||
slug = args.slug or re.sub(r"[^a-z0-9]+", "-", args.title.lower()).strip("-")
|
||||
|
||||
ws = _api_post("/workstreams/", {
|
||||
"topic_id": topic_id,
|
||||
"title": args.title,
|
||||
"slug": slug,
|
||||
"description": args.description,
|
||||
"owner": args.owner,
|
||||
"status": "active",
|
||||
})
|
||||
_api_post("/progress/", {
|
||||
"topic_id": topic_id,
|
||||
"workstream_id": ws["id"],
|
||||
"event_type": "workstream_created",
|
||||
"summary": f"Workstream created: {args.title}",
|
||||
"author": "custodian",
|
||||
"detail": {"owner": args.owner, "slug": slug},
|
||||
})
|
||||
print(f"Created workstream: {ws['title']}")
|
||||
print(f" id: {ws['id']}")
|
||||
print(f" slug: {ws['slug']}")
|
||||
print(f" domain: {args.domain}")
|
||||
print(f" owner: {ws.get('owner') or '—'}")
|
||||
|
||||
|
||||
def cmd_create_task(args: argparse.Namespace) -> None:
|
||||
"""Create a task under a workstream (by ID or slug)."""
|
||||
_api_get("/state/health")
|
||||
|
||||
# Resolve workstream: accept UUID or slug
|
||||
workstream_id = args.workstream
|
||||
if not _is_uuid(workstream_id):
|
||||
wss = _api_get("/workstreams/")
|
||||
match = next((w for w in wss if w.get("slug") == workstream_id), None)
|
||||
if not match:
|
||||
print(f"ERROR: No workstream found with slug '{workstream_id}'.")
|
||||
print(" Use 'custodian status' or check the dashboard for valid slugs.")
|
||||
sys.exit(1)
|
||||
workstream_id = match["id"]
|
||||
|
||||
task = _api_post("/tasks/", {
|
||||
"workstream_id": workstream_id,
|
||||
"title": args.title,
|
||||
"priority": args.priority,
|
||||
"description": args.description,
|
||||
"assignee": args.assignee,
|
||||
})
|
||||
_api_post("/progress/", {
|
||||
"workstream_id": workstream_id,
|
||||
"task_id": task["id"],
|
||||
"event_type": "task_created",
|
||||
"summary": f"Task created: {args.title}",
|
||||
"author": "custodian",
|
||||
"detail": {"priority": args.priority},
|
||||
})
|
||||
print(f"Created task: {task['title']}")
|
||||
print(f" id: {task['id']}")
|
||||
print(f" priority: {task['priority']}")
|
||||
print(f" status: {task['status']}")
|
||||
|
||||
|
||||
def _is_uuid(s: str) -> bool:
|
||||
import uuid as _uuid
|
||||
try:
|
||||
_uuid.UUID(s)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def cmd_status(_args: argparse.Namespace) -> None:
|
||||
"""Quick status: API health + summary totals."""
|
||||
health = _api_get("/state/health")
|
||||
@@ -202,6 +284,22 @@ def main() -> None:
|
||||
help="Project directory (defaults to current directory)",
|
||||
)
|
||||
|
||||
# create-workstream
|
||||
cws = sub.add_parser("create-workstream", help="Create a workstream under a domain topic")
|
||||
cws.add_argument("--domain", choices=VALID_DOMAINS, required=True, help="Domain to create the workstream under")
|
||||
cws.add_argument("--title", required=True, help="Workstream title")
|
||||
cws.add_argument("--slug", default=None, help="URL slug (auto-generated from title if omitted)")
|
||||
cws.add_argument("--owner", default=None, help="Owner name")
|
||||
cws.add_argument("--description", default=None, help="Optional description")
|
||||
|
||||
# create-task
|
||||
ctask = sub.add_parser("create-task", help="Create a task under a workstream")
|
||||
ctask.add_argument("--workstream", required=True, metavar="ID_OR_SLUG", help="Workstream UUID or slug")
|
||||
ctask.add_argument("--title", required=True, help="Task title")
|
||||
ctask.add_argument("--priority", choices=["low", "medium", "high", "critical"], default="medium")
|
||||
ctask.add_argument("--assignee", default=None)
|
||||
ctask.add_argument("--description", default=None)
|
||||
|
||||
# status
|
||||
sub.add_parser("status", help="Show State Hub health and summary totals")
|
||||
|
||||
@@ -209,6 +307,10 @@ def main() -> None:
|
||||
|
||||
if args.command == "register-project":
|
||||
cmd_register(args)
|
||||
elif args.command == "create-workstream":
|
||||
cmd_create_workstream(args)
|
||||
elif args.command == "create-task":
|
||||
cmd_create_task(args)
|
||||
elif args.command == "status":
|
||||
cmd_status(args)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user