eduard/cobra-boards

Cobra Boards, team task boards you can run from Claude or ChatGPT. FastAPI + DataK3 + appid + Dodil Models + MCP.

A remix template · 1 remixes · Remix eduard/cobra-boards

app/boards/doors/text.py

"""How the board reads in a chat. Claude and ChatGPT get short, scannable text with the numbers
people say out loud ("#12", "Ops"), not a JSON dump they have to re-explain to their person."""


def _card_line(c: dict, show_board: bool = False) -> str:
    bits = [f"#{c['number']} {c['title']}"]
    if show_board:
        bits.append(f"[{c['board']} › {c['list']}]")
    if c.get("assignee"):
        bits.append(f"@{c['assignee']}")
    if c.get("due_date"):
        bits.append(("OVERDUE " if c.get("overdue") else "due ") + c["due_date"])
    if c.get("blocked_by"):
        bits.append("blocked by " + ", ".join(f"#{n}" for n in c["blocked_by"]))
    if c.get("labels"):
        bits.append(" ".join(f"`{l}`" for l in c["labels"]))
    return "- " + " · ".join(bits)


def boards(items: list) -> str:
    if not items:
        return "You're not on any board yet. Create one with create_board."
    lines = [f"- **{b['title']}** ({b['role']}) · {b['open_cards']} open"
             + (f" · {b['overdue']} overdue" if b["overdue"] else "") + f" · id `{b['id']}`"
             for b in items]
    return "Your boards:\n" + "\n".join(lines)


def board(b: dict) -> str:
    head = f"**{b['title']}** · you are {b['role']}" + ("" if b["can_edit"] else " (read only)")
    out = [head]
    for lst in b["lists"]:
        out.append(f"\n**{lst['name']}** ({len(lst['cards'])})" + (" ✓" if lst["is_done"] else ""))
        out += [_card_line(c) for c in lst["cards"]] or ["- (empty)"]
    out.append("\nPeople: " + ", ".join(f"{m['email']} ({m['role']})" for m in b["members"]))
    return "\n".join(out)


def card(c: dict) -> str:
    out = [f"**#{c['number']} {c['title']}** · {c['board']} › {c['list']}"
           + (" · done" if c.get("done") else "") + f" · version {c['version']}"]
    if c.get("description"):
        out.append(c["description"])
    facts = []
    facts.append(f"assignee: {c['assignee'] or 'nobody'}")
    facts.append(f"due: {c['due_date'] or 'no date'}" + (" (OVERDUE)" if c.get("overdue") else ""))
    if c.get("labels"):
        facts.append("labels: " + ", ".join(c["labels"]))
    out.append(" · ".join(facts))
    if c.get("blocked_by_all"):
        out.append("Waits on: " + ", ".join(f"#{b['number']} {b['title']}" + (" (done)" if b["done"]
                                                                              else "")
                                            for b in c["blocked_by_all"]))
    if c.get("blocks"):
        out.append("Blocks: " + ", ".join(f"#{n}" for n in c["blocks"]))
    if c.get("checklist"):
        done = sum(i["done"] for i in c["checklist"])
        out.append(f"Checklist {done}/{len(c['checklist'])}:")
        out += [f"- [{'x' if i['done'] else ' '}] {i['text']} (`{i['id']}`)" for i in c["checklist"]]
    if c.get("archived"):
        out.append("ARCHIVED" + (f" by {c['archived_by']}" if c.get("archived_by") else "")
                   + ": off the board; restore_card brings it back.")
    if c.get("attachments"):
        out.append("Files: " + ", ".join(f"{a['name']} ({_size(a['size'])})"
                                          for a in c["attachments"]))
    if c.get("comments"):
        out.append(f"Comments ({len(c['comments'])}):")
        out += [f"- {(x['at'] or '')[:16].replace('T', ' ')} {x['author']}: {x['body']}"
                for x in c["comments"][-8:]]
    if c.get("history"):
        out.append("Recent history:")
        out += [f"- {h['at'][:16].replace('T', ' ')} {h['actor']}: {h['summary']}"
                for h in c["history"][:8]]
    return "\n".join(out)


def card_changed(c: dict, verb: str) -> str:
    return f"{verb} #{c['number']} {c['title']} → {c['board']} › {c['list']}" + (
        f" · @{c['assignee']}" if c.get("assignee") else "") + (
        f" · due {c['due_date']}" if c.get("due_date") else "") + f" (version {c['version']})"


def my_week(w: dict) -> str:
    sections = [("Overdue", w["overdue"]), ("Due in the next 7 days", w["due_soon"]),
                ("No due date", w["no_due_date"]), ("Later", w["later"])]
    total = sum(len(v) for _, v in sections)
    if not total:
        return f"Nothing open is assigned to you (today is {w['today']})."
    out = [f"Your plate today ({w['today']}): {total} open card" + ("s" if total != 1 else "")]
    for name, items in sections:
        if items:
            out.append(f"\n**{name}** ({len(items)})")
            out += [_card_line(c, show_board=True) for c in items]
    return "\n".join(out)


def card_list(items: list, empty: str, title: str) -> str:
    if not items:
        return empty
    return f"{title} ({len(items)}):\n" + "\n".join(_card_line(c, show_board=True) for c in items)


def blocking(r: dict) -> str:
    if not r["blocked"]:
        return f"Nothing is blocking #{r['card']} {r['title']}: it can go ahead."
    out = [f"#{r['card']} {r['title']} is waiting on {len(r['chain'])} unfinished card"
           + ("s" if len(r["chain"]) != 1 else "") + ":"]
    for c in r["chain"]:
        waits = (" ← waits on " + ", ".join(f"#{n}" for n in c["waits_on"])) if c["waits_on"] else ""
        out.append("  " * (c["depth"] - 1) + f"- #{c['number']} {c['title']} [{c['list']}]"
                   + (f" @{c['assignee']}" if c["assignee"] else " (unassigned)") + waits)
    out.append("Start with: " + ", ".join(f"#{c['number']} {c['title']}" for c in r["start_with"]))
    return "\n".join(out)


def history(items: list) -> str:
    if not items:
        return "No history yet."
    return "\n".join(f"- {h['at'][:16].replace('T', ' ')} {h['actor']}: {h['summary']}"
                     for h in items)


def checklist(r: dict) -> str:
    items = r["checklist"]
    done = sum(i["done"] for i in items)
    return f"#{r['card']} checklist {done}/{len(items)}:\n" + "\n".join(
        f"- [{'x' if i['done'] else ' '}] {i['text']} (`{i['id']}`)" for i in items)


def similar(r: dict) -> str:
    if not r["cards"]:
        return "No similar cards on your boards."
    return f"Cards like “{r['similar_to'][:80]}”:\n" + "\n".join(
        f"- #{c['number']} {c['title']} [{c['board']} › {c['list']}] · {c['similarity']:.0%} alike"
        for c in r["cards"])


def _size(n: int) -> str:
    return f"{n / 1048576:.1f} MB" if n >= 1048576 else f"{max(1, round(n / 1024))} KB"


def notifications(r: dict) -> str:
    if not r["items"]:
        return "No notifications. You're all caught up."
    head = f"{r['unread']} unread" if r["unread"] else "All read"
    return head + ":\n" + "\n".join(
        ("• " if not n["read"] else "- ") + f"{n['actor']} {n['summary']} [{n['board']}]"
        + f" (`{n['id']}`)" for n in r["items"])