humanbottasks 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.
alarm.py ADDED
@@ -0,0 +1,62 @@
1
+ """Task-Eingangs-Alarm: gibt NEUE offene Tasks aus; leere Ausgabe = nichts senden.
2
+
3
+ Wird als no_agent-Cron-Script genutzt: stdout wird verbatim an Telegram geliefert.
4
+ """
5
+ import os
6
+ import sqlite3
7
+
8
+ DB = "C:/Users/marku/humanbottasks/tasks.db"
9
+ STATE = "C:/Users/marku/humanbottasks/data/reported_ids.txt"
10
+
11
+
12
+ def _load():
13
+ if not os.path.exists(STATE):
14
+ return set()
15
+ with open(STATE, encoding="utf-8") as f:
16
+ return {ln.strip() for ln in f if ln.strip()}
17
+
18
+
19
+ def _save(ids):
20
+ os.makedirs(os.path.dirname(STATE), exist_ok=True)
21
+ with open(STATE, "w", encoding="utf-8") as f:
22
+ for i in sorted(ids):
23
+ f.write(i + "\n")
24
+
25
+
26
+ def main():
27
+ if not os.path.exists(DB):
28
+ return
29
+ conn = sqlite3.connect(DB)
30
+ conn.row_factory = sqlite3.Row
31
+ try:
32
+ rows = conn.execute(
33
+ "SELECT * FROM tasks WHERE status='pending' ORDER BY created_at"
34
+ ).fetchall()
35
+ except sqlite3.OperationalError:
36
+ return
37
+ finally:
38
+ conn.close()
39
+
40
+ if not rows:
41
+ return
42
+
43
+ reported = _load()
44
+ new = [r for r in rows if r["id"] not in reported]
45
+
46
+ # Zustand synchron halten (auch bereits bekannte pending merken)
47
+ _save([r["id"] for r in rows])
48
+
49
+ if not new:
50
+ return
51
+
52
+ lines = ["\U0001f195 Neue humanbottasks-Task(s):"]
53
+ for r in new:
54
+ lines.append(f" {r['id']} [{r['task_type']}] ${r['max_price_usd']:.2f}")
55
+ lines.append(f" {r['description']}")
56
+ lines.append("")
57
+ lines.append('Erledigen (im Ordner humanbottasks): python fulfill.py done <task_id> "<Ergebnis>"')
58
+ print("\n".join(lines))
59
+
60
+
61
+ if __name__ == "__main__":
62
+ main()
fulfill.py ADDED
@@ -0,0 +1,17 @@
1
+ """CLI fuer den Menschen: offene Tasks ansehen und erledigen (MVP-Fulfillment)."""
2
+ import sys
3
+ import store as q
4
+
5
+ q.init()
6
+
7
+ if len(sys.argv) >= 4 and sys.argv[1] == "done":
8
+ q.complete(sys.argv[2], " ".join(sys.argv[3:]))
9
+ print("Erledigt:", sys.argv[2])
10
+
11
+ rows = q.pending()
12
+ if not rows:
13
+ print("Keine offenen Tasks.")
14
+ else:
15
+ for r in rows:
16
+ print(f"{r['id']} [{r['task_type']}] ${r['max_price_usd']:.2f} {r['description'][:90]}")
17
+ print("\nErledigen: python fulfill.py done <task_id> <Ergebnis-Text>")
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.5
2
+ Name: humanbottasks
3
+ Version: 0.1.0
4
+ Summary: Human fallback for AI agents: delegate tasks a model can't do to a human, over MCP.
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: mcp<2
8
+ Description-Content-Type: text/markdown
9
+
10
+ # humanbottasks
11
+
12
+ Gemanagter Human-Service-Layer fuer AI-Agenten. Ein AI-Agent delegiert ueber MCP
13
+ eine Aufgabe, die er nicht selbst kann, an einen Menschen; der Mensch erledigt
14
+ sie, der Agent ruft das Ergebnis ab.
15
+
16
+ ## MVP-Status
17
+ - `server.py` MCP-Server (Tools: request_human_task, get_task_status)
18
+ - `store.py` SQLite-Queue (tasks.db)
19
+ - `fulfill.py` CLI fuer den Menschen (Tasks ansehen/erledigen)
20
+ - `demo.py` End-to-End-Demo (Agent -> Task -> Mensch -> Ergebnis)
21
+ - `test_smoke.py` Smoke-Test
22
+
23
+ ## Setup & Start
24
+ cd humanbottasks
25
+ unset PYTHONPATH
26
+ uv venv
27
+ uv pip install "mcp<2"
28
+ uv run server.py
29
+
30
+ ## Transporte
31
+ uv run server.py # stdio (Claude Desktop, Cursor, lokale Frameworks)
32
+ uv run server.py --transport streamable-http # HTTP-Endpoint http://127.0.0.1:8000/mcp (lokal/Remote-Agents)
33
+ uv run server.py --transport sse # Legacy-SSE http://127.0.0.1:8000/sse
34
+
35
+ Host/Port via Umgebungsvariablen: HBT_HOST (default 127.0.0.1), HBT_PORT (default 8000).
36
+ Fuer Remote-Zugriff: HBT_HOST=0.0.0.0 setzen.
37
+
38
+ ## Install (fuer andere Builder, 1 Zeile)
39
+ uvx --from git+https://github.com/humanbottasks/humanbottasks humanbottasks
40
+
41
+ ## Task-Typen
42
+ verification · physical_action · subjective_judgment · ground_truth_labeling · prove_human
43
+
44
+ ## MCP-Clients (installiert + konfiguriert)
45
+ - **Claude Desktop**: `C:\Users\marku\AppData\Roaming\Claude\claude_desktop_config.json`
46
+ - **Cursor**: `C:\Users\marku\.cursor\mcp.json`
47
+ - **Windsurf**: `C:\Users\marku\.codeium\windsurf\mcp_config.json`
48
+ - Templates in `config/` (falls neu einrichten noetig)
49
+
50
+ ## Naechste Schritte (Reihenfolge)
51
+ 1. Nachfrage-Test: Server laeuft, ein echter Agent ruft request_human_task.
52
+ 2. Website/Landing fuer Agent-Builder.
53
+ 3. Payments (Stripe/Circle) erst, wenn Nachfrage da ist.
@@ -0,0 +1,9 @@
1
+ alarm.py,sha256=FT0h1dDwKaXigKt4YFBTlne3OgywM1ZvoK__IF9GUEw,1648
2
+ fulfill.py,sha256=IHwXpEYRFOvWNQrDxt__2xUKg_JBr5aRolpXJPPjEPI,527
3
+ server.py,sha256=W0tLb3d3UGYHu6tQ8KBlQZTAn4oR5JDq3zJJVVYwLfA,2863
4
+ store.py,sha256=UYV8tPNxCNYBmBb8-1cCWe_f174XO2G_KMg4fU7fiCg,1478
5
+ humanbottasks-0.1.0.dist-info/METADATA,sha256=Qu9E725rnZaokyVIFcs4AynbxpPdvfVGAHHZgii_wOs,2095
6
+ humanbottasks-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
7
+ humanbottasks-0.1.0.dist-info/entry_points.txt,sha256=R0YGJ1pHHjyLt6avnCEtpPmeKj4nUICjIPPEprSKyzI,46
8
+ humanbottasks-0.1.0.dist-info/licenses/LICENSE,sha256=qtbVlsMaNUW5OdwWSvBd_1VRCGhlWbUif2dvSB1OB6k,1070
9
+ humanbottasks-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ humanbottasks = server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 humanbottasks
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
server.py ADDED
@@ -0,0 +1,83 @@
1
+ """humanbottasks MCP-Server: AI-Agenten delegieren Tasks an Menschen.
2
+
3
+ Der Server exponiert zwei Tools ueber MCP:
4
+ - request_human_task: Agent stellt eine Aufgabe ein
5
+ - get_task_status: Agent fragt Status/Ergebnis ab
6
+
7
+ Transport (--transport): stdio (default), streamable-http, sse.
8
+
9
+ Ein Mensch erledigt die Tasks ueber fulfill.py (CLI).
10
+ """
11
+ import os
12
+ from mcp.server.fastmcp import FastMCP
13
+ import store as q
14
+
15
+ q.init()
16
+ mcp = FastMCP(
17
+ "humanbottasks",
18
+ host=os.environ.get("HBT_HOST", "127.0.0.1"),
19
+ port=int(os.environ.get("HBT_PORT", "8000")),
20
+ )
21
+
22
+ TASK_TYPES = [
23
+ "verification", # etwas pruefen/verifizieren
24
+ "physical_action", # physische Handlung (kaufen, verschicken, anrufen, unterschreiben)
25
+ "subjective_judgment", # subjektives Urteil (klingt natuerlich? ist gut?)
26
+ "ground_truth_labeling", # Daten labeln / Ground-Truth erzeugen
27
+ "prove_human", # CAPTCHA / Identitaets-/Konto-Verifikation
28
+ ]
29
+
30
+
31
+ @mcp.tool()
32
+ def request_human_task(task_type: str, description: str, max_price_usd: float = 10.0) -> str:
33
+ """Delegiere eine Aufgabe an einen menschlichen Arbeiter.
34
+
35
+ Nutze das fuer alles, was du als KI nicht selbst erledigen kannst:
36
+ physische Aktionen, Verifikation, subjektive Urteile oder Ground-Truth-Labeling.
37
+
38
+ Args:
39
+ task_type: Eine von 'verification', 'physical_action', 'subjective_judgment',
40
+ 'ground_truth_labeling', 'prove_human'.
41
+ description: Klare, konkrete Beschreibung der Aufgabe.
42
+ max_price_usd: Maximalpreis in USD, den du bereit bist zu zahlen.
43
+
44
+ Returns:
45
+ Eine task_id, mit der du den Status abfragen kannst.
46
+ """
47
+ if task_type not in TASK_TYPES:
48
+ return f"Fehler: task_type muss eine von {TASK_TYPES} sein, nicht '{task_type}'."
49
+ if not description.strip():
50
+ return "Fehler: description darf nicht leer sein."
51
+ tid = q.add_task(task_type, description.strip(), max_price_usd)
52
+ return f"Task angenommen. task_id={tid}. Status: get_task_status('{tid}')."
53
+
54
+
55
+ @mcp.tool()
56
+ def get_task_status(task_id: str) -> str:
57
+ """Status und Ergebnis einer delegierten Task abfragen.
58
+
59
+ Args:
60
+ task_id: Die task_id aus request_human_task.
61
+
62
+ Returns:
63
+ 'pending'/'done' plus Ergebnis, wenn fertig.
64
+ """
65
+ t = q.get_task(task_id)
66
+ if not t:
67
+ return f"Unbekannte task_id: {task_id}"
68
+ if t["status"] == "done":
69
+ return f"Fertig. Ergebnis: {t['result']}"
70
+ return f"Status: {t['status']} (Typ: {t['task_type']}, max ${t['max_price_usd']:.2f})"
71
+
72
+
73
+ def main():
74
+ import argparse
75
+ p = argparse.ArgumentParser(description="humanbottasks MCP-Server")
76
+ p.add_argument("--transport", default="stdio",
77
+ choices=["stdio", "sse", "streamable-http"])
78
+ a = p.parse_args()
79
+ mcp.run(transport=a.transport)
80
+
81
+
82
+ if __name__ == "__main__":
83
+ main()
store.py ADDED
@@ -0,0 +1,52 @@
1
+ """SQLite-Task-Queue fuer humanbottasks (minimales MVP)."""
2
+ import sqlite3, uuid, time, os
3
+
4
+ DB = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tasks.db")
5
+
6
+
7
+ def _conn():
8
+ c = sqlite3.connect(DB)
9
+ c.row_factory = sqlite3.Row
10
+ return c
11
+
12
+
13
+ def init():
14
+ with _conn() as c:
15
+ c.execute("""CREATE TABLE IF NOT EXISTS tasks (
16
+ id TEXT PRIMARY KEY,
17
+ task_type TEXT NOT NULL,
18
+ description TEXT NOT NULL,
19
+ max_price_usd REAL NOT NULL,
20
+ status TEXT NOT NULL DEFAULT 'pending',
21
+ result TEXT,
22
+ created_at REAL NOT NULL,
23
+ completed_at REAL
24
+ )""")
25
+
26
+
27
+ def add_task(task_type, description, max_price_usd):
28
+ tid = uuid.uuid4().hex[:12]
29
+ with _conn() as c:
30
+ c.execute(
31
+ "INSERT INTO tasks (id, task_type, description, max_price_usd, status, created_at) VALUES (?,?,?,?,?,?)",
32
+ (tid, task_type, description, max_price_usd, "pending", time.time()),
33
+ )
34
+ return tid
35
+
36
+
37
+ def get_task(tid):
38
+ with _conn() as c:
39
+ return c.execute("SELECT * FROM tasks WHERE id=?", (tid,)).fetchone()
40
+
41
+
42
+ def pending():
43
+ with _conn() as c:
44
+ return c.execute("SELECT * FROM tasks WHERE status='pending' ORDER BY created_at").fetchall()
45
+
46
+
47
+ def complete(tid, result):
48
+ with _conn() as c:
49
+ c.execute(
50
+ "UPDATE tasks SET status='done', result=?, completed_at=? WHERE id=?",
51
+ (result, time.time(), tid),
52
+ )