kanbot 0.1.0__tar.gz
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.
- kanbot-0.1.0/.gitignore +12 -0
- kanbot-0.1.0/PKG-INFO +145 -0
- kanbot-0.1.0/README.md +129 -0
- kanbot-0.1.0/kanbot/__init__.py +3 -0
- kanbot-0.1.0/kanbot/__main__.py +6 -0
- kanbot-0.1.0/kanbot/agents.py +134 -0
- kanbot-0.1.0/kanbot/cli.py +260 -0
- kanbot-0.1.0/kanbot/config.py +77 -0
- kanbot-0.1.0/kanbot/runner/__init__.py +1 -0
- kanbot-0.1.0/kanbot/runner/agents.py +141 -0
- kanbot-0.1.0/kanbot/runner/discovery.py +327 -0
- kanbot-0.1.0/kanbot/runner/worker.py +173 -0
- kanbot-0.1.0/kanbot/server/__init__.py +1 -0
- kanbot-0.1.0/kanbot/server/app.py +329 -0
- kanbot-0.1.0/kanbot/server/db.py +421 -0
- kanbot-0.1.0/kanbot/server/hub.py +243 -0
- kanbot-0.1.0/kanbot/server/insights.py +129 -0
- kanbot-0.1.0/kanbot/server/schemas.py +54 -0
- kanbot-0.1.0/kanbot/server/static/app.js +1298 -0
- kanbot-0.1.0/kanbot/server/static/index.html +53 -0
- kanbot-0.1.0/kanbot/server/static/styles.css +430 -0
- kanbot-0.1.0/pyproject.toml +32 -0
- kanbot-0.1.0/scripts/e2e.py +83 -0
- kanbot-0.1.0/scripts/e2e_live.py +111 -0
- kanbot-0.1.0/scripts/e2e_revive.py +80 -0
- kanbot-0.1.0/vercel.json +14 -0
kanbot-0.1.0/.gitignore
ADDED
kanbot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kanbot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Kanban board where every card is a task run by your local CLI coding agents (Claude, Codex, Gemini, GLM/ZAI, or any CLI you define).
|
|
5
|
+
Project-URL: Homepage, https://github.com/yourname/deckhand
|
|
6
|
+
Author: Deckhand
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: agents,automation,claude,cli,codex,kanban
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Requires-Dist: fastapi>=0.110
|
|
11
|
+
Requires-Dist: httpx>=0.27
|
|
12
|
+
Requires-Dist: rich>=13.0
|
|
13
|
+
Requires-Dist: uvicorn[standard]>=0.27
|
|
14
|
+
Requires-Dist: websockets>=12.0
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# KanBot
|
|
18
|
+
|
|
19
|
+
**A visual control room for your coding-agent TUIs — and a Kanban board where every card is a task run by them.**
|
|
20
|
+
|
|
21
|
+
You run a lot of terminal coding agents (Claude Code, Codex, …). KanBot gives you
|
|
22
|
+
one screen to *see what every session is doing*, pick any of them back up, and
|
|
23
|
+
drop new tasks that agents run for you — with live logs streamed straight to the
|
|
24
|
+
card.
|
|
25
|
+
|
|
26
|
+
Two things in one board:
|
|
27
|
+
|
|
28
|
+
1. **Track your TUIs.** A background runner watches each agent's local session
|
|
29
|
+
store and surfaces every session as a card: the project, the latest message,
|
|
30
|
+
how many turns, how long it's been brewing, and whether it's **working right
|
|
31
|
+
now**. Sessions flow by recency — working → **Running**, just-finished →
|
|
32
|
+
**Done**, older → **Backlog**.
|
|
33
|
+
2. **Run new tasks.** Drop a card, pick an agent, and the runner executes it and
|
|
34
|
+
streams stdout/stderr to the card. Or drag any tracked session into **Running**
|
|
35
|
+
to resume it (`claude --resume`, `codex exec resume`).
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Backlog Running Review Done
|
|
39
|
+
(stale sessions (sessions (your (recently
|
|
40
|
+
+ new tasks) working now finished finished
|
|
41
|
+
+ running tasks) tasks) sessions)
|
|
42
|
+
│ drag → Running, or "Run", queues for a runner
|
|
43
|
+
▼
|
|
44
|
+
╔══════════════════════════════╗
|
|
45
|
+
║ kanbot runner (background) ║ detects claude · codex · gemini · glm · shell
|
|
46
|
+
║ watches ~/.claude, ~/.codex ║ executes & resumes, streams logs back
|
|
47
|
+
╚══════════════════════════════╝
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quickstart
|
|
51
|
+
|
|
52
|
+
Easiest (isolated, sidesteps Homebrew's PEP 668 `externally-managed` error):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pipx install kanbot && kanbot up # or zero-install: uvx kanbot up
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
From source (until it's on PyPI):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
python3 -m venv .venv && . .venv/bin/activate
|
|
62
|
+
pip install -e .
|
|
63
|
+
kanbot up # server + local runner, board at :8787
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> Don't use bare `pip install` on macOS Homebrew Python — it errors with
|
|
67
|
+
> `externally-managed-environment` (PEP 668). `pipx`/`uv` handle the env for you.
|
|
68
|
+
|
|
69
|
+
The board immediately fills with your recent Claude/Codex sessions. Click any one
|
|
70
|
+
to see its recent transcript in a terminal view and **resume** it; or hit
|
|
71
|
+
**+ add task** to give an agent fresh work.
|
|
72
|
+
|
|
73
|
+
Run the pieces separately (e.g. runner on another machine):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
kanbot server # the board / API
|
|
77
|
+
kanbot runner --server http://HOST:8787 --name gpu-box
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Tracking other agents (Hermes, OpenCode, your own…)
|
|
81
|
+
|
|
82
|
+
Claude Code and Codex are tracked out of the box. Any agent that logs
|
|
83
|
+
newline-delimited JSON transcripts can be added with **no code change** — point
|
|
84
|
+
KanBot at its store in `~/.kanbot/config.json` (a.k.a. `~/.kanbot/config.json`):
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"discovery_sources": [
|
|
89
|
+
{
|
|
90
|
+
"name": "hermes",
|
|
91
|
+
"label": "Hermes",
|
|
92
|
+
"root": "~/.hermes/sessions",
|
|
93
|
+
"pattern": "*.jsonl",
|
|
94
|
+
"recursive": true,
|
|
95
|
+
"fmt": "claude"
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- `fmt`: `"claude"` for flat records (`{type, message, cwd, timestamp}`) or
|
|
102
|
+
`"codex"` for payload-nested records (`{payload: {role, content, cwd}}`).
|
|
103
|
+
- `kanbot agents` shows which trackers are active and where they read from.
|
|
104
|
+
|
|
105
|
+
## Run agents
|
|
106
|
+
|
|
107
|
+
`kanbot agents` lists the CLIs detected on this machine. Built-in catalog:
|
|
108
|
+
|
|
109
|
+
| agent | run | resume |
|
|
110
|
+
|-------|-----|--------|
|
|
111
|
+
| `claude` | `claude -p "<prompt>"` | `claude --resume <id> -p "<prompt>"` |
|
|
112
|
+
| `codex` | `codex exec --full-auto "<prompt>"` | `codex exec resume <id> "<prompt>"` |
|
|
113
|
+
| `gemini` | `gemini -y -p "<prompt>"` | — |
|
|
114
|
+
| `glm` | Claude Code w/ `ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic` | ✓ |
|
|
115
|
+
| `opencode`, `aider`, `cursor-agent`, `hermes`, `shell` | see `kanbot/agents.py` | — |
|
|
116
|
+
|
|
117
|
+
Override or add any agent's command in `~/.kanbot/config.json` →
|
|
118
|
+
`agent_overrides`. A card set to `auto` runs on whatever the matched runner has.
|
|
119
|
+
|
|
120
|
+
> Note: built-in run commands use auto-approve flags so tasks run unattended.
|
|
121
|
+
> Review `kanbot/agents.py` and dial them back if you want a human in the loop.
|
|
122
|
+
|
|
123
|
+
## Tags & insights
|
|
124
|
+
|
|
125
|
+
Tags are colored labels; a tag can also be an **insight provider** (◆) that pulls
|
|
126
|
+
live context onto any card: **git** (branch/diff), **files** (recent changes), or
|
|
127
|
+
a **custom command** (e.g. `pytest -q`).
|
|
128
|
+
|
|
129
|
+
## CLI
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
kanbot up server + local runner (best first run)
|
|
133
|
+
kanbot server board / API only
|
|
134
|
+
kanbot runner background runner only (--server, --name, --concurrency)
|
|
135
|
+
kanbot agents detected agents + active session trackers
|
|
136
|
+
kanbot config server URL, token, runner name, enable/disable agents
|
|
137
|
+
kanbot open open the board
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Config: `~/.kanbot/config.json` · data: `~/.kanbot/kanbot.db`.
|
|
141
|
+
Set `KANBOT_TOKEN` on the server to require a matching `--token` from runners.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
kanbot-0.1.0/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# KanBot
|
|
2
|
+
|
|
3
|
+
**A visual control room for your coding-agent TUIs — and a Kanban board where every card is a task run by them.**
|
|
4
|
+
|
|
5
|
+
You run a lot of terminal coding agents (Claude Code, Codex, …). KanBot gives you
|
|
6
|
+
one screen to *see what every session is doing*, pick any of them back up, and
|
|
7
|
+
drop new tasks that agents run for you — with live logs streamed straight to the
|
|
8
|
+
card.
|
|
9
|
+
|
|
10
|
+
Two things in one board:
|
|
11
|
+
|
|
12
|
+
1. **Track your TUIs.** A background runner watches each agent's local session
|
|
13
|
+
store and surfaces every session as a card: the project, the latest message,
|
|
14
|
+
how many turns, how long it's been brewing, and whether it's **working right
|
|
15
|
+
now**. Sessions flow by recency — working → **Running**, just-finished →
|
|
16
|
+
**Done**, older → **Backlog**.
|
|
17
|
+
2. **Run new tasks.** Drop a card, pick an agent, and the runner executes it and
|
|
18
|
+
streams stdout/stderr to the card. Or drag any tracked session into **Running**
|
|
19
|
+
to resume it (`claude --resume`, `codex exec resume`).
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Backlog Running Review Done
|
|
23
|
+
(stale sessions (sessions (your (recently
|
|
24
|
+
+ new tasks) working now finished finished
|
|
25
|
+
+ running tasks) tasks) sessions)
|
|
26
|
+
│ drag → Running, or "Run", queues for a runner
|
|
27
|
+
▼
|
|
28
|
+
╔══════════════════════════════╗
|
|
29
|
+
║ kanbot runner (background) ║ detects claude · codex · gemini · glm · shell
|
|
30
|
+
║ watches ~/.claude, ~/.codex ║ executes & resumes, streams logs back
|
|
31
|
+
╚══════════════════════════════╝
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quickstart
|
|
35
|
+
|
|
36
|
+
Easiest (isolated, sidesteps Homebrew's PEP 668 `externally-managed` error):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pipx install kanbot && kanbot up # or zero-install: uvx kanbot up
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
From source (until it's on PyPI):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python3 -m venv .venv && . .venv/bin/activate
|
|
46
|
+
pip install -e .
|
|
47
|
+
kanbot up # server + local runner, board at :8787
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> Don't use bare `pip install` on macOS Homebrew Python — it errors with
|
|
51
|
+
> `externally-managed-environment` (PEP 668). `pipx`/`uv` handle the env for you.
|
|
52
|
+
|
|
53
|
+
The board immediately fills with your recent Claude/Codex sessions. Click any one
|
|
54
|
+
to see its recent transcript in a terminal view and **resume** it; or hit
|
|
55
|
+
**+ add task** to give an agent fresh work.
|
|
56
|
+
|
|
57
|
+
Run the pieces separately (e.g. runner on another machine):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
kanbot server # the board / API
|
|
61
|
+
kanbot runner --server http://HOST:8787 --name gpu-box
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Tracking other agents (Hermes, OpenCode, your own…)
|
|
65
|
+
|
|
66
|
+
Claude Code and Codex are tracked out of the box. Any agent that logs
|
|
67
|
+
newline-delimited JSON transcripts can be added with **no code change** — point
|
|
68
|
+
KanBot at its store in `~/.kanbot/config.json` (a.k.a. `~/.kanbot/config.json`):
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"discovery_sources": [
|
|
73
|
+
{
|
|
74
|
+
"name": "hermes",
|
|
75
|
+
"label": "Hermes",
|
|
76
|
+
"root": "~/.hermes/sessions",
|
|
77
|
+
"pattern": "*.jsonl",
|
|
78
|
+
"recursive": true,
|
|
79
|
+
"fmt": "claude"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `fmt`: `"claude"` for flat records (`{type, message, cwd, timestamp}`) or
|
|
86
|
+
`"codex"` for payload-nested records (`{payload: {role, content, cwd}}`).
|
|
87
|
+
- `kanbot agents` shows which trackers are active and where they read from.
|
|
88
|
+
|
|
89
|
+
## Run agents
|
|
90
|
+
|
|
91
|
+
`kanbot agents` lists the CLIs detected on this machine. Built-in catalog:
|
|
92
|
+
|
|
93
|
+
| agent | run | resume |
|
|
94
|
+
|-------|-----|--------|
|
|
95
|
+
| `claude` | `claude -p "<prompt>"` | `claude --resume <id> -p "<prompt>"` |
|
|
96
|
+
| `codex` | `codex exec --full-auto "<prompt>"` | `codex exec resume <id> "<prompt>"` |
|
|
97
|
+
| `gemini` | `gemini -y -p "<prompt>"` | — |
|
|
98
|
+
| `glm` | Claude Code w/ `ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic` | ✓ |
|
|
99
|
+
| `opencode`, `aider`, `cursor-agent`, `hermes`, `shell` | see `kanbot/agents.py` | — |
|
|
100
|
+
|
|
101
|
+
Override or add any agent's command in `~/.kanbot/config.json` →
|
|
102
|
+
`agent_overrides`. A card set to `auto` runs on whatever the matched runner has.
|
|
103
|
+
|
|
104
|
+
> Note: built-in run commands use auto-approve flags so tasks run unattended.
|
|
105
|
+
> Review `kanbot/agents.py` and dial them back if you want a human in the loop.
|
|
106
|
+
|
|
107
|
+
## Tags & insights
|
|
108
|
+
|
|
109
|
+
Tags are colored labels; a tag can also be an **insight provider** (◆) that pulls
|
|
110
|
+
live context onto any card: **git** (branch/diff), **files** (recent changes), or
|
|
111
|
+
a **custom command** (e.g. `pytest -q`).
|
|
112
|
+
|
|
113
|
+
## CLI
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
kanbot up server + local runner (best first run)
|
|
117
|
+
kanbot server board / API only
|
|
118
|
+
kanbot runner background runner only (--server, --name, --concurrency)
|
|
119
|
+
kanbot agents detected agents + active session trackers
|
|
120
|
+
kanbot config server URL, token, runner name, enable/disable agents
|
|
121
|
+
kanbot open open the board
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Config: `~/.kanbot/config.json` · data: `~/.kanbot/kanbot.db`.
|
|
125
|
+
Set `KANBOT_TOKEN` on the server to require a matching `--token` from runners.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Built-in CLI agent catalog, shared by the server (display) and runner (execution).
|
|
2
|
+
|
|
3
|
+
Each agent is defined declaratively so adding "whatever else is available in the
|
|
4
|
+
CLI" is a one-liner here, or a config override on the runner side. The runner
|
|
5
|
+
detects which `bin` are on PATH and only advertises those it finds.
|
|
6
|
+
|
|
7
|
+
Command templates use Python str.format with:
|
|
8
|
+
{prompt} -> the task prompt (already shell-safe; passed as a single argv item)
|
|
9
|
+
The command is a list of argv tokens; the runner substitutes {prompt} per-token
|
|
10
|
+
so no shell quoting is needed.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class AgentSpec:
|
|
20
|
+
name: str # stable id, e.g. "claude"
|
|
21
|
+
label: str # display name
|
|
22
|
+
bin: str # executable to look for on PATH
|
|
23
|
+
argv: List[str] # argv template; tokens may contain {prompt}
|
|
24
|
+
description: str = ""
|
|
25
|
+
env: Dict[str, str] = field(default_factory=dict)
|
|
26
|
+
color: str = "#8b5cf6"
|
|
27
|
+
# argv template to resume/continue an existing agent session. Tokens may
|
|
28
|
+
# contain {prompt} and {session_id}. Empty => resume not supported.
|
|
29
|
+
resume_argv: List[str] = field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Non-interactive / headless invocations for each known coding CLI.
|
|
33
|
+
BUILTIN_AGENTS: List[AgentSpec] = [
|
|
34
|
+
AgentSpec(
|
|
35
|
+
name="claude",
|
|
36
|
+
label="Claude Code",
|
|
37
|
+
bin="claude",
|
|
38
|
+
argv=["claude", "-p", "{prompt}", "--dangerously-skip-permissions"],
|
|
39
|
+
resume_argv=["claude", "--resume", "{session_id}", "-p", "{prompt}",
|
|
40
|
+
"--dangerously-skip-permissions"],
|
|
41
|
+
description="Anthropic Claude Code in headless print mode.",
|
|
42
|
+
color="#d97757",
|
|
43
|
+
),
|
|
44
|
+
AgentSpec(
|
|
45
|
+
name="codex",
|
|
46
|
+
label="Codex",
|
|
47
|
+
bin="codex",
|
|
48
|
+
argv=["codex", "exec", "--sandbox", "workspace-write",
|
|
49
|
+
"--skip-git-repo-check", "{prompt}"],
|
|
50
|
+
resume_argv=["codex", "exec", "resume", "--skip-git-repo-check",
|
|
51
|
+
"{session_id}", "{prompt}"],
|
|
52
|
+
description="OpenAI Codex CLI, non-interactive exec (workspace-write sandbox).",
|
|
53
|
+
color="#10a37f",
|
|
54
|
+
),
|
|
55
|
+
AgentSpec(
|
|
56
|
+
name="gemini",
|
|
57
|
+
label="Gemini CLI",
|
|
58
|
+
bin="gemini",
|
|
59
|
+
argv=["gemini", "-y", "-p", "{prompt}"],
|
|
60
|
+
description="Google Gemini CLI in YOLO/auto mode.",
|
|
61
|
+
color="#4285f4",
|
|
62
|
+
),
|
|
63
|
+
AgentSpec(
|
|
64
|
+
name="glm",
|
|
65
|
+
label="GLM / Z.ai",
|
|
66
|
+
bin="claude",
|
|
67
|
+
argv=["claude", "-p", "{prompt}", "--dangerously-skip-permissions"],
|
|
68
|
+
resume_argv=["claude", "--resume", "{session_id}", "-p", "{prompt}",
|
|
69
|
+
"--dangerously-skip-permissions"],
|
|
70
|
+
description="Z.ai GLM coding plan via Claude Code (set ANTHROPIC_BASE_URL).",
|
|
71
|
+
env={"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic"},
|
|
72
|
+
color="#2563eb",
|
|
73
|
+
),
|
|
74
|
+
AgentSpec(
|
|
75
|
+
name="opencode",
|
|
76
|
+
label="OpenCode",
|
|
77
|
+
bin="opencode",
|
|
78
|
+
argv=["opencode", "run", "{prompt}"],
|
|
79
|
+
description="OpenCode terminal agent, non-interactive run.",
|
|
80
|
+
color="#f59e0b",
|
|
81
|
+
),
|
|
82
|
+
AgentSpec(
|
|
83
|
+
name="hermes",
|
|
84
|
+
label="Hermes",
|
|
85
|
+
bin="hermes",
|
|
86
|
+
argv=["hermes", "-p", "{prompt}"],
|
|
87
|
+
description="Hermes coding agent (best-effort; override argv in config if it differs).",
|
|
88
|
+
color="#e879f9",
|
|
89
|
+
),
|
|
90
|
+
AgentSpec(
|
|
91
|
+
name="aider",
|
|
92
|
+
label="Aider",
|
|
93
|
+
bin="aider",
|
|
94
|
+
argv=["aider", "--yes", "--no-auto-commits", "--message", "{prompt}"],
|
|
95
|
+
description="Aider pair-programmer, single message mode.",
|
|
96
|
+
color="#22c55e",
|
|
97
|
+
),
|
|
98
|
+
AgentSpec(
|
|
99
|
+
name="cursor-agent",
|
|
100
|
+
label="Cursor Agent",
|
|
101
|
+
bin="cursor-agent",
|
|
102
|
+
argv=["cursor-agent", "-p", "{prompt}"],
|
|
103
|
+
description="Cursor CLI agent in print mode.",
|
|
104
|
+
color="#000000",
|
|
105
|
+
),
|
|
106
|
+
AgentSpec(
|
|
107
|
+
name="shell",
|
|
108
|
+
label="Shell command",
|
|
109
|
+
bin="bash",
|
|
110
|
+
argv=["bash", "-lc", "{prompt}"],
|
|
111
|
+
description="Run the prompt as a raw shell command. Always available.",
|
|
112
|
+
color="#64748b",
|
|
113
|
+
),
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
BUILTIN_BY_NAME: Dict[str, AgentSpec] = {a.name: a for a in BUILTIN_AGENTS}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def builtin_names() -> List[str]:
|
|
120
|
+
return [a.name for a in BUILTIN_AGENTS]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def spec_to_dict(a: AgentSpec) -> dict:
|
|
124
|
+
return {
|
|
125
|
+
"name": a.name,
|
|
126
|
+
"label": a.label,
|
|
127
|
+
"bin": a.bin,
|
|
128
|
+
"description": a.description,
|
|
129
|
+
"color": a.color,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def catalog() -> List[dict]:
|
|
134
|
+
return [spec_to_dict(a) for a in BUILTIN_AGENTS]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""KanBot command-line interface.
|
|
2
|
+
|
|
3
|
+
kanbot up # start server + a local runner together (best first run)
|
|
4
|
+
kanbot server # just the web server / API / board
|
|
5
|
+
kanbot runner # just the background runner (connects to a server)
|
|
6
|
+
kanbot agents # show which CLI coding agents are detected here
|
|
7
|
+
kanbot config # view / set server URL, token, runner name
|
|
8
|
+
kanbot open # open the board in your browser
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import asyncio
|
|
14
|
+
import sys
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
import webbrowser
|
|
18
|
+
|
|
19
|
+
from . import __version__
|
|
20
|
+
from .config import Config, config_path, db_path
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _rich():
|
|
24
|
+
try:
|
|
25
|
+
from rich.console import Console
|
|
26
|
+
return Console()
|
|
27
|
+
except Exception:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def cmd_server(args) -> int:
|
|
32
|
+
import uvicorn
|
|
33
|
+
from .server.app import create_app
|
|
34
|
+
|
|
35
|
+
app = create_app(db_path=args.db)
|
|
36
|
+
url = f"http://{args.host}:{args.port}"
|
|
37
|
+
print(f"KanBot server v{__version__} → {url}")
|
|
38
|
+
print(f" db: {args.db or db_path()}")
|
|
39
|
+
print(f" open: {url} (then run `kanbot runner` on any machine)")
|
|
40
|
+
uvicorn.run(app, host=args.host, port=args.port, log_level=args.log_level)
|
|
41
|
+
return 0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def cmd_runner(args) -> int:
|
|
45
|
+
from .runner.worker import Runner
|
|
46
|
+
|
|
47
|
+
cfg = Config.load()
|
|
48
|
+
if args.server:
|
|
49
|
+
cfg.server_url = args.server
|
|
50
|
+
if args.token:
|
|
51
|
+
cfg.token = args.token
|
|
52
|
+
if args.name:
|
|
53
|
+
cfg.runner_name = args.name
|
|
54
|
+
if args.concurrency:
|
|
55
|
+
cfg.max_concurrency = args.concurrency
|
|
56
|
+
cfg.save()
|
|
57
|
+
|
|
58
|
+
runner = Runner(cfg)
|
|
59
|
+
try:
|
|
60
|
+
asyncio.run(runner.run_forever())
|
|
61
|
+
except KeyboardInterrupt:
|
|
62
|
+
print("\nrunner stopped.")
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cmd_up(args) -> int:
|
|
67
|
+
"""Start the server in-process and attach a local runner. One command demo."""
|
|
68
|
+
import uvicorn
|
|
69
|
+
from .runner.worker import Runner
|
|
70
|
+
from .server.app import create_app
|
|
71
|
+
|
|
72
|
+
app = create_app(db_path=args.db)
|
|
73
|
+
config = uvicorn.Config(app, host=args.host, port=args.port, log_level="warning")
|
|
74
|
+
server = uvicorn.Server(config)
|
|
75
|
+
|
|
76
|
+
def serve():
|
|
77
|
+
asyncio.run(server.serve())
|
|
78
|
+
|
|
79
|
+
t = threading.Thread(target=serve, daemon=True)
|
|
80
|
+
t.start()
|
|
81
|
+
|
|
82
|
+
# wait for the server to come up
|
|
83
|
+
import httpx
|
|
84
|
+
base = f"http://{args.host}:{args.port}"
|
|
85
|
+
for _ in range(50):
|
|
86
|
+
try:
|
|
87
|
+
httpx.get(base + "/api/health", timeout=0.5)
|
|
88
|
+
break
|
|
89
|
+
except Exception:
|
|
90
|
+
time.sleep(0.1)
|
|
91
|
+
|
|
92
|
+
print(f"KanBot is up → {base}")
|
|
93
|
+
if not args.no_open:
|
|
94
|
+
try:
|
|
95
|
+
webbrowser.open(base)
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
cfg = Config.load()
|
|
100
|
+
cfg.server_url = base
|
|
101
|
+
if args.name:
|
|
102
|
+
cfg.runner_name = args.name
|
|
103
|
+
if args.concurrency:
|
|
104
|
+
cfg.max_concurrency = args.concurrency
|
|
105
|
+
cfg.save()
|
|
106
|
+
runner = Runner(cfg)
|
|
107
|
+
print(f"Local runner '{cfg.runner_name}' attaching with agents: "
|
|
108
|
+
f"{', '.join(runner.agents) or '(none — install claude/codex/etc.)'}")
|
|
109
|
+
print("Press Ctrl-C to stop.\n")
|
|
110
|
+
try:
|
|
111
|
+
asyncio.run(runner.run_forever())
|
|
112
|
+
except KeyboardInterrupt:
|
|
113
|
+
print("\nshutting down.")
|
|
114
|
+
server.should_exit = True
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cmd_agents(args) -> int:
|
|
119
|
+
from .runner.agents import detect_agents
|
|
120
|
+
from .agents import BUILTIN_AGENTS
|
|
121
|
+
|
|
122
|
+
cfg = Config.load()
|
|
123
|
+
found = detect_agents(cfg)
|
|
124
|
+
console = _rich()
|
|
125
|
+
if console:
|
|
126
|
+
from rich.table import Table
|
|
127
|
+
table = Table(title="CLI agents on this machine")
|
|
128
|
+
table.add_column("agent")
|
|
129
|
+
table.add_column("status")
|
|
130
|
+
table.add_column("description")
|
|
131
|
+
for spec in BUILTIN_AGENTS:
|
|
132
|
+
ok = spec.name in found
|
|
133
|
+
disabled = spec.name in cfg.disabled_agents
|
|
134
|
+
status = "[green]available[/green]" if ok else (
|
|
135
|
+
"[yellow]disabled[/yellow]" if disabled else "[dim]not found[/dim]")
|
|
136
|
+
table.add_row(spec.name, status, spec.description)
|
|
137
|
+
console.print(table)
|
|
138
|
+
else:
|
|
139
|
+
for spec in BUILTIN_AGENTS:
|
|
140
|
+
mark = "✓" if spec.name in found else "·"
|
|
141
|
+
print(f" {mark} {spec.name:14} {spec.description}")
|
|
142
|
+
print(f"\nadvertised capabilities: {', '.join(found) or '(none)'}")
|
|
143
|
+
|
|
144
|
+
# Session trackers: which TUIs KanBot can see / revive.
|
|
145
|
+
from .runner.discovery import active_providers, builtin_providers
|
|
146
|
+
trackers = active_providers(cfg.discovery_sources)
|
|
147
|
+
active_names = {t["name"] for t in trackers}
|
|
148
|
+
print("\nsession trackers (TUIs KanBot watches):")
|
|
149
|
+
for p in builtin_providers():
|
|
150
|
+
mark = "✓" if p.name in active_names else "·"
|
|
151
|
+
state = "tracking" if p.name in active_names else "no sessions found"
|
|
152
|
+
print(f" {mark} {p.label:14} {p.root} [{state}]")
|
|
153
|
+
for t in trackers:
|
|
154
|
+
if t["name"] not in ("claude", "codex"):
|
|
155
|
+
print(f" ✓ {t['label']:14} {t['root']} [custom]")
|
|
156
|
+
print("\nTrack another agent: add to discovery_sources in "
|
|
157
|
+
f"{config_path()}, e.g.\n"
|
|
158
|
+
' {"name": "hermes", "label": "Hermes", "root": "~/.hermes/sessions",\n'
|
|
159
|
+
' "pattern": "*.jsonl", "recursive": true, "fmt": "claude"}')
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def cmd_config(args) -> int:
|
|
164
|
+
cfg = Config.load()
|
|
165
|
+
changed = False
|
|
166
|
+
if args.server:
|
|
167
|
+
cfg.server_url = args.server; changed = True
|
|
168
|
+
if args.token is not None:
|
|
169
|
+
cfg.token = args.token; changed = True
|
|
170
|
+
if args.name:
|
|
171
|
+
cfg.runner_name = args.name; changed = True
|
|
172
|
+
if args.concurrency:
|
|
173
|
+
cfg.max_concurrency = args.concurrency; changed = True
|
|
174
|
+
if args.disable:
|
|
175
|
+
for a in args.disable:
|
|
176
|
+
if a not in cfg.disabled_agents:
|
|
177
|
+
cfg.disabled_agents.append(a)
|
|
178
|
+
changed = True
|
|
179
|
+
if args.enable:
|
|
180
|
+
cfg.disabled_agents = [a for a in cfg.disabled_agents if a not in args.enable]
|
|
181
|
+
changed = True
|
|
182
|
+
if changed:
|
|
183
|
+
cfg.save()
|
|
184
|
+
print(f"saved {config_path()}")
|
|
185
|
+
print(f"server_url : {cfg.server_url}")
|
|
186
|
+
print(f"token : {'(set)' if cfg.token else '(none)'}")
|
|
187
|
+
print(f"runner_name : {cfg.runner_name}")
|
|
188
|
+
print(f"runner_id : {cfg.runner_id}")
|
|
189
|
+
print(f"max_concurrency : {cfg.max_concurrency}")
|
|
190
|
+
print(f"disabled_agents : {', '.join(cfg.disabled_agents) or '(none)'}")
|
|
191
|
+
return 0
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def cmd_open(args) -> int:
|
|
195
|
+
cfg = Config.load()
|
|
196
|
+
url = args.server or cfg.server_url
|
|
197
|
+
print(f"opening {url}")
|
|
198
|
+
webbrowser.open(url)
|
|
199
|
+
return 0
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
203
|
+
p = argparse.ArgumentParser(prog="kanbot", description=__doc__,
|
|
204
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
205
|
+
p.add_argument("--version", action="version", version=f"kanbot {__version__}")
|
|
206
|
+
sub = p.add_subparsers(dest="cmd")
|
|
207
|
+
|
|
208
|
+
sp = sub.add_parser("up", help="start server + local runner (recommended first run)")
|
|
209
|
+
sp.add_argument("--host", default="127.0.0.1")
|
|
210
|
+
sp.add_argument("--port", type=int, default=8787)
|
|
211
|
+
sp.add_argument("--db", default=None)
|
|
212
|
+
sp.add_argument("--name", default=None, help="runner name")
|
|
213
|
+
sp.add_argument("--concurrency", type=int, default=None)
|
|
214
|
+
sp.add_argument("--no-open", action="store_true", help="don't open the browser")
|
|
215
|
+
sp.set_defaults(func=cmd_up)
|
|
216
|
+
|
|
217
|
+
sp = sub.add_parser("server", help="run the web server / API only")
|
|
218
|
+
sp.add_argument("--host", default="127.0.0.1")
|
|
219
|
+
sp.add_argument("--port", type=int, default=8787)
|
|
220
|
+
sp.add_argument("--db", default=None)
|
|
221
|
+
sp.add_argument("--log-level", default="info")
|
|
222
|
+
sp.set_defaults(func=cmd_server)
|
|
223
|
+
|
|
224
|
+
sp = sub.add_parser("runner", help="run the background runner only")
|
|
225
|
+
sp.add_argument("--server", default=None, help="server URL (e.g. http://host:8787)")
|
|
226
|
+
sp.add_argument("--token", default=None)
|
|
227
|
+
sp.add_argument("--name", default=None)
|
|
228
|
+
sp.add_argument("--concurrency", type=int, default=None)
|
|
229
|
+
sp.set_defaults(func=cmd_runner)
|
|
230
|
+
|
|
231
|
+
sp = sub.add_parser("agents", help="show detected CLI agents")
|
|
232
|
+
sp.set_defaults(func=cmd_agents)
|
|
233
|
+
|
|
234
|
+
sp = sub.add_parser("config", help="view or set configuration")
|
|
235
|
+
sp.add_argument("--server", default=None)
|
|
236
|
+
sp.add_argument("--token", default=None)
|
|
237
|
+
sp.add_argument("--name", default=None)
|
|
238
|
+
sp.add_argument("--concurrency", type=int, default=None)
|
|
239
|
+
sp.add_argument("--disable", nargs="*", help="agent names to disable")
|
|
240
|
+
sp.add_argument("--enable", nargs="*", help="agent names to re-enable")
|
|
241
|
+
sp.set_defaults(func=cmd_config)
|
|
242
|
+
|
|
243
|
+
sp = sub.add_parser("open", help="open the board in a browser")
|
|
244
|
+
sp.add_argument("--server", default=None)
|
|
245
|
+
sp.set_defaults(func=cmd_open)
|
|
246
|
+
|
|
247
|
+
return p
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def main(argv=None) -> int:
|
|
251
|
+
parser = build_parser()
|
|
252
|
+
args = parser.parse_args(argv)
|
|
253
|
+
if not getattr(args, "cmd", None):
|
|
254
|
+
parser.print_help()
|
|
255
|
+
return 0
|
|
256
|
+
return args.func(args)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
if __name__ == "__main__":
|
|
260
|
+
sys.exit(main())
|