taskboy 0.1.1__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.
Files changed (95) hide show
  1. taskboy/__init__.py +0 -0
  2. taskboy/adapters/__init__.py +0 -0
  3. taskboy/adapters/_util.py +30 -0
  4. taskboy/adapters/aws_read.py +107 -0
  5. taskboy/adapters/confluence.py +88 -0
  6. taskboy/adapters/github_api.py +564 -0
  7. taskboy/adapters/issues.py +435 -0
  8. taskboy/adapters/jira.py +263 -0
  9. taskboy/adapters/sentry.py +82 -0
  10. taskboy/adapters/slack_history.py +204 -0
  11. taskboy/assets.py +27 -0
  12. taskboy/audit.py +78 -0
  13. taskboy/broker.py +266 -0
  14. taskboy/classifier.py +219 -0
  15. taskboy/cli.py +161 -0
  16. taskboy/config.py +539 -0
  17. taskboy/dashboard/__init__.py +5 -0
  18. taskboy/dashboard/api.py +963 -0
  19. taskboy/dashboard/app.py +78 -0
  20. taskboy/dashboard/auth.py +117 -0
  21. taskboy/dashboard/editors.py +140 -0
  22. taskboy/dashboard/gitops.py +60 -0
  23. taskboy/dashboard/render.py +48 -0
  24. taskboy/debug_feed.py +170 -0
  25. taskboy/deploy/env.example +17 -0
  26. taskboy/deploy/git-cred-helper.py +36 -0
  27. taskboy/deploy/install.sh +101 -0
  28. taskboy/deploy/remote-update.sh +52 -0
  29. taskboy/deploy/taskboy-restart.path +10 -0
  30. taskboy/deploy/taskboy-restart.service +8 -0
  31. taskboy/deploy/taskboy.service +22 -0
  32. taskboy/hooks.py +208 -0
  33. taskboy/issue_runs.py +203 -0
  34. taskboy/llm.py +125 -0
  35. taskboy/main.py +269 -0
  36. taskboy/memory.py +59 -0
  37. taskboy/models.py +103 -0
  38. taskboy/mrkdwn.py +19 -0
  39. taskboy/notify.py +41 -0
  40. taskboy/orchestrator.py +401 -0
  41. taskboy/personality.py +17 -0
  42. taskboy/prompts.py +288 -0
  43. taskboy/quick.py +234 -0
  44. taskboy/redact.py +49 -0
  45. taskboy/repocache.py +117 -0
  46. taskboy/review_requests.py +388 -0
  47. taskboy/router.py +146 -0
  48. taskboy/runner.py +581 -0
  49. taskboy/scheduler.py +301 -0
  50. taskboy/secrets.py +83 -0
  51. taskboy/settings.py +19 -0
  52. taskboy/setup_checks.py +184 -0
  53. taskboy/setup_wizard.py +699 -0
  54. taskboy/skills.py +95 -0
  55. taskboy/slack.py +666 -0
  56. taskboy/slack_users.py +32 -0
  57. taskboy/started_messages.py +33 -0
  58. taskboy/store.py +1393 -0
  59. taskboy/task_actions.py +86 -0
  60. taskboy/templates/config.example.yaml +149 -0
  61. taskboy/templates/conventions.md +60 -0
  62. taskboy/templates/help.example.md +29 -0
  63. taskboy/templates/personality_agent.example.md +5 -0
  64. taskboy/templates/personality_reviewer.example.md +5 -0
  65. taskboy/templates/services/aws.yaml +7 -0
  66. taskboy/templates/services/confluence.yaml +4 -0
  67. taskboy/templates/services/github.yaml +14 -0
  68. taskboy/templates/services/jira.yaml +6 -0
  69. taskboy/templates/services/sentry.yaml +4 -0
  70. taskboy/templates/services/slack.yaml +9 -0
  71. taskboy/templates/skills/README.md +33 -0
  72. taskboy/templates/skills/discoverissues/SKILL.md +46 -0
  73. taskboy/templates/skills/implementapprovedissues/SKILL.md +41 -0
  74. taskboy/templates/skills/jira2pr/SKILL.md +66 -0
  75. taskboy/templates/skills/monitor/SKILL.md +32 -0
  76. taskboy/templates/skills/monitornew/SKILL.md +27 -0
  77. taskboy/templates/skills/refineissue/SKILL.md +35 -0
  78. taskboy/templates/skills/release/SKILL.md +36 -0
  79. taskboy/templates/skills/review/SKILL.md +89 -0
  80. taskboy/templates/skills/reviewandmonitor/SKILL.md +22 -0
  81. taskboy/templates/skills/reviews/SKILL.md +26 -0
  82. taskboy/templates/skills/slack2jira/SKILL.md +47 -0
  83. taskboy/templates/skills/slack2pr/SKILL.md +64 -0
  84. taskboy/templates/skills/spec2pr/SKILL.md +35 -0
  85. taskboy/templates/slack_app_manifest.yaml +48 -0
  86. taskboy/templates/task_started_messages.yaml +14 -0
  87. taskboy/ui_dist/assets/index-DmYxR9Qy.css +1 -0
  88. taskboy/ui_dist/assets/index-LDDO4iT2.js +15 -0
  89. taskboy/ui_dist/index.html +15 -0
  90. taskboy/workspace.py +86 -0
  91. taskboy-0.1.1.dist-info/METADATA +120 -0
  92. taskboy-0.1.1.dist-info/RECORD +95 -0
  93. taskboy-0.1.1.dist-info/WHEEL +5 -0
  94. taskboy-0.1.1.dist-info/entry_points.txt +2 -0
  95. taskboy-0.1.1.dist-info/top_level.txt +1 -0
taskboy/cli.py ADDED
@@ -0,0 +1,161 @@
1
+ """operator cli: inject tasks without slack (testing), inspect, cancel, pause intake."""
2
+
3
+ import argparse
4
+ import asyncio
5
+ import time
6
+
7
+ from taskboy import settings
8
+ from taskboy.config import load_config
9
+ from taskboy.models import CANCELLED, TERMINAL_STATES, utcnow
10
+ from taskboy.notify import StdoutNotifier
11
+ from taskboy.orchestrator import accept_task
12
+ from taskboy.store import Store, TransitionRaced
13
+
14
+
15
+ def main() -> None:
16
+ parser = argparse.ArgumentParser(prog="taskboy")
17
+ sub = parser.add_subparsers(dest="command", required=True)
18
+
19
+ inject = sub.add_parser("inject", help="create a task without slack, through the identical intake path")
20
+ inject.add_argument("text")
21
+ inject.add_argument("--user", default="cli")
22
+ inject.add_argument("--channel", default="cli")
23
+ inject.add_argument("--model", default=None, help="model alias override (recorded and audited, MOD-008)")
24
+ inject.add_argument("--watch", action="store_true", help="poll until the task reaches a terminal state")
25
+
26
+ sub.add_parser("list", help="show recent tasks")
27
+
28
+ cancel = sub.add_parser("cancel", help="cancel a task")
29
+ cancel.add_argument("task_id")
30
+
31
+ permissions = sub.add_parser("permissions", help="list a task's permission requests")
32
+ permissions.add_argument("task_id")
33
+
34
+ for verb in ("grant", "deny"):
35
+ decide = sub.add_parser(verb, help=f"{verb} a sub-agent's permission request (granting a blocked task resumes it)")
36
+ decide.add_argument("task_id")
37
+ decide.add_argument("kind", choices=["tool", "repo"])
38
+ decide.add_argument("target", help="the tool name or owner/name repository")
39
+
40
+ sub.add_parser("pause-intake", help="refuse new tasks; running tasks continue (REL-009)")
41
+ sub.add_parser("resume-intake", help="accept new tasks again")
42
+ sub.add_parser("run", help="run the service in the foreground")
43
+
44
+ extract = sub.add_parser("assets", help="copy a packaged asset tree out of the installed package")
45
+ extract.add_argument("name", choices=["templates", "deploy"], help="templates = config example, per-service configs, personalities, skill templates, slack manifest; deploy = systemd units, env example, git credential helper")
46
+ extract.add_argument("dest", nargs="?", default=".", help="directory to copy into (default: current directory)")
47
+
48
+ from taskboy import setup_wizard
49
+
50
+ setup_wizard.add_parser(sub)
51
+
52
+ args = parser.parse_args()
53
+ if args.command == "run":
54
+ from taskboy.main import main as run_main
55
+
56
+ run_main()
57
+ return
58
+ if args.command == "setup":
59
+ raise SystemExit(setup_wizard.run(args))
60
+ if args.command == "assets":
61
+ from taskboy import assets
62
+
63
+ print(f"copied packaged {args.name} to {assets.extract(args.name, args.dest)}")
64
+ return
65
+
66
+ store = Store(settings.DB_PATH)
67
+ try:
68
+ if args.command == "inject":
69
+ asyncio.run(_inject(store, args))
70
+ elif args.command == "list":
71
+ _list(store)
72
+ elif args.command == "cancel":
73
+ _cancel(store, args.task_id)
74
+ elif args.command == "permissions":
75
+ _permissions(store, args.task_id)
76
+ elif args.command in ("grant", "deny"):
77
+ asyncio.run(_decide_permission(store, args.command, args.task_id, args.kind, args.target))
78
+ elif args.command == "pause-intake":
79
+ store.meta_set("intake_paused", "1")
80
+ print("intake paused")
81
+ elif args.command == "resume-intake":
82
+ store.meta_set("intake_paused", "0")
83
+ print("intake resumed")
84
+ finally:
85
+ store.close()
86
+
87
+
88
+ async def _inject(store: Store, args) -> None:
89
+ config = load_config(settings.CONFIG_PATH)
90
+ message_ts = str(time.time_ns()) # unique per injection, so every inject is a new task
91
+ task, status = await accept_task(
92
+ store,
93
+ config,
94
+ StdoutNotifier(),
95
+ team_id="cli",
96
+ channel_id=args.channel,
97
+ thread_ts=message_ts,
98
+ message_ts=message_ts,
99
+ user_id=args.user,
100
+ text=args.text,
101
+ model_override=args.model,
102
+ )
103
+ print(f"status: {status}" + (f", task: {task.task_id}" if task else ""))
104
+ if not args.watch or task is None:
105
+ return
106
+ last_state = task.state
107
+ while last_state not in TERMINAL_STATES:
108
+ await asyncio.sleep(0.5)
109
+ current = store.get_task(task.task_id)
110
+ assert current is not None
111
+ if current.state != last_state:
112
+ last_state = current.state
113
+ print(f"[{task.task_id}] -> {last_state}")
114
+ final = store.get_task(task.task_id)
115
+ assert final is not None
116
+ print(final.result_summary or final.error or final.blocked_reason or "")
117
+
118
+
119
+ def _list(store: Store) -> None:
120
+ for task in store.recent_tasks(limit=20):
121
+ print(f"{task.task_id} {task.state:9} {task.created_at} {task.request_text[:70]}")
122
+
123
+
124
+ def _permissions(store: Store, task_id: str) -> None:
125
+ if store.get_task(task_id) is None:
126
+ print(f"no such task: {task_id}")
127
+ return
128
+ rows = store.permission_requests_for(task_id)
129
+ if not rows:
130
+ print("no permission requests")
131
+ return
132
+ for row in rows:
133
+ decided = f" by {row['decided_by']}" if row["decided_by"] else ""
134
+ print(f"{row['status']:8} {row['kind']:4} {row['target']}{decided} ({row['reason'][:80]})")
135
+
136
+
137
+ async def _decide_permission(store: Store, decision_verb: str, task_id: str, kind: str, target: str) -> None:
138
+ from taskboy.task_actions import decide_permission
139
+
140
+ decision = "granted" if decision_verb == "grant" else "denied"
141
+ task, status = await decide_permission(store, StdoutNotifier(), task_id, kind, target, decision, "cli")
142
+ print(f"{task_id}: {status}" + (f" (now {task.state})" if task else ""))
143
+
144
+
145
+ def _cancel(store: Store, task_id: str) -> None:
146
+ task = store.get_task(task_id)
147
+ if task is None:
148
+ print(f"no such task: {task_id}")
149
+ return
150
+ if task.state in TERMINAL_STATES:
151
+ print(f"{task_id} is already {task.state}")
152
+ return
153
+ try:
154
+ store.transition(task_id, task.state, CANCELLED, "cancelled via cli", finished_at=utcnow())
155
+ print(f"{task_id} cancelled" + (" (the running session will stop shortly)" if task.state == "running" else ""))
156
+ except TransitionRaced:
157
+ print(f"{task_id} changed state while cancelling — check `taskboy list` and retry")
158
+
159
+
160
+ if __name__ == "__main__":
161
+ main()