kon-coding-agent 0.3.1__tar.gz → 0.3.2__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.
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/CHANGELOG.md +16 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/PKG-INFO +1 -1
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/pyproject.toml +1 -1
- kon_coding_agent-0.3.2/scripts/show_themes.py +216 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/config.py +3 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/context/skills.py +6 -2
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/defaults/config.toml +3 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/_tool_utils.py +4 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/edit.py +1 -9
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/find.py +10 -3
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/grep.py +11 -4
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/web_fetch.py +2 -3
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/app.py +1 -1
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/blocks.py +6 -3
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/commands.py +12 -2
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/styles.py +2 -2
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/context/test_skills.py +19 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/tools/test_edit_display.py +7 -12
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/uv.lock +1 -1
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/.gitignore +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/.kon/skills/kon-release-publish/SKILL.md +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/.kon/skills/kon-tmux-test/SKILL.md +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/.kon/skills/kon-tmux-test/run-e2e-tests.sh +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/.kon/skills/kon-tmux-test/setup-test-project.sh +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/.python-version +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/AGENTS.md +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/LICENSE +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/README.md +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/docs/architecture-review.md +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/docs/images/kon-screenshot.png +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/docs/local-models.md +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/__init__.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/context/__init__.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/context/_xml.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/context/agent_mds.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/context/git.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/context/loader.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/core/__init__.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/core/compaction.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/core/handoff.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/core/types.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/defaults/__init__.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/events.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/__init__.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/base.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/models.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/oauth/__init__.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/oauth/copilot.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/oauth/openai.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/__init__.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/anthropic.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/azure_ai_foundry.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/copilot.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/copilot_anthropic.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/github_copilot_headers.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/mock.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/openai_codex_responses.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/openai_compat.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/openai_completions.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/openai_responses.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/sanitize.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/loop.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/metrics.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/permissions.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/py.typed +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/session.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/themes.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/__init__.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/_read_image.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/base.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/bash.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/read.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/web_search.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools/write.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/tools_manager.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/turn.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/__init__.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/app_protocol.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/autocomplete.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/chat.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/clipboard.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/export.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/floating_list.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/formatting.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/input.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/path_complete.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/prompt_history.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/selection_mode.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/session_ui.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/ui/widgets.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/update_check.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/conftest.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/context/test_agents.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/llm/__init__.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/llm/test_anthropic_provider.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/llm/test_azure_ai_foundry_provider.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/llm/test_mock_provider.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/llm/test_openai_codex_provider_errors.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_agentic_loop.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_cli_provider_resolution.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_compaction.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_config_binaries.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_config_error_fallback.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_config_injection.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_config_migration.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_handoff.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_launch_warnings.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_metrics.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_model_provider_resolution.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_openai_compat.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_permissions.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_session_persistence.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_session_resume.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_system_prompt.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_system_prompt_git_context.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_update_check.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/test_update_notice_behavior.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/tools/test_diff.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/tools/test_edit.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/tools/test_read.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/tools/test_read_image.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/tools/test_read_image_integration.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/tools/test_write.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/ui/test_autocomplete.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/ui/test_floating_list.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/ui/test_input_handoff.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/ui/test_input_paste.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/ui/test_prompt_history.py +0 -0
- {kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/ui/test_status_line.py +0 -0
|
@@ -6,6 +6,22 @@ All notable changes to this project will be documented in this file.
|
|
|
6
6
|
|
|
7
7
|
- No changes yet.
|
|
8
8
|
|
|
9
|
+
## 0.3.2 - 2026-03-22
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added a `collapse_thinking` config flag to control thinking block display.
|
|
14
|
+
- Added a Ghostty theme preview script.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Improved theme and model picker indicators.
|
|
19
|
+
- Refactored tool display helpers into shared `truncate_text` and `shorten_path` utilities.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Fixed duplicate skill warnings coming from the home directory.
|
|
24
|
+
|
|
9
25
|
## 0.3.1 - 2026-03-21
|
|
10
26
|
|
|
11
27
|
### Added
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
KON_THEME_TO_GHOSTTY_THEME = {
|
|
10
|
+
"catppuccin-latte": "Catppuccin Latte",
|
|
11
|
+
"catppuccin-mocha": "Catppuccin Mocha",
|
|
12
|
+
"dracula": "Dracula",
|
|
13
|
+
"github-dark": "GitHub Dark",
|
|
14
|
+
"github-light": "GitHub Light Default",
|
|
15
|
+
"gruvbox-dark": "Gruvbox Dark",
|
|
16
|
+
"gruvbox-light": "Gruvbox Light",
|
|
17
|
+
"nord": "Nord",
|
|
18
|
+
"one-dark": "Atom One Dark",
|
|
19
|
+
"one-light": "Atom One Light",
|
|
20
|
+
"solarized-dark": "Builtin Solarized Dark",
|
|
21
|
+
"solarized-light": "Builtin Solarized Light",
|
|
22
|
+
"tokyo-day": "TokyoNight Day",
|
|
23
|
+
"tokyo-night": "TokyoNight Night",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def parse_args() -> argparse.Namespace:
|
|
28
|
+
parser = argparse.ArgumentParser()
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--project-dir",
|
|
31
|
+
type=Path,
|
|
32
|
+
default=Path.cwd(),
|
|
33
|
+
help="Directory to open before running `uv run kon -c`.",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--ghostty-config",
|
|
37
|
+
type=Path,
|
|
38
|
+
default=Path.home() / ".config/ghostty/config",
|
|
39
|
+
help="Ghostty config file to rewrite during previews.",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--kon-config",
|
|
43
|
+
type=Path,
|
|
44
|
+
default=Path.home() / ".kon/config.toml",
|
|
45
|
+
help="Kon config file to rewrite during previews.",
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--duration", type=float, default=10.0, help="How long each preview stays open in seconds."
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--pause", type=float, default=1.5, help="Pause between previews in seconds."
|
|
52
|
+
)
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
"--ghostty-app",
|
|
55
|
+
type=Path,
|
|
56
|
+
default=Path("/Applications/Ghostty.app"),
|
|
57
|
+
help="Path to Ghostty.app for launching new macOS instances.",
|
|
58
|
+
)
|
|
59
|
+
return parser.parse_args()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def list_ghostty_themes() -> set[str]:
|
|
63
|
+
result = subprocess.run(
|
|
64
|
+
["ghostty", "+list-themes"], check=True, capture_output=True, text=True
|
|
65
|
+
)
|
|
66
|
+
themes = set()
|
|
67
|
+
for line in result.stdout.splitlines():
|
|
68
|
+
theme = line.strip()
|
|
69
|
+
if not theme:
|
|
70
|
+
continue
|
|
71
|
+
if theme.endswith("(resources)"):
|
|
72
|
+
theme = theme[: -len("(resources)")].rstrip()
|
|
73
|
+
themes.add(theme)
|
|
74
|
+
return themes
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def ensure_theme_mapping_is_valid(available_themes: set[str]) -> None:
|
|
78
|
+
missing = {
|
|
79
|
+
kon_theme: ghostty_theme
|
|
80
|
+
for kon_theme, ghostty_theme in KON_THEME_TO_GHOSTTY_THEME.items()
|
|
81
|
+
if ghostty_theme not in available_themes
|
|
82
|
+
}
|
|
83
|
+
if missing:
|
|
84
|
+
lines = ["Missing Ghostty theme mappings:"]
|
|
85
|
+
for kon_theme, ghostty_theme in missing.items():
|
|
86
|
+
lines.append(f" {kon_theme} -> {ghostty_theme}")
|
|
87
|
+
raise RuntimeError("\n".join(lines))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def replace_or_append_theme_line(config_text: str, theme_name: str) -> str:
|
|
91
|
+
lines = config_text.splitlines()
|
|
92
|
+
in_ui_section = False
|
|
93
|
+
ui_section_found = False
|
|
94
|
+
|
|
95
|
+
for index, line in enumerate(lines):
|
|
96
|
+
stripped = line.strip()
|
|
97
|
+
if stripped.startswith("[") and stripped.endswith("]"):
|
|
98
|
+
if stripped == "[ui]":
|
|
99
|
+
in_ui_section = True
|
|
100
|
+
ui_section_found = True
|
|
101
|
+
continue
|
|
102
|
+
if in_ui_section:
|
|
103
|
+
lines.insert(index, f'theme = "{theme_name}"')
|
|
104
|
+
return "\n".join(lines) + "\n"
|
|
105
|
+
in_ui_section = False
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
if in_ui_section and stripped.startswith("theme") and "=" in stripped:
|
|
109
|
+
lines[index] = f'theme = "{theme_name}"'
|
|
110
|
+
return "\n".join(lines) + "\n"
|
|
111
|
+
|
|
112
|
+
if ui_section_found:
|
|
113
|
+
lines.append(f'theme = "{theme_name}"')
|
|
114
|
+
return "\n".join(lines) + "\n"
|
|
115
|
+
|
|
116
|
+
suffix = "\n" if config_text.endswith("\n") or not config_text else "\n\n"
|
|
117
|
+
return f'{config_text}{suffix}[ui]\ntheme = "{theme_name}"\n'
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def write_ghostty_config(config_path: Path, theme_name: str) -> None:
|
|
121
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
config_path.write_text(f"theme = {theme_name}\n", encoding="utf-8")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def write_kon_theme(config_path: Path, theme_name: str) -> None:
|
|
126
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
original = config_path.read_text(encoding="utf-8") if config_path.exists() else ""
|
|
128
|
+
updated = replace_or_append_theme_line(original, theme_name)
|
|
129
|
+
config_path.write_text(updated, encoding="utf-8")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def sh_quote(value: str) -> str:
|
|
133
|
+
return "'" + value.replace("'", "'\"'\"'") + "'"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def launch_preview(ghostty_app: Path, project_dir: Path, duration: float) -> None:
|
|
137
|
+
trap_cmd = (
|
|
138
|
+
'trap \'test -n "$kon_pid" && kill -TERM "$kon_pid" 2>/dev/null || true\' EXIT INT TERM'
|
|
139
|
+
)
|
|
140
|
+
shell_script = (
|
|
141
|
+
f"cd {sh_quote(str(project_dir))} && "
|
|
142
|
+
"kon_pid='' && "
|
|
143
|
+
f"{trap_cmd} && "
|
|
144
|
+
"uv run kon -c & kon_pid=$! && "
|
|
145
|
+
f"sleep {duration} && "
|
|
146
|
+
'kill -TERM "$kon_pid" 2>/dev/null || true && '
|
|
147
|
+
'wait "$kon_pid" 2>/dev/null || true'
|
|
148
|
+
)
|
|
149
|
+
subprocess.run(
|
|
150
|
+
[
|
|
151
|
+
"open",
|
|
152
|
+
"-na",
|
|
153
|
+
str(ghostty_app),
|
|
154
|
+
"--args",
|
|
155
|
+
f"--working-directory={project_dir}",
|
|
156
|
+
"-e",
|
|
157
|
+
"sh",
|
|
158
|
+
"-lc",
|
|
159
|
+
shell_script,
|
|
160
|
+
],
|
|
161
|
+
check=True,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def main() -> int:
|
|
166
|
+
args = parse_args()
|
|
167
|
+
project_dir = args.project_dir.expanduser().resolve()
|
|
168
|
+
ghostty_config_path = args.ghostty_config.expanduser()
|
|
169
|
+
kon_config_path = args.kon_config.expanduser()
|
|
170
|
+
ghostty_app = args.ghostty_app.expanduser()
|
|
171
|
+
|
|
172
|
+
if not project_dir.exists():
|
|
173
|
+
raise FileNotFoundError(f"Project directory does not exist: {project_dir}")
|
|
174
|
+
if not ghostty_app.exists():
|
|
175
|
+
raise FileNotFoundError(f"Ghostty app not found: {ghostty_app}")
|
|
176
|
+
|
|
177
|
+
available_themes = list_ghostty_themes()
|
|
178
|
+
ensure_theme_mapping_is_valid(available_themes)
|
|
179
|
+
|
|
180
|
+
original_ghostty_config = (
|
|
181
|
+
ghostty_config_path.read_text(encoding="utf-8") if ghostty_config_path.exists() else None
|
|
182
|
+
)
|
|
183
|
+
original_kon_config = (
|
|
184
|
+
kon_config_path.read_text(encoding="utf-8") if kon_config_path.exists() else None
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
for kon_theme, ghostty_theme in KON_THEME_TO_GHOSTTY_THEME.items():
|
|
189
|
+
print(f"Previewing kon={kon_theme} ghostty={ghostty_theme}")
|
|
190
|
+
write_kon_theme(kon_config_path, kon_theme)
|
|
191
|
+
write_ghostty_config(ghostty_config_path, ghostty_theme)
|
|
192
|
+
launch_preview(ghostty_app, project_dir, args.duration)
|
|
193
|
+
time.sleep(args.duration + args.pause + 2)
|
|
194
|
+
finally:
|
|
195
|
+
if original_kon_config is None:
|
|
196
|
+
if kon_config_path.exists():
|
|
197
|
+
kon_config_path.unlink()
|
|
198
|
+
else:
|
|
199
|
+
kon_config_path.write_text(original_kon_config, encoding="utf-8")
|
|
200
|
+
|
|
201
|
+
if original_ghostty_config is None:
|
|
202
|
+
if ghostty_config_path.exists():
|
|
203
|
+
ghostty_config_path.unlink()
|
|
204
|
+
else:
|
|
205
|
+
ghostty_config_path.write_text(original_ghostty_config, encoding="utf-8")
|
|
206
|
+
|
|
207
|
+
print("Done. Restored Ghostty and kon config files.")
|
|
208
|
+
return 0
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
if __name__ == "__main__":
|
|
212
|
+
try:
|
|
213
|
+
raise SystemExit(main())
|
|
214
|
+
except KeyboardInterrupt as exc:
|
|
215
|
+
print("Interrupted.", file=sys.stderr)
|
|
216
|
+
raise SystemExit(130) from exc
|
|
@@ -42,6 +42,9 @@ class MetaConfig(BaseModel):
|
|
|
42
42
|
|
|
43
43
|
class UIConfig(BaseModel):
|
|
44
44
|
theme: str = "gruvbox-dark"
|
|
45
|
+
# When true, finalized thinking blocks are collapsed to a single line summary.
|
|
46
|
+
# Set to false to always show the full thinking content.
|
|
47
|
+
collapse_thinking: bool = True
|
|
45
48
|
|
|
46
49
|
@field_validator("theme")
|
|
47
50
|
@classmethod
|
|
@@ -218,8 +218,12 @@ def load_skills(cwd: str | None = None) -> LoadSkillsResult:
|
|
|
218
218
|
else:
|
|
219
219
|
skill_map[skill.name] = skill
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
local_skills_dir = (resolved_cwd / CONFIG_DIR_NAME / "skills").resolve(strict=False)
|
|
222
|
+
global_skills_dir = (get_config_dir() / "skills").resolve(strict=False)
|
|
223
|
+
|
|
224
|
+
add_skills(_load_skills_from_dir(local_skills_dir))
|
|
225
|
+
if global_skills_dir != local_skills_dir:
|
|
226
|
+
add_skills(_load_skills_from_dir(global_skills_dir))
|
|
223
227
|
|
|
224
228
|
return LoadSkillsResult(skills=list(skill_map.values()), warnings=all_warnings)
|
|
225
229
|
|
|
@@ -53,6 +53,9 @@ extra = ["web_search", "web_fetch"]
|
|
|
53
53
|
|
|
54
54
|
[ui]
|
|
55
55
|
theme = "gruvbox-dark"
|
|
56
|
+
# When true, finalized thinking blocks are collapsed to a single line summary.
|
|
57
|
+
# Set to false to always show the full thinking content.
|
|
58
|
+
collapse_thinking = true
|
|
56
59
|
|
|
57
60
|
[permissions]
|
|
58
61
|
# Approval behavior for mutating tools.
|
|
@@ -83,6 +83,10 @@ def shorten_path(path: str) -> str:
|
|
|
83
83
|
return path
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def truncate_text(text: str, n: int = 80) -> str:
|
|
87
|
+
return text[:77] + "..." if len(text) > n else text
|
|
88
|
+
|
|
89
|
+
|
|
86
90
|
def truncate_lines_by_bytes(
|
|
87
91
|
lines: list[str], max_output_bytes: int, marker: str = "[output truncated]"
|
|
88
92
|
) -> tuple[str, bool]:
|
|
@@ -131,14 +131,6 @@ def generate_diff(
|
|
|
131
131
|
return "\n".join(output), added, removed
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
def truncate_diff_line(line: str, max_chars: int = 105) -> str:
|
|
135
|
-
if len(line) <= max_chars:
|
|
136
|
-
return line
|
|
137
|
-
if max_chars <= 3:
|
|
138
|
-
return "." * max_chars
|
|
139
|
-
return f"{line[: max_chars - 3]}..."
|
|
140
|
-
|
|
141
|
-
|
|
142
134
|
def format_diff_display(diff: str) -> str:
|
|
143
135
|
colors = config.ui.colors
|
|
144
136
|
lines = diff.split("\n")
|
|
@@ -148,7 +140,7 @@ def format_diff_display(diff: str) -> str:
|
|
|
148
140
|
if not line:
|
|
149
141
|
continue
|
|
150
142
|
|
|
151
|
-
truncated =
|
|
143
|
+
truncated = line[:102] + "..." if len(line) > 105 else line
|
|
152
144
|
escaped = truncated.replace("[", "\\[")
|
|
153
145
|
|
|
154
146
|
if line.startswith("-"):
|
|
@@ -5,7 +5,13 @@ from pydantic import BaseModel, Field
|
|
|
5
5
|
|
|
6
6
|
from ..core.types import ToolResult
|
|
7
7
|
from ..tools_manager import ensure_tool
|
|
8
|
-
from ._tool_utils import
|
|
8
|
+
from ._tool_utils import (
|
|
9
|
+
ToolCancelledError,
|
|
10
|
+
communicate_or_cancel,
|
|
11
|
+
shorten_path,
|
|
12
|
+
truncate_lines_by_bytes,
|
|
13
|
+
truncate_text,
|
|
14
|
+
)
|
|
9
15
|
from .base import BaseTool
|
|
10
16
|
|
|
11
17
|
MAX_RESULTS = 100
|
|
@@ -37,8 +43,9 @@ class FindTool(BaseTool):
|
|
|
37
43
|
pattern = params.pattern.replace('"', '\\"')
|
|
38
44
|
parts = [f'"{pattern}"']
|
|
39
45
|
if params.path:
|
|
40
|
-
parts.append(f"in {params.path}")
|
|
41
|
-
|
|
46
|
+
parts.append(f"in {shorten_path(params.path)}")
|
|
47
|
+
message = " ".join(parts)
|
|
48
|
+
return truncate_text(message)
|
|
42
49
|
|
|
43
50
|
async def execute(
|
|
44
51
|
self, params: FindParams, cancel_event: asyncio.Event | None = None
|
|
@@ -6,7 +6,13 @@ from pydantic import BaseModel, Field
|
|
|
6
6
|
|
|
7
7
|
from ..core.types import ToolResult
|
|
8
8
|
from ..tools_manager import ensure_tool
|
|
9
|
-
from ._tool_utils import
|
|
9
|
+
from ._tool_utils import (
|
|
10
|
+
ToolCancelledError,
|
|
11
|
+
communicate_or_cancel,
|
|
12
|
+
shorten_path,
|
|
13
|
+
truncate_lines_by_bytes,
|
|
14
|
+
truncate_text,
|
|
15
|
+
)
|
|
10
16
|
from .base import BaseTool
|
|
11
17
|
|
|
12
18
|
MAX_MATCHES = 100
|
|
@@ -40,10 +46,11 @@ class GrepTool(BaseTool):
|
|
|
40
46
|
pattern = params.pattern.replace('"', '\\"')
|
|
41
47
|
parts = [f'"{pattern}"']
|
|
42
48
|
if params.path:
|
|
43
|
-
parts.append(f"in {params.path}")
|
|
49
|
+
parts.append(f"in {shorten_path(params.path)}")
|
|
44
50
|
if params.include:
|
|
45
|
-
parts.append(f"
|
|
46
|
-
|
|
51
|
+
parts.append(f"({params.include})")
|
|
52
|
+
message = " ".join(parts)
|
|
53
|
+
return truncate_text(message)
|
|
47
54
|
|
|
48
55
|
async def execute(
|
|
49
56
|
self, params: GrepParams, cancel_event: asyncio.Event | None = None
|
|
@@ -6,7 +6,7 @@ from trafilatura import extract, fetch_url
|
|
|
6
6
|
from trafilatura.settings import DEFAULT_CONFIG
|
|
7
7
|
|
|
8
8
|
from ..core.types import ToolResult
|
|
9
|
-
from ._tool_utils import ToolCancelledError, await_task_or_cancel
|
|
9
|
+
from ._tool_utils import ToolCancelledError, await_task_or_cancel, truncate_text
|
|
10
10
|
from .base import BaseTool
|
|
11
11
|
|
|
12
12
|
MAX_CHARS = 80_000
|
|
@@ -33,8 +33,7 @@ class WebFetchTool(BaseTool):
|
|
|
33
33
|
)
|
|
34
34
|
|
|
35
35
|
def format_call(self, params: WebFetchParams) -> str:
|
|
36
|
-
|
|
37
|
-
return url[:77] + "..." if len(url) > 80 else url
|
|
36
|
+
return truncate_text(params.url)
|
|
38
37
|
|
|
39
38
|
async def execute(
|
|
40
39
|
self, params: WebFetchParams, cancel_event: asyncio.Event | None = None
|
|
@@ -90,7 +90,7 @@ _CHANGELOG_URL = "https://github.com/0xku/kon/blob/main/CHANGELOG.md"
|
|
|
90
90
|
try:
|
|
91
91
|
VERSION = version(_PYPI_PACKAGE_NAME)
|
|
92
92
|
except PackageNotFoundError:
|
|
93
|
-
VERSION = "0.3.
|
|
93
|
+
VERSION = "0.3.2"
|
|
94
94
|
|
|
95
95
|
_COPILOT_API_TYPES: frozenset[ApiType] = frozenset(
|
|
96
96
|
{ApiType.GITHUB_COPILOT, ApiType.GITHUB_COPILOT_RESPONSES, ApiType.ANTHROPIC_COPILOT}
|
|
@@ -83,7 +83,7 @@ class ThinkingBlock(_StreamingMarkdownMixin, Static):
|
|
|
83
83
|
self.add_class("thinking-block")
|
|
84
84
|
|
|
85
85
|
def compose(self) -> ComposeResult:
|
|
86
|
-
if self._finalized and self._content:
|
|
86
|
+
if self._finalized and self._content and config.ui.collapse_thinking:
|
|
87
87
|
yield Label(self._format_collapsed(), id="thinking-content", markup=False)
|
|
88
88
|
else:
|
|
89
89
|
yield Label(self._content, id="thinking-content", markup=False)
|
|
@@ -114,13 +114,16 @@ class ThinkingBlock(_StreamingMarkdownMixin, Static):
|
|
|
114
114
|
self.call_after_refresh(self._do_finalize)
|
|
115
115
|
|
|
116
116
|
def _do_finalize(self) -> None:
|
|
117
|
-
if self._content:
|
|
117
|
+
if self._content and config.ui.collapse_thinking:
|
|
118
118
|
self.label.update(self._format_collapsed())
|
|
119
119
|
|
|
120
120
|
def set_content(self, text: str) -> None:
|
|
121
121
|
self._content = text
|
|
122
122
|
self._finalized = True
|
|
123
|
-
|
|
123
|
+
if config.ui.collapse_thinking:
|
|
124
|
+
self.label.update(self._format_collapsed())
|
|
125
|
+
else:
|
|
126
|
+
self.label.update(text)
|
|
124
127
|
|
|
125
128
|
|
|
126
129
|
class ContentBlock(_StreamingMarkdownMixin, Static):
|
|
@@ -175,6 +175,8 @@ Extra tools:
|
|
|
175
175
|
parts = [m.provider]
|
|
176
176
|
if not m.supports_images:
|
|
177
177
|
parts.append("[no-vision]")
|
|
178
|
+
if m.id == self._model and m.provider == self._model_provider:
|
|
179
|
+
parts.append("✓")
|
|
178
180
|
caption = " ".join(parts)
|
|
179
181
|
items.append(ListItem(value=m, label=m.id, description=caption))
|
|
180
182
|
|
|
@@ -199,8 +201,13 @@ Extra tools:
|
|
|
199
201
|
chat.add_info_message(str(e), error=True)
|
|
200
202
|
return
|
|
201
203
|
|
|
204
|
+
current_theme = config.ui.theme
|
|
202
205
|
items = [
|
|
203
|
-
ListItem(
|
|
206
|
+
ListItem(
|
|
207
|
+
value=theme_id,
|
|
208
|
+
label=label,
|
|
209
|
+
description=f"{theme_id} ✓" if theme_id == current_theme else theme_id,
|
|
210
|
+
)
|
|
204
211
|
for theme_id, label in get_theme_options()
|
|
205
212
|
]
|
|
206
213
|
|
|
@@ -218,7 +225,10 @@ Extra tools:
|
|
|
218
225
|
set_theme(theme_id)
|
|
219
226
|
self._apply_theme(theme_id)
|
|
220
227
|
chat = self.query_one("#chat-log", ChatLog)
|
|
221
|
-
chat.add_info_message(
|
|
228
|
+
chat.add_info_message(
|
|
229
|
+
f"Theme changed to {theme_id}. Full theme refresh applies when kon is restarted.",
|
|
230
|
+
warning=True,
|
|
231
|
+
)
|
|
222
232
|
|
|
223
233
|
def _select_model(self, model) -> None:
|
|
224
234
|
chat = self.query_one("#chat-log", ChatLog)
|
|
@@ -324,6 +324,25 @@ description: Global version
|
|
|
324
324
|
)
|
|
325
325
|
assert collision.message == expected
|
|
326
326
|
|
|
327
|
+
def test_skips_duplicate_global_dir_when_cwd_is_home(self, tmp_path, monkeypatch):
|
|
328
|
+
home_dir = tmp_path / "home"
|
|
329
|
+
skill_dir = home_dir / ".kon" / "skills" / "noc"
|
|
330
|
+
skill_dir.mkdir(parents=True)
|
|
331
|
+
(skill_dir / "SKILL.md").write_text("""---
|
|
332
|
+
name: noc
|
|
333
|
+
description: Planning-only mode
|
|
334
|
+
---
|
|
335
|
+
""")
|
|
336
|
+
|
|
337
|
+
monkeypatch.setattr("kon.context.skills.get_config_dir", lambda: home_dir / ".kon")
|
|
338
|
+
|
|
339
|
+
result = load_skills(str(home_dir))
|
|
340
|
+
|
|
341
|
+
assert len(result.skills) == 1
|
|
342
|
+
assert result.skills[0].name == "noc"
|
|
343
|
+
assert result.skills[0].path == str(skill_dir / "SKILL.md")
|
|
344
|
+
assert result.warnings == []
|
|
345
|
+
|
|
327
346
|
def test_empty_when_no_skill_directories(self, tmp_path, monkeypatch):
|
|
328
347
|
repo = tmp_path / "repo"
|
|
329
348
|
repo.mkdir()
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
from kon import config
|
|
2
|
-
from kon.tools.edit import format_diff_display
|
|
2
|
+
from kon.tools.edit import format_diff_display
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
def
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
def test_format_diff_display_short_lines_not_truncated() -> None:
|
|
6
|
+
short = "+2 short line"
|
|
7
|
+
display = format_diff_display(short)
|
|
8
|
+
assert "..." not in display
|
|
9
|
+
assert "short line" in display
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
def
|
|
11
|
-
line = "+2 " + "x" * 200
|
|
12
|
-
truncated = truncate_diff_line(line, max_chars=90)
|
|
13
|
-
assert len(truncated) == 90
|
|
14
|
-
assert truncated.endswith("...")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def test_format_diff_display_truncates_and_keeps_color_markup() -> None:
|
|
12
|
+
def test_format_diff_display_truncates_long_lines() -> None:
|
|
18
13
|
long_added = "+2 " + "x" * 200
|
|
19
14
|
long_removed = "-2 " + "y" * 200
|
|
20
15
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/.kon/skills/kon-tmux-test/run-e2e-tests.sh
RENAMED
|
File without changes
|
{kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/.kon/skills/kon-tmux-test/setup-test-project.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/copilot_anthropic.py
RENAMED
|
File without changes
|
{kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/github_copilot_headers.py
RENAMED
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/openai_codex_responses.py
RENAMED
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/src/kon/llm/providers/openai_completions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/llm/test_azure_ai_foundry_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/llm/test_openai_codex_provider_errors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kon_coding_agent-0.3.1 → kon_coding_agent-0.3.2}/tests/tools/test_read_image_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|