#!/usr/bin/env python3
"""
Task Capture & Organization Agent — runnable demo (Agent Blueprint from AgentKits)

This runs a REAL agent loop: it sends the kit's actual system prompt and a
realistic sample input to the model, prints the structured decision, and then
executes any proposed actions against MOCK tools (labelled [MOCK]).

The model/reasoning is real. The tools are stubs that return fake data and do
NOT touch any real system. Wiring the mock tools to your own integrations
(CRM, database, GitHub, etc.) is the final implementation step — see README.md.

Works with Anthropic OR OpenAI — it auto-detects whichever API key is set.

© 2026 AgentKits · www.agent-kits.com · Licensed CC BY 4.0 (attribution required)
Original blueprint: https://agent-kits.com/kit/task-capture-organizer

Setup (pick one):
    pip install anthropic   &&  export ANTHROPIC_API_KEY=sk-ant-...
    pip install openai      &&  export OPENAI_API_KEY=sk-...
Then:
    python run.py
"""
import os, json, sys

ANTHROPIC_MODEL = "claude-sonnet-4-6"
OPENAI_MODEL = "gpt-4o"

SYSTEM_PROMPT = """You are a Task Capture & Organization Agent. You turn a person's raw input (brain-dump, notes, messages) into an organized task list and propose structure. You are judged on a faithful, useful organization and on never inventing tasks/deadlines or acting without confirmation.

== CORE PRINCIPLES ==
1. Capture only what's stated. Extract tasks the person actually expressed. Don't manufacture tasks, and don't add work they didn't mention.
2. Don't fabricate details. Assign a due date only if stated; otherwise 'no date'. Mark inferred priorities and projects as inferred, not facts. Keep the task's intent faithful.
3. Propose, don't execute. You organize and suggest. You never schedule, send, complete, or delete anything, and you never modify existing items, without explicit confirmation.

== HARD RULES (NON-NEGOTIABLE) ==
- NO INVENTED TASKS: Only capture tasks actually stated. Don't pad the list with assumed work.
- NO FABRICATED DEADLINES/OWNERS: Due date only if stated; otherwise 'no date'. Don't invent deadlines or assign people.
- INFERRED IS LABELED: Priority and project groupings you infer are marked 'inferred' for the person to confirm.
- NO AUTONOMOUS ACTION: Never auto-schedule to a calendar, send a message, complete, or delete a task without confirmation. Propose; the person approves.
- FLAG AMBIGUITY: Vague items ('the thing for John') are captured as-is and flagged for clarification, not guessed into specifics.

== METHOD ==
- Read the input. Extract stated tasks. Group into inferred projects, suggest inferred priority, capture stated due dates, propose a next action. Flag ambiguous items. Present for confirmation.

== OUTPUT FORMAT (return ONE JSON object) ==
{
  "tasks": [
    { "task": "<as stated>", "project": "<inferred group>", "priority": "<high|med|low (inferred)>", "due": "<date if stated, else 'no date'>", "next_action": "<suggested>", "flag": "ok|needs_clarification" }
  ],
  "needs_clarification": ["<ambiguous items to confirm>"],
  "proposed_actions": ["<e.g. 'add these to your task app on confirmation' — not done yet>"],
  "note": "Captured only stated tasks. Priorities/projects are inferred. Nothing scheduled, sent, or completed without your confirmation."
}
Never invent tasks or deadlines. Label inferences. Don't act without confirmation.
"""

SAMPLE_INPUT = """Input: 'email the Q3 report to Sarah by Thursday, buy milk, look into the budget thing, call dentist.'
"""

# ---- MOCK tools (stubs — replace with real integrations to deploy) ----
def get_input(args=None):
    print(f"  [MOCK] get_input(" + str(args) + ")")
    return {"status": "mock", "tool": "get_input"}

def extract_tasks(args=None):
    print(f"  [MOCK] extract_tasks(" + str(args) + ")")
    return {"status": "mock", "tool": "extract_tasks"}

def infer_project(args=None):
    print(f"  [MOCK] infer_project(" + str(args) + ")")
    return {"status": "mock", "tool": "infer_project"}

def suggest_priority(args=None):
    print(f"  [MOCK] suggest_priority(" + str(args) + ")")
    return {"status": "mock", "tool": "suggest_priority"}

def identify_due_date(args=None):
    print(f"  [MOCK] identify_due_date(" + str(args) + ")")
    return {"status": "mock", "tool": "identify_due_date"}

def flag_ambiguous(args=None):
    print(f"  [MOCK] flag_ambiguous(" + str(args) + ")")
    return {"status": "mock", "tool": "flag_ambiguous"}

def organize_list(args=None):
    print(f"  [MOCK] organize_list(" + str(args) + ")")
    return {"status": "mock", "tool": "organize_list"}

def propose_next_action(args=None):
    print(f"  [MOCK] propose_next_action(" + str(args) + ")")
    return {"status": "mock", "tool": "propose_next_action"}

TOOLS = {
    "get_input": get_input,
    "extract_tasks": extract_tasks,
    "infer_project": infer_project,
    "suggest_priority": suggest_priority,
    "identify_due_date": identify_due_date,
    "flag_ambiguous": flag_ambiguous,
    "organize_list": organize_list,
    "propose_next_action": propose_next_action,
}


def call_model(system, user):
    """Dispatch to whichever provider's key is configured."""
    if os.environ.get("ANTHROPIC_API_KEY"):
        try:
            from anthropic import Anthropic
        except ImportError:
            sys.exit("Run: pip install anthropic")
        msg = Anthropic().messages.create(
            model=ANTHROPIC_MODEL,
            max_tokens=1600,
            system=system,
            messages=[{"role": "user", "content": user}],
        )
        return "".join(b.text for b in msg.content if getattr(b, "type", "") == "text")
    if os.environ.get("OPENAI_API_KEY"):
        try:
            from openai import OpenAI
        except ImportError:
            sys.exit("Run: pip install openai")
        resp = OpenAI().chat.completions.create(
            model=OPENAI_MODEL,
            max_tokens=1600,
            messages=[
                {"role": "system", "content": system},
                {"role": "user", "content": user},
            ],
        )
        return resp.choices[0].message.content
    sys.exit("Set ANTHROPIC_API_KEY or OPENAI_API_KEY first.")


def extract_json(text):
    a, b = text.find("{"), text.rfind("}")
    if a == -1 or b == -1:
        return None
    try:
        return json.loads(text[a : b + 1])
    except json.JSONDecodeError:
        return None


def main():
    print("=" * 70)
    print("INPUT:\n" + SAMPLE_INPUT)
    print("=" * 70)

    text = call_model(SYSTEM_PROMPT, SAMPLE_INPUT)
    print("AGENT DECISION:\n" + text)
    print("=" * 70)

    data = extract_json(text)
    if not data:
        print("(No JSON object parsed — the model returned prose.)")
        return

    actions = data.get("actions") or []
    if actions:
        print("EXECUTING PROPOSED ACTIONS (mock):")
        for a in actions:
            name = a.get("tool")
            args = a.get("args", {})
            if name in TOOLS:
                TOOLS[name](args)
            else:
                print(f"  [skip] no mock tool for '{name}'")
    else:
        print("This blueprint returned a structured result (no side-effecting "
              "actions in this sample). Available mock tools: "
              + ", ".join(TOOLS.keys()))


if __name__ == "__main__":
    main()
