monkeybot-cli 0.10.0__tar.gz → 0.11.1__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.
- monkeybot_cli-0.11.1/.gitignore +90 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/PKG-INFO +2 -2
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/pyproject.toml +8 -2
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/skills/monkeybot/SKILL.md +1 -1
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/skills/monkeybot/references/config-sections.md +3 -1
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_tui_widgets.py +58 -1
- monkeybot_cli-0.11.1/src/monkeybot_cli/commands/mcp_login.py +81 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/new.py +44 -4
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/main.py +13 -1
- monkeybot_cli-0.11.1/src/monkeybot_cli/overrides.txt +5 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/runtime_python.py +161 -50
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold.py +88 -16
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/command_allowlist.yaml +2 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/monkeybot.example.yaml +16 -1
- monkeybot_cli-0.11.1/src/monkeybot_cli/scaffold_defaults/templates/coding/AGENT.md +35 -0
- monkeybot_cli-0.11.1/src/monkeybot_cli/scaffold_defaults/templates/coding/command_allowlist.yaml +92 -0
- monkeybot_cli-0.11.1/src/monkeybot_cli/scaffold_defaults/templates/coding/monkeybot.overrides.yaml +31 -0
- monkeybot_cli-0.11.1/src/monkeybot_cli/scaffold_defaults/templates/coding/permissions.yaml +42 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_tui.py +79 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_cli.py +44 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_runtime_python.py +170 -15
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_scaffold.py +194 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/uv.lock +200 -179
- monkeybot_cli-0.10.0/.gitignore +0 -71
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/__init__.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_file_index.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_local_shell.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_renderer.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_session.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_status_bar.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_theme.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_tool_display.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/chat_tui.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/__init__.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/chat.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/doctor.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/loop.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/refresh.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/run_cmd.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/talk.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/trace.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/commands/validate.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/compat.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/config_resolve.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/exit_commands.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/extras_catalog.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/gateway_health.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/opensandbox_lifecycle.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/output.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/process_tree.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/providers.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/__init__.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/audio_io.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/client.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/gateway_manager.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/push_to_talk.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/session.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/session_controller.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/talk_ui.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/realtime/wire_encode.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/AGENT.md +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/Dockerfile +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/__init__.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/browser/SKILL.md +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/dockerignore +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/env.example +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/goal/SKILL.md +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/image-generator/SKILL.md +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/image-generator/generate_image.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/loop/SKILL.md +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/mcp.json +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/opensandbox.docker.toml +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/otel-collector.example.yaml +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/scaffold_defaults/permissions.yaml +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/session_controller.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/src/monkeybot_cli/terminal_markdown.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_continue.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_e2e.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_errors.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_file_index.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_grounding.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_local_shell.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_renderer_parity.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_session.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_status_bar.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_theme.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_chat_tool_display.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_config_resolve.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_doctor.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_extras_catalog.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_gateway_health.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_opensandbox_lifecycle.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_realtime_session_controller.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_run_cmd.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_terminal_markdown.py +0 -0
- {monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/tests/test_trace_cmd.py +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Internal (not shipped in OSS)
|
|
2
|
+
internal/
|
|
3
|
+
|
|
4
|
+
# Environment
|
|
5
|
+
.env
|
|
6
|
+
.env.*
|
|
7
|
+
!.env.example
|
|
8
|
+
# The grants fixture's .env is fixture data, not a local secret.
|
|
9
|
+
!regression/fixtures/workspaces/grants_ws/granted/.env
|
|
10
|
+
|
|
11
|
+
# Local scaffolded harness config (defaults ship in monkeybot-cli scaffold_defaults/)
|
|
12
|
+
/monkeybot_config/
|
|
13
|
+
|
|
14
|
+
# Live eval run artifacts. Baselines under regression/t4/baselines/ stay versioned.
|
|
15
|
+
/regression/t4/runs/
|
|
16
|
+
# Live-eval agent project local state. Live secrets come from the repo-root .env; a stray key
|
|
17
|
+
# file in the agent project stays out of git. The two workspace .gitkeep files need git add -f.
|
|
18
|
+
/regression/t4/agent/.env
|
|
19
|
+
/regression/t4/agent/data/
|
|
20
|
+
/regression/t4/agent/workspace/
|
|
21
|
+
|
|
22
|
+
# Python
|
|
23
|
+
__pycache__/
|
|
24
|
+
*.py[cod]
|
|
25
|
+
*.pyo
|
|
26
|
+
*.pyd
|
|
27
|
+
.Python
|
|
28
|
+
*.egg-info/
|
|
29
|
+
dist/
|
|
30
|
+
build/
|
|
31
|
+
.eggs/
|
|
32
|
+
*.egg
|
|
33
|
+
|
|
34
|
+
# uv
|
|
35
|
+
.venv/
|
|
36
|
+
.uv/
|
|
37
|
+
|
|
38
|
+
# Data (SQLite DB + memory files — never commit)
|
|
39
|
+
data/
|
|
40
|
+
# Shared attachment fixtures live here (§5.4); they are not a local data dir.
|
|
41
|
+
!testkit/data/
|
|
42
|
+
!testkit/data/**
|
|
43
|
+
/memory/
|
|
44
|
+
.monkeybot/
|
|
45
|
+
# Store layout for the attachment loop fixture. The directory name is ignored above.
|
|
46
|
+
!regression/fixtures/workspaces/att_ws/.monkeybot/
|
|
47
|
+
!regression/fixtures/workspaces/att_ws/.monkeybot/**
|
|
48
|
+
*.sqlite3
|
|
49
|
+
workspace/.monkeybot/
|
|
50
|
+
*.sqlite
|
|
51
|
+
*.sqlite.writer-pid
|
|
52
|
+
|
|
53
|
+
# Testing & coverage
|
|
54
|
+
/test-runs/
|
|
55
|
+
/.cursor/hooks/.state/
|
|
56
|
+
.pytest_cache/
|
|
57
|
+
.coverage
|
|
58
|
+
htmlcov/
|
|
59
|
+
.mypy_cache/
|
|
60
|
+
.ruff_cache/
|
|
61
|
+
.deepeval/
|
|
62
|
+
|
|
63
|
+
# macOS
|
|
64
|
+
.DS_Store
|
|
65
|
+
|
|
66
|
+
# IDEs
|
|
67
|
+
.idea/
|
|
68
|
+
.vscode/
|
|
69
|
+
*.swp
|
|
70
|
+
*.swo
|
|
71
|
+
|
|
72
|
+
# Docker
|
|
73
|
+
docker/data/
|
|
74
|
+
|
|
75
|
+
# Planning Documents
|
|
76
|
+
.prt/
|
|
77
|
+
.monkeymode/
|
|
78
|
+
|
|
79
|
+
# Reference clones (read-only)
|
|
80
|
+
code/
|
|
81
|
+
|
|
82
|
+
# Local venv for evals dependency experiments
|
|
83
|
+
.evals-venv/
|
|
84
|
+
|
|
85
|
+
# monkeythink
|
|
86
|
+
.monkeythink/
|
|
87
|
+
# Eval fixtures ship their own build/ decoy
|
|
88
|
+
!/regression/fixtures/workspaces/calc_repo/build/
|
|
89
|
+
!/regression/fixtures/workspaces/calc_repo_green/build/
|
|
90
|
+
!/regression/fixtures/workspaces/shop_repo/build/
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: monkeybot-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.1
|
|
4
4
|
Summary: CLI to create, configure, validate, and chat with monkeybot agents.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: httpx>=0.27.0
|
|
7
7
|
Requires-Dist: monkeybot-browser-mcp<1,>=0.5.0
|
|
8
|
-
Requires-Dist: monkeybot[cli]<4,>=3.
|
|
8
|
+
Requires-Dist: monkeybot[cli]<4,>=3.10.0
|
|
9
9
|
Requires-Dist: python-dotenv>=1.0.0
|
|
10
10
|
Requires-Dist: pyyaml>=6.0.2
|
|
11
11
|
Requires-Dist: textual>=8.2.8
|
|
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "monkeybot-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.11.1"
|
|
8
8
|
description = "CLI to create, configure, validate, and chat with monkeybot agents."
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
11
11
|
# `cli` pulls realtime + typer so `monkeybot talk` works out of the box (text mode).
|
|
12
12
|
# Audio mic/PTT needs the agent (or this env) to also install monkeybot[cli-realtime].
|
|
13
|
-
"monkeybot[cli]>=3.
|
|
13
|
+
"monkeybot[cli]>=3.10.0,<4",
|
|
14
14
|
"monkeybot-browser-mcp>=0.5.0,<1",
|
|
15
15
|
"httpx>=0.27.0",
|
|
16
16
|
"pyyaml>=6.0.2",
|
|
@@ -21,6 +21,12 @@ dependencies = [
|
|
|
21
21
|
[project.scripts]
|
|
22
22
|
monkeybot = "monkeybot_cli.main:main"
|
|
23
23
|
|
|
24
|
+
[tool.uv]
|
|
25
|
+
# browser-harness 0.1.13 pins pillow==12.3.0, which is still inside the Pillow 13 advisory range.
|
|
26
|
+
# uv lock and uv sync honor this. uv tool install and uv pip install do not;
|
|
27
|
+
# those commands pass src/monkeybot_cli/overrides.txt via --overrides.
|
|
28
|
+
override-dependencies = ["pillow>=13"]
|
|
29
|
+
|
|
24
30
|
[tool.uv.sources]
|
|
25
31
|
monkeybot = { path = "..", editable = true }
|
|
26
32
|
monkeybot-browser-mcp = { path = "../integrations/browser-mcp", editable = true }
|
|
@@ -176,7 +176,7 @@ Only if the user is changing monkeybot itself (not creating an agent):
|
|
|
176
176
|
git clone https://github.com/human-plus-machine/monkeybot.git
|
|
177
177
|
cd monkeybot && uv sync
|
|
178
178
|
cd cli && uv sync
|
|
179
|
-
uv tool install --editable .
|
|
179
|
+
uv tool install --editable . --overrides src/monkeybot_cli/overrides.txt
|
|
180
180
|
```
|
|
181
181
|
|
|
182
182
|
## Rules
|
{monkeybot_cli-0.10.0 → monkeybot_cli-0.11.1}/skills/monkeybot/references/config-sections.md
RENAMED
|
@@ -84,7 +84,9 @@ MemPalace capture, wake-up, and search teaching are on by default. Turn them off
|
|
|
84
84
|
|---|---|---|
|
|
85
85
|
| `enabled` | `true` | Set `false` to skip capture, wake-up, and prompt teaching |
|
|
86
86
|
| `backend` | `chroma` | Alternate MemPalace vector backend |
|
|
87
|
-
| `embedding_model` | `embeddinggemma-300m`
|
|
87
|
+
| `embedding_model` | `minilm` | MemPalace's default embedder. `embeddinggemma-300m` is a legacy alias; startup rewrites it to `minilm` |
|
|
88
|
+
|
|
89
|
+
Chats in one project share saved lines by default, including the wake-up briefing. Set `MONKEYBOT_MEMORY_SHARE_THREADS=0` to keep each chat's lines in that chat. Unset, `1`, `true`, `yes`, and `on` share.
|
|
88
90
|
|
|
89
91
|
## `subagents`
|
|
90
92
|
|
|
@@ -28,6 +28,10 @@ from monkeybot_cli.chat_tool_display import format_tool_expand_body
|
|
|
28
28
|
_SPIN = "⠋⠙⠹⠸⠴⠦⠧⠇⠏"
|
|
29
29
|
_HISTORY_LIMIT = 500
|
|
30
30
|
_COMPOSER_PLACEHOLDER = "Message the agent — / commands · @ files · ! shell"
|
|
31
|
+
# Enter's raw bytes. modifyOtherKeys and a few terminals report Shift/Alt+Enter
|
|
32
|
+
# as these characters instead of the name "enter".
|
|
33
|
+
_ENTER_KEY_NAMES = frozenset({"enter", "\r", "\n"})
|
|
34
|
+
_NEWLINE_MODIFIERS = frozenset({"shift", "alt"})
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
def _plain_title(text: str) -> Content:
|
|
@@ -747,6 +751,50 @@ class ShortcutsScreen(ModalScreen[None]):
|
|
|
747
751
|
self.dismiss(None)
|
|
748
752
|
|
|
749
753
|
|
|
754
|
+
def is_newline_key(key: str) -> bool:
|
|
755
|
+
"""True when this key should insert a newline instead of sending.
|
|
756
|
+
|
|
757
|
+
Kitty-protocol terminals report ``shift+enter``. modifyOtherKeys and some
|
|
758
|
+
macOS terminals report the same chord as ``shift+`` plus a raw CR or LF.
|
|
759
|
+
``ctrl+j`` is the chord every terminal can deliver.
|
|
760
|
+
"""
|
|
761
|
+
if key in {"shift+enter", "alt+enter", "ctrl+j", "newline"}:
|
|
762
|
+
return True
|
|
763
|
+
parts = key.split("+")
|
|
764
|
+
if len(parts) < 2 or parts[-1] not in _ENTER_KEY_NAMES:
|
|
765
|
+
return False
|
|
766
|
+
return bool(_NEWLINE_MODIFIERS.intersection(parts[:-1]))
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def install_terminal_newline_keys() -> None:
|
|
770
|
+
"""Teach Textual's parser the Enter chords it currently mislabels.
|
|
771
|
+
|
|
772
|
+
Alt+Enter arrives as ESC then CR, and Textual drops the Alt because Enter
|
|
773
|
+
is not a single-character key. modifyOtherKeys Shift+Enter arrives as
|
|
774
|
+
``shift+`` plus a raw carriage return, which matches no binding.
|
|
775
|
+
"""
|
|
776
|
+
from textual._xterm_parser import XTermParser
|
|
777
|
+
|
|
778
|
+
current = XTermParser._sequence_to_key_events
|
|
779
|
+
if getattr(current, "_monkeybot_newline", False):
|
|
780
|
+
return
|
|
781
|
+
|
|
782
|
+
def _sequence_to_key_events(self: XTermParser, sequence: str, alt: bool = False): # type: ignore[no-untyped-def]
|
|
783
|
+
for event in current(self, sequence, alt=alt):
|
|
784
|
+
key = event.key
|
|
785
|
+
if alt and key == "enter":
|
|
786
|
+
yield Key("alt+enter", None)
|
|
787
|
+
elif key in {"shift+\r", "shift+\n"}:
|
|
788
|
+
yield Key("shift+enter", None)
|
|
789
|
+
elif key in {"alt+\r", "alt+\n"}:
|
|
790
|
+
yield Key("alt+enter", None)
|
|
791
|
+
else:
|
|
792
|
+
yield event
|
|
793
|
+
|
|
794
|
+
_sequence_to_key_events._monkeybot_newline = True # type: ignore[attr-defined]
|
|
795
|
+
XTermParser._sequence_to_key_events = _sequence_to_key_events # type: ignore[method-assign]
|
|
796
|
+
|
|
797
|
+
|
|
750
798
|
class Composer(TextArea):
|
|
751
799
|
"""Growing multiline input: Enter sends, Shift+Enter/Ctrl+J newline, history, slash."""
|
|
752
800
|
|
|
@@ -1003,6 +1051,11 @@ class Composer(TextArea):
|
|
|
1003
1051
|
if self._search_mode:
|
|
1004
1052
|
self._handle_search_key(event)
|
|
1005
1053
|
return
|
|
1054
|
+
if is_newline_key(event.key):
|
|
1055
|
+
self.action_insert_newline()
|
|
1056
|
+
event.prevent_default()
|
|
1057
|
+
event.stop()
|
|
1058
|
+
return
|
|
1006
1059
|
if event.character == "?" and not self.text.strip():
|
|
1007
1060
|
with contextlib.suppress(Exception):
|
|
1008
1061
|
app = self.app
|
|
@@ -1054,7 +1107,8 @@ class Composer(TextArea):
|
|
|
1054
1107
|
event.prevent_default()
|
|
1055
1108
|
event.stop()
|
|
1056
1109
|
return
|
|
1057
|
-
|
|
1110
|
+
# TextArea._on_key runs next via the message MRO and inserts printable
|
|
1111
|
+
# characters. Calling it here as well only creates an unawaited coroutine.
|
|
1058
1112
|
self.call_after_refresh(self._sync_height)
|
|
1059
1113
|
|
|
1060
1114
|
def _handle_search_key(self, event: Key) -> None:
|
|
@@ -1115,3 +1169,6 @@ class TranscriptPane(VerticalScroll):
|
|
|
1115
1169
|
on_scroll = getattr(app, "_on_transcript_scroll", None)
|
|
1116
1170
|
if callable(on_scroll):
|
|
1117
1171
|
on_scroll()
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
install_terminal_newline_keys()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""monkeybot mcp-login — browser sign-in for one remote MCP server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from mcp.client.auth.exceptions import OAuthFlowError
|
|
14
|
+
from monkeybot.core.mcp.mcp_client import MCPAuthError
|
|
15
|
+
from monkeybot.core.mcp.oauth_flow import login_mcp_server
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
CLIENT_SECRET_ENV = "MONKEYBOT_MCP_CLIENT_SECRET"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run_mcp_login(args: argparse.Namespace) -> int:
|
|
23
|
+
"""Sign in, print JSON progress on stdout, and return 0 when the attempt finishes."""
|
|
24
|
+
client_secret = args.client_secret or os.environ.get(CLIENT_SECRET_ENV, "")
|
|
25
|
+
try:
|
|
26
|
+
result = asyncio.run(
|
|
27
|
+
login_mcp_server(
|
|
28
|
+
args.url,
|
|
29
|
+
Path(args.storage).expanduser(),
|
|
30
|
+
open_browser=not args.no_browser,
|
|
31
|
+
client_id=args.client_id,
|
|
32
|
+
client_secret=client_secret,
|
|
33
|
+
redirect_port=args.redirect_port,
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
except (MCPAuthError, OAuthFlowError) as exc:
|
|
37
|
+
print(str(exc), file=sys.stderr)
|
|
38
|
+
return 1
|
|
39
|
+
except Exception:
|
|
40
|
+
logger.exception("mcp oauth sign-in failed")
|
|
41
|
+
print("Sign-in failed.", file=sys.stderr)
|
|
42
|
+
return 1
|
|
43
|
+
print(json.dumps(result), flush=True)
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def register(sub: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
48
|
+
"""Register the ``mcp-login`` subcommand."""
|
|
49
|
+
command = sub.add_parser(
|
|
50
|
+
"mcp-login",
|
|
51
|
+
help="Sign in to a remote MCP server in the browser",
|
|
52
|
+
)
|
|
53
|
+
command.add_argument("--url", required=True, help="Streamable HTTP URL of the MCP server")
|
|
54
|
+
command.add_argument(
|
|
55
|
+
"--storage", required=True, help="Directory for this machine's OAuth tokens"
|
|
56
|
+
)
|
|
57
|
+
command.add_argument(
|
|
58
|
+
"--no-browser",
|
|
59
|
+
action="store_true",
|
|
60
|
+
help="Print the authorization URL instead of opening a browser",
|
|
61
|
+
)
|
|
62
|
+
command.add_argument(
|
|
63
|
+
"--client-id",
|
|
64
|
+
default="",
|
|
65
|
+
help="OAuth client id, for a server that will not register one automatically",
|
|
66
|
+
)
|
|
67
|
+
command.add_argument(
|
|
68
|
+
"--client-secret",
|
|
69
|
+
default="",
|
|
70
|
+
help=(
|
|
71
|
+
f"OAuth client secret, when that client requires one. Prefer {CLIENT_SECRET_ENV}: "
|
|
72
|
+
"command-line arguments are visible to other processes."
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
command.add_argument(
|
|
76
|
+
"--redirect-port",
|
|
77
|
+
type=int,
|
|
78
|
+
default=None,
|
|
79
|
+
help="Loopback port for the sign-in redirect, for a client registered with an exact URI",
|
|
80
|
+
)
|
|
81
|
+
command.set_defaults(func=run_mcp_login)
|
|
@@ -13,7 +13,7 @@ from monkeybot_cli.extras_catalog import (
|
|
|
13
13
|
additional_provider_extra_choices,
|
|
14
14
|
normalize_extra_token,
|
|
15
15
|
)
|
|
16
|
-
from monkeybot_cli.scaffold import run_new
|
|
16
|
+
from monkeybot_cli.scaffold import DEFAULT_TEMPLATE, TEMPLATES, run_new
|
|
17
17
|
|
|
18
18
|
_DEFAULT_PROVIDER = "gemini"
|
|
19
19
|
_DEFAULT_MODEL = "gemini-3-flash"
|
|
@@ -26,7 +26,19 @@ def _prompt_line(prompt: str) -> str:
|
|
|
26
26
|
return ""
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
TEMPLATE_CHOICES: tuple[ExtraChoice, ...] = (
|
|
30
|
+
ExtraChoice("default", "General-purpose agent"),
|
|
31
|
+
ExtraChoice("coding", "Coding agent for a repository (verification gate, dev toolchain)"),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _prompt_single(
|
|
36
|
+
choices: tuple[ExtraChoice, ...],
|
|
37
|
+
*,
|
|
38
|
+
title: str,
|
|
39
|
+
default_key: str,
|
|
40
|
+
noun: str = "provider",
|
|
41
|
+
) -> str:
|
|
30
42
|
"""Numbered single-select; Enter keeps default_key."""
|
|
31
43
|
print(title)
|
|
32
44
|
default_idx = 1
|
|
@@ -35,7 +47,7 @@ def _prompt_single(choices: tuple[ExtraChoice, ...], *, title: str, default_key:
|
|
|
35
47
|
if choice.key == default_key:
|
|
36
48
|
default_idx = i
|
|
37
49
|
print(f" {i}) {choice.key} — {choice.label}{marker}")
|
|
38
|
-
raw = _prompt_line(f"Choose
|
|
50
|
+
raw = _prompt_line(f"Choose {noun} [{default_idx}]: ").strip()
|
|
39
51
|
if not raw:
|
|
40
52
|
return default_key
|
|
41
53
|
if raw.isdigit():
|
|
@@ -48,7 +60,7 @@ def _prompt_single(choices: tuple[ExtraChoice, ...], *, title: str, default_key:
|
|
|
48
60
|
for choice in choices:
|
|
49
61
|
if raw.lower() == choice.key.lower():
|
|
50
62
|
return choice.key
|
|
51
|
-
print(f" unknown
|
|
63
|
+
print(f" unknown {noun} {raw!r}; using {default_key}", file=sys.stderr)
|
|
52
64
|
return default_key
|
|
53
65
|
|
|
54
66
|
|
|
@@ -128,6 +140,13 @@ def run_new_command(args: argparse.Namespace) -> int:
|
|
|
128
140
|
|
|
129
141
|
provider = args.provider
|
|
130
142
|
model = args.model
|
|
143
|
+
template = getattr(args, "template", None)
|
|
144
|
+
if template is not None and template not in TEMPLATES:
|
|
145
|
+
print(
|
|
146
|
+
f"error: unknown --template {template!r}; choose from {', '.join(TEMPLATES)}",
|
|
147
|
+
file=sys.stderr,
|
|
148
|
+
)
|
|
149
|
+
return 2
|
|
131
150
|
extras, unknown = _resolve_extras_from_args(getattr(args, "with_extras", None))
|
|
132
151
|
if unknown:
|
|
133
152
|
print(
|
|
@@ -137,6 +156,13 @@ def run_new_command(args: argparse.Namespace) -> int:
|
|
|
137
156
|
return 2
|
|
138
157
|
|
|
139
158
|
if not args.yes:
|
|
159
|
+
if template is None:
|
|
160
|
+
template = _prompt_single(
|
|
161
|
+
TEMPLATE_CHOICES,
|
|
162
|
+
title="Template:",
|
|
163
|
+
default_key=DEFAULT_TEMPLATE,
|
|
164
|
+
noun="template",
|
|
165
|
+
)
|
|
140
166
|
if provider is None:
|
|
141
167
|
provider = _prompt_single(
|
|
142
168
|
PROVIDER_CHOICES,
|
|
@@ -167,6 +193,7 @@ def run_new_command(args: argparse.Namespace) -> int:
|
|
|
167
193
|
provider=provider,
|
|
168
194
|
model=model,
|
|
169
195
|
extras=extras or None,
|
|
196
|
+
template=template or DEFAULT_TEMPLATE,
|
|
170
197
|
)
|
|
171
198
|
print(f"monkeybot scaffold under {dest}:")
|
|
172
199
|
print("\n".join(report))
|
|
@@ -177,6 +204,12 @@ def run_new_command(args: argparse.Namespace) -> int:
|
|
|
177
204
|
print(" cp .env.example .env # then add provider keys")
|
|
178
205
|
print(" monkeybot doctor")
|
|
179
206
|
print(" monkeybot chat")
|
|
207
|
+
if template == "coding":
|
|
208
|
+
print()
|
|
209
|
+
print("Coding template: clone the repository into ./workspace.")
|
|
210
|
+
print("Path prefixes in command_allowlist.yaml are relative to paths.workspace_root,")
|
|
211
|
+
print("so ../memory is the agent memory only while that stays ./workspace.")
|
|
212
|
+
print("If you set paths.workspace_root to another directory, update those prefixes.")
|
|
180
213
|
return 0
|
|
181
214
|
|
|
182
215
|
|
|
@@ -200,6 +233,13 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
200
233
|
"observability, openai, bedrock, claude"
|
|
201
234
|
),
|
|
202
235
|
)
|
|
236
|
+
p.add_argument(
|
|
237
|
+
"--template",
|
|
238
|
+
choices=TEMPLATES,
|
|
239
|
+
default=None,
|
|
240
|
+
help="Scaffold flavour: default (general agent) or coding (repo workflow). "
|
|
241
|
+
"Prompted interactively when omitted.",
|
|
242
|
+
)
|
|
203
243
|
p.add_argument("--force", action="store_true", help="Overwrite existing scaffold files")
|
|
204
244
|
p.add_argument("--yes", "-y", action="store_true", help="Skip interactive prompts")
|
|
205
245
|
p.set_defaults(func=run_new_command)
|
|
@@ -5,7 +5,18 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import sys
|
|
7
7
|
|
|
8
|
-
from monkeybot_cli.commands import
|
|
8
|
+
from monkeybot_cli.commands import (
|
|
9
|
+
chat,
|
|
10
|
+
doctor,
|
|
11
|
+
loop,
|
|
12
|
+
mcp_login,
|
|
13
|
+
new,
|
|
14
|
+
refresh,
|
|
15
|
+
run_cmd,
|
|
16
|
+
talk,
|
|
17
|
+
trace,
|
|
18
|
+
validate,
|
|
19
|
+
)
|
|
9
20
|
from monkeybot_cli.runtime_python import RuntimeUpgradeError, report_runtime_upgrade_error
|
|
10
21
|
|
|
11
22
|
|
|
@@ -25,6 +36,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
25
36
|
talk.register(sub)
|
|
26
37
|
loop.register(sub)
|
|
27
38
|
trace.register(sub)
|
|
39
|
+
mcp_login.register(sub)
|
|
28
40
|
return parser
|
|
29
41
|
|
|
30
42
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# browser-harness 0.1.13 requires pillow==12.3.0. That release is inside
|
|
2
|
+
# advisories fixed in Pillow 13. [tool.uv] override-dependencies applies this
|
|
3
|
+
# floor to uv lock and uv sync only. uv tool install and uv pip install need
|
|
4
|
+
# this file via --overrides.
|
|
5
|
+
pillow>=13
|