deepy-cli 0.1.4__tar.gz → 0.1.5__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.
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/PKG-INFO +25 -7
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/README.md +24 -6
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/pyproject.toml +1 -1
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/__init__.py +1 -1
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/cli.py +73 -35
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/config/__init__.py +18 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/config/settings.py +115 -0
- deepy_cli-0.1.5/src/deepy/ui/markdown.py +346 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/message_view.py +46 -39
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/prompt_input.py +22 -12
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/slash_commands.py +2 -0
- deepy_cli-0.1.5/src/deepy/ui/styles.py +154 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/terminal.py +278 -53
- deepy_cli-0.1.5/src/deepy/ui/theme_picker.py +116 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/welcome.py +35 -21
- deepy_cli-0.1.4/src/deepy/ui/markdown.py +0 -152
- deepy_cli-0.1.4/src/deepy/ui/styles.py +0 -21
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/__main__.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/data/__init__.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/data/tools/AskUserQuestion.md +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/data/tools/WebFetch.md +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/data/tools/WebSearch.md +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/data/tools/__init__.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/data/tools/bash.md +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/data/tools/edit.md +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/data/tools/modify.md +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/data/tools/read.md +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/data/tools/write.md +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/errors.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/llm/__init__.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/llm/agent.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/llm/context.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/llm/events.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/llm/model_capabilities.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/llm/provider.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/llm/replay.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/llm/runner.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/llm/thinking.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/prompts/__init__.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/prompts/compact.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/prompts/rules.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/prompts/runtime_context.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/prompts/system.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/prompts/tool_docs.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/sessions/__init__.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/sessions/jsonl.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/sessions/manager.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/skills.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/status.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/tools/__init__.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/tools/agents.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/tools/builtin.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/tools/file_state.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/tools/result.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/tools/shell_utils.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/__init__.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/app.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/ask_user_question.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/exit_summary.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/loading_text.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/prompt_buffer.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/session_list.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/session_picker.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/ui/thinking_state.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/update_check.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/usage.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/utils/__init__.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/utils/debug_logger.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/utils/error_logger.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/utils/json.py +0 -0
- {deepy_cli-0.1.4 → deepy_cli-0.1.5}/src/deepy/utils/notify.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: deepy-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Deepy - Vibe coding for DeepSeek models in your terminal
|
|
5
5
|
Keywords: deepseek,coding-agent,terminal,cli,agents
|
|
6
6
|
Author: kirineko
|
|
@@ -71,8 +71,8 @@ context state visible while the agent works.
|
|
|
71
71
|
- Session history, `/resume`, `/new`, automatic context tracking, and compacting
|
|
72
72
|
for long project work.
|
|
73
73
|
- TOML-only private configuration at `~/.deepy/config.toml`.
|
|
74
|
-
-
|
|
75
|
-
context window status, and version update checks.
|
|
74
|
+
- Theme-aware terminal UI with Markdown rendering, DeepSeek thinking display,
|
|
75
|
+
per-turn usage, context window status, and version update checks.
|
|
76
76
|
|
|
77
77
|
## See It Work
|
|
78
78
|
|
|
@@ -137,11 +137,17 @@ Deepy only uses TOML configuration. JSON config files are intentionally rejected
|
|
|
137
137
|
|
|
138
138
|
```toml
|
|
139
139
|
# ~/.deepy/config.toml
|
|
140
|
+
[model]
|
|
140
141
|
api_key = "sk-..."
|
|
141
|
-
|
|
142
|
+
name = "deepseek-v4-pro"
|
|
142
143
|
base_url = "https://api.deepseek.com"
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
|
|
145
|
+
[context]
|
|
146
|
+
window_tokens = 1048576
|
|
147
|
+
compact_trigger_ratio = 0.8
|
|
148
|
+
|
|
149
|
+
[ui]
|
|
150
|
+
theme = "auto" # auto, dark, or light
|
|
145
151
|
```
|
|
146
152
|
|
|
147
153
|
WebSearch uses Deepy's hosted SearXNG endpoint by default. You can override it
|
|
@@ -158,11 +164,21 @@ You can also initialize config non-interactively:
|
|
|
158
164
|
deepy config init --api-key sk-... --model deepseek-v4-pro
|
|
159
165
|
```
|
|
160
166
|
|
|
167
|
+
If your terminal uses a light background and parts of the UI look low contrast,
|
|
168
|
+
set the UI theme explicitly:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
deepy config theme light
|
|
172
|
+
```
|
|
173
|
+
|
|
161
174
|
## Common Commands
|
|
162
175
|
|
|
163
176
|
```bash
|
|
164
177
|
deepy --version
|
|
165
178
|
deepy config setup
|
|
179
|
+
deepy config reset
|
|
180
|
+
deepy config theme
|
|
181
|
+
deepy config theme light
|
|
166
182
|
deepy doctor
|
|
167
183
|
deepy doctor --live --json
|
|
168
184
|
deepy status
|
|
@@ -178,6 +194,8 @@ Inside the interactive terminal:
|
|
|
178
194
|
/skills List available skills
|
|
179
195
|
/new Start a fresh conversation
|
|
180
196
|
/resume Pick a previous session
|
|
197
|
+
/theme Show or change UI theme
|
|
198
|
+
/reset Delete config and run setup again
|
|
181
199
|
/ Open the command menu
|
|
182
200
|
Esc Interrupt the current model turn
|
|
183
201
|
Ctrl+D Press twice to quit
|
|
@@ -208,6 +226,6 @@ assets live outside the package directory and are not included in the wheel.
|
|
|
208
226
|
|
|
209
227
|
## Release Status
|
|
210
228
|
|
|
211
|
-
Deepy is preparing its first public `0.1.
|
|
229
|
+
Deepy is preparing its first public `0.1.5` release. The current release path is
|
|
212
230
|
GitHub + PyPI. Standalone binaries and npm wrappers can be added later, but the
|
|
213
231
|
primary distribution is the Python CLI.
|
|
@@ -43,8 +43,8 @@ context state visible while the agent works.
|
|
|
43
43
|
- Session history, `/resume`, `/new`, automatic context tracking, and compacting
|
|
44
44
|
for long project work.
|
|
45
45
|
- TOML-only private configuration at `~/.deepy/config.toml`.
|
|
46
|
-
-
|
|
47
|
-
context window status, and version update checks.
|
|
46
|
+
- Theme-aware terminal UI with Markdown rendering, DeepSeek thinking display,
|
|
47
|
+
per-turn usage, context window status, and version update checks.
|
|
48
48
|
|
|
49
49
|
## See It Work
|
|
50
50
|
|
|
@@ -109,11 +109,17 @@ Deepy only uses TOML configuration. JSON config files are intentionally rejected
|
|
|
109
109
|
|
|
110
110
|
```toml
|
|
111
111
|
# ~/.deepy/config.toml
|
|
112
|
+
[model]
|
|
112
113
|
api_key = "sk-..."
|
|
113
|
-
|
|
114
|
+
name = "deepseek-v4-pro"
|
|
114
115
|
base_url = "https://api.deepseek.com"
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
|
|
117
|
+
[context]
|
|
118
|
+
window_tokens = 1048576
|
|
119
|
+
compact_trigger_ratio = 0.8
|
|
120
|
+
|
|
121
|
+
[ui]
|
|
122
|
+
theme = "auto" # auto, dark, or light
|
|
117
123
|
```
|
|
118
124
|
|
|
119
125
|
WebSearch uses Deepy's hosted SearXNG endpoint by default. You can override it
|
|
@@ -130,11 +136,21 @@ You can also initialize config non-interactively:
|
|
|
130
136
|
deepy config init --api-key sk-... --model deepseek-v4-pro
|
|
131
137
|
```
|
|
132
138
|
|
|
139
|
+
If your terminal uses a light background and parts of the UI look low contrast,
|
|
140
|
+
set the UI theme explicitly:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
deepy config theme light
|
|
144
|
+
```
|
|
145
|
+
|
|
133
146
|
## Common Commands
|
|
134
147
|
|
|
135
148
|
```bash
|
|
136
149
|
deepy --version
|
|
137
150
|
deepy config setup
|
|
151
|
+
deepy config reset
|
|
152
|
+
deepy config theme
|
|
153
|
+
deepy config theme light
|
|
138
154
|
deepy doctor
|
|
139
155
|
deepy doctor --live --json
|
|
140
156
|
deepy status
|
|
@@ -150,6 +166,8 @@ Inside the interactive terminal:
|
|
|
150
166
|
/skills List available skills
|
|
151
167
|
/new Start a fresh conversation
|
|
152
168
|
/resume Pick a previous session
|
|
169
|
+
/theme Show or change UI theme
|
|
170
|
+
/reset Delete config and run setup again
|
|
153
171
|
/ Open the command menu
|
|
154
172
|
Esc Interrupt the current model turn
|
|
155
173
|
Ctrl+D Press twice to quit
|
|
@@ -180,6 +198,6 @@ assets live outside the package directory and are not included in the wheel.
|
|
|
180
198
|
|
|
181
199
|
## Release Status
|
|
182
200
|
|
|
183
|
-
Deepy is preparing its first public `0.1.
|
|
201
|
+
Deepy is preparing its first public `0.1.5` release. The current release path is
|
|
184
202
|
GitHub + PyPI. Standalone binaries and npm wrappers can be added later, but the
|
|
185
203
|
primary distribution is the Python CLI.
|
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
5
|
-
import os
|
|
6
5
|
import sys
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from typing import Sequence
|
|
@@ -10,8 +9,21 @@ from typing import Sequence
|
|
|
10
9
|
import tomli_w
|
|
11
10
|
|
|
12
11
|
from . import __version__
|
|
13
|
-
from .config import
|
|
14
|
-
|
|
12
|
+
from .config import (
|
|
13
|
+
Settings,
|
|
14
|
+
load_settings,
|
|
15
|
+
settings_to_toml_dict,
|
|
16
|
+
ui_theme_from_selection,
|
|
17
|
+
ui_theme_number,
|
|
18
|
+
update_config_theme,
|
|
19
|
+
write_config,
|
|
20
|
+
)
|
|
21
|
+
from .config.settings import (
|
|
22
|
+
DEFAULT_BASE_URL,
|
|
23
|
+
DEFAULT_MODEL,
|
|
24
|
+
DEFAULT_UI_THEME,
|
|
25
|
+
UI_THEMES,
|
|
26
|
+
)
|
|
15
27
|
from .errors import format_error_display
|
|
16
28
|
from .llm.provider import build_provider_bundle
|
|
17
29
|
from .llm.runner import DEFAULT_MAX_TURNS, run_prompt_once
|
|
@@ -20,6 +32,7 @@ from .skills import discover_skills, find_skill, format_skills_for_terminal, rea
|
|
|
20
32
|
from .status import build_status_report, format_status_report, status_report_to_dict
|
|
21
33
|
from .usage import TokenUsage, format_usage_line, usage_from_run_result
|
|
22
34
|
from .ui import run_interactive
|
|
35
|
+
from .ui.styles import resolve_ui_palette
|
|
23
36
|
from .utils import json as json_utils
|
|
24
37
|
|
|
25
38
|
|
|
@@ -42,9 +55,13 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
42
55
|
init_parser.add_argument("--api-key", help="DeepSeek API key.")
|
|
43
56
|
init_parser.add_argument("--model", default=DEFAULT_MODEL, help="Model name.")
|
|
44
57
|
init_parser.add_argument("--base-url", default=DEFAULT_BASE_URL, help="OpenAI-compatible base URL.")
|
|
58
|
+
init_parser.add_argument("--theme", default=DEFAULT_UI_THEME, help="UI theme: auto, dark, or light.")
|
|
45
59
|
init_parser.add_argument("--force", action="store_true", help="Overwrite existing config.")
|
|
46
60
|
setup_parser = config_sub.add_parser("setup", help="Interactively configure Deepy.")
|
|
47
61
|
setup_parser.add_argument("--force", action="store_true", help="Overwrite existing config.")
|
|
62
|
+
config_sub.add_parser("reset", help="Delete local config and run interactive setup again.")
|
|
63
|
+
theme_parser = config_sub.add_parser("theme", help="Show or update terminal UI theme.")
|
|
64
|
+
theme_parser.add_argument("theme", nargs="?", help="Theme to save: auto, dark, or light.")
|
|
48
65
|
|
|
49
66
|
doctor_parser = subparsers.add_parser("doctor", help="Validate local Deepy setup.")
|
|
50
67
|
doctor_parser.add_argument("--json", action="store_true", help="Print JSON diagnostics.")
|
|
@@ -101,6 +118,7 @@ def _cmd_config_init(args: argparse.Namespace) -> int:
|
|
|
101
118
|
api_key=args.api_key or "",
|
|
102
119
|
model=args.model,
|
|
103
120
|
base_url=args.base_url,
|
|
121
|
+
theme=args.theme,
|
|
104
122
|
)
|
|
105
123
|
print(f"Wrote {config_path}")
|
|
106
124
|
return 0
|
|
@@ -110,7 +128,7 @@ def _cmd_config_setup(args: argparse.Namespace) -> int:
|
|
|
110
128
|
config_path = args.config.expanduser() if args.config else Path.home() / ".deepy" / "config.toml"
|
|
111
129
|
if config_path.suffix == ".json":
|
|
112
130
|
raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
|
|
113
|
-
if config_path.exists()
|
|
131
|
+
if config_path.exists():
|
|
114
132
|
existing = load_settings(config_path)
|
|
115
133
|
else:
|
|
116
134
|
existing = Settings(path=config_path)
|
|
@@ -119,11 +137,26 @@ def _cmd_config_setup(args: argparse.Namespace) -> int:
|
|
|
119
137
|
api_key = _prompt_config_value("API key", default=existing.model.api_key or "", is_password=True)
|
|
120
138
|
model = _prompt_config_value("Model", default=existing.model.name)
|
|
121
139
|
base_url = _prompt_config_value("Base URL", default=existing.model.base_url)
|
|
122
|
-
|
|
140
|
+
theme = _prompt_theme_value(default=existing.ui.theme)
|
|
141
|
+
_write_config(config_path, api_key=api_key, model=model, base_url=base_url, theme=theme)
|
|
123
142
|
print(f"Wrote {config_path}")
|
|
124
143
|
return 0
|
|
125
144
|
|
|
126
145
|
|
|
146
|
+
def _cmd_config_reset(args: argparse.Namespace) -> int:
|
|
147
|
+
config_path = args.config.expanduser() if args.config else Path.home() / ".deepy" / "config.toml"
|
|
148
|
+
if config_path.suffix == ".json":
|
|
149
|
+
raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
|
|
150
|
+
if config_path.exists():
|
|
151
|
+
config_path.unlink()
|
|
152
|
+
print(f"Removed {config_path}")
|
|
153
|
+
else:
|
|
154
|
+
print(f"No existing config at {config_path}")
|
|
155
|
+
print("Starting Deepy configuration setup...")
|
|
156
|
+
setup_args = argparse.Namespace(config=args.config, force=True)
|
|
157
|
+
return _cmd_config_setup(setup_args)
|
|
158
|
+
|
|
159
|
+
|
|
127
160
|
def _prompt_config_value(label: str, *, default: str, is_password: bool = False) -> str:
|
|
128
161
|
from prompt_toolkit import PromptSession
|
|
129
162
|
|
|
@@ -136,36 +169,33 @@ def _prompt_config_value(label: str, *, default: str, is_password: bool = False)
|
|
|
136
169
|
return value or default
|
|
137
170
|
|
|
138
171
|
|
|
139
|
-
def
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
"
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
"
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
167
|
-
config_path.write_text(tomli_w.dumps(payload), encoding="utf-8")
|
|
168
|
-
os.chmod(config_path, 0o600)
|
|
172
|
+
def _prompt_theme_value(*, default: str = DEFAULT_UI_THEME) -> str:
|
|
173
|
+
print("UI theme:")
|
|
174
|
+
print("1. auto Detect when possible; falls back to dark")
|
|
175
|
+
print("2. dark Optimized for dark terminal backgrounds")
|
|
176
|
+
print("3. light Optimized for light terminal backgrounds")
|
|
177
|
+
value = _prompt_config_value("UI theme number", default=ui_theme_number(default))
|
|
178
|
+
return ui_theme_from_selection(value, default=default)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _write_config(config_path: Path, *, api_key: str, model: str, base_url: str, theme: str) -> None:
|
|
182
|
+
write_config(config_path, api_key=api_key, model=model, base_url=base_url, theme=theme)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _cmd_config_theme(args: argparse.Namespace) -> int:
|
|
186
|
+
settings = load_settings(args.config)
|
|
187
|
+
if args.theme is None:
|
|
188
|
+
palette = resolve_ui_palette(settings.ui.theme)
|
|
189
|
+
print(f"saved: {settings.ui.theme}")
|
|
190
|
+
print(f"resolved: {palette.name}")
|
|
191
|
+
return 0
|
|
192
|
+
if args.theme not in UI_THEMES:
|
|
193
|
+
print("Invalid theme. Usage: deepy config theme [auto|dark|light]", file=sys.stderr)
|
|
194
|
+
return 1
|
|
195
|
+
config_path = settings.path or (args.config.expanduser() if args.config else Path.home() / ".deepy" / "config.toml")
|
|
196
|
+
update_config_theme(config_path, args.theme)
|
|
197
|
+
print(f"Saved UI theme: {args.theme}")
|
|
198
|
+
return 0
|
|
169
199
|
|
|
170
200
|
|
|
171
201
|
def _doctor(args: argparse.Namespace) -> tuple[int, dict[str, object]]:
|
|
@@ -385,6 +415,10 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
385
415
|
return _cmd_config_init(args)
|
|
386
416
|
if args.config_command == "setup":
|
|
387
417
|
return _cmd_config_setup(args)
|
|
418
|
+
if args.config_command == "reset":
|
|
419
|
+
return _cmd_config_reset(args)
|
|
420
|
+
if args.config_command == "theme":
|
|
421
|
+
return _cmd_config_theme(args)
|
|
388
422
|
if args.command == "doctor":
|
|
389
423
|
return _cmd_doctor(args)
|
|
390
424
|
if args.command == "run":
|
|
@@ -404,6 +438,10 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
404
438
|
setup_args = argparse.Namespace(config=args.config, force=True)
|
|
405
439
|
_cmd_config_setup(setup_args)
|
|
406
440
|
settings = load_settings(args.config)
|
|
441
|
+
if settings.path is not None and not settings.ui.theme_configured:
|
|
442
|
+
theme = _prompt_theme_value(default=settings.ui.theme)
|
|
443
|
+
update_config_theme(settings.path, theme)
|
|
444
|
+
settings = load_settings(args.config)
|
|
407
445
|
return run_interactive(settings)
|
|
408
446
|
|
|
409
447
|
|
|
@@ -2,22 +2,40 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from .settings import (
|
|
4
4
|
ContextConfig,
|
|
5
|
+
DEFAULT_UI_THEME,
|
|
5
6
|
DEFAULT_WEB_SEARCH_SEARXNG_URL,
|
|
6
7
|
ModelConfig,
|
|
7
8
|
Settings,
|
|
9
|
+
UI_THEME_OPTIONS,
|
|
10
|
+
UI_THEMES,
|
|
11
|
+
UiConfig,
|
|
8
12
|
default_config_path,
|
|
13
|
+
is_valid_ui_theme,
|
|
9
14
|
load_settings,
|
|
10
15
|
mask_secret,
|
|
11
16
|
settings_to_toml_dict,
|
|
17
|
+
update_config_theme,
|
|
18
|
+
ui_theme_from_selection,
|
|
19
|
+
ui_theme_number,
|
|
20
|
+
write_config,
|
|
12
21
|
)
|
|
13
22
|
|
|
14
23
|
__all__ = [
|
|
15
24
|
"ContextConfig",
|
|
25
|
+
"DEFAULT_UI_THEME",
|
|
16
26
|
"DEFAULT_WEB_SEARCH_SEARXNG_URL",
|
|
17
27
|
"ModelConfig",
|
|
18
28
|
"Settings",
|
|
29
|
+
"UI_THEME_OPTIONS",
|
|
30
|
+
"UI_THEMES",
|
|
31
|
+
"UiConfig",
|
|
19
32
|
"default_config_path",
|
|
33
|
+
"is_valid_ui_theme",
|
|
20
34
|
"load_settings",
|
|
21
35
|
"mask_secret",
|
|
22
36
|
"settings_to_toml_dict",
|
|
37
|
+
"update_config_theme",
|
|
38
|
+
"ui_theme_from_selection",
|
|
39
|
+
"ui_theme_number",
|
|
40
|
+
"write_config",
|
|
23
41
|
]
|
|
@@ -6,13 +6,18 @@ from dataclasses import asdict, dataclass, field
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any, Mapping, Self
|
|
8
8
|
|
|
9
|
+
import tomli_w
|
|
10
|
+
|
|
9
11
|
DEFAULT_MODEL = "deepseek-v4-pro"
|
|
10
12
|
DEFAULT_BASE_URL = "https://api.deepseek.com"
|
|
11
13
|
DEFAULT_CONTEXT_WINDOW_TOKENS = 1_048_576
|
|
12
14
|
DEFAULT_COMPACT_TRIGGER_RATIO = 0.8
|
|
13
15
|
DEFAULT_COMPACT_PROMPT_TOKEN_THRESHOLD = 838_861
|
|
14
16
|
DEFAULT_WEB_SEARCH_SEARXNG_URL = "https://s.kirineko.tech/"
|
|
17
|
+
DEFAULT_UI_THEME = "auto"
|
|
15
18
|
REASONING_EFFORTS = {"high", "max"}
|
|
19
|
+
UI_THEMES = {"auto", "dark", "light"}
|
|
20
|
+
UI_THEME_OPTIONS = (("1", "auto"), ("2", "dark"), ("3", "light"))
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
def default_config_path() -> Path:
|
|
@@ -161,6 +166,19 @@ class ToolsConfig:
|
|
|
161
166
|
return cls(web_search=WebSearchToolConfig.from_mapping(_as_mapping(raw.get("web_search"))))
|
|
162
167
|
|
|
163
168
|
|
|
169
|
+
@dataclass(frozen=True)
|
|
170
|
+
class UiConfig:
|
|
171
|
+
theme: str = DEFAULT_UI_THEME
|
|
172
|
+
theme_configured: bool = False
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def from_mapping(cls, raw: Mapping[str, Any]) -> Self:
|
|
176
|
+
theme = raw.get("theme")
|
|
177
|
+
if isinstance(theme, str) and theme.strip() in UI_THEMES:
|
|
178
|
+
return cls(theme=theme.strip(), theme_configured=True)
|
|
179
|
+
return cls()
|
|
180
|
+
|
|
181
|
+
|
|
164
182
|
@dataclass(frozen=True)
|
|
165
183
|
class Settings:
|
|
166
184
|
model: ModelConfig = field(default_factory=ModelConfig)
|
|
@@ -168,6 +186,7 @@ class Settings:
|
|
|
168
186
|
logging: LoggingConfig = field(default_factory=LoggingConfig)
|
|
169
187
|
notify: NotifyConfig = field(default_factory=NotifyConfig)
|
|
170
188
|
tools: ToolsConfig = field(default_factory=ToolsConfig)
|
|
189
|
+
ui: UiConfig = field(default_factory=UiConfig)
|
|
171
190
|
path: Path | None = None
|
|
172
191
|
|
|
173
192
|
@classmethod
|
|
@@ -184,6 +203,7 @@ class Settings:
|
|
|
184
203
|
logging=LoggingConfig.from_mapping(_as_mapping(raw.get("logging"))),
|
|
185
204
|
notify=NotifyConfig.from_mapping(_as_mapping(raw.get("notify"))),
|
|
186
205
|
tools=ToolsConfig.from_mapping(_as_mapping(raw.get("tools"))),
|
|
206
|
+
ui=UiConfig.from_mapping(_as_mapping(raw.get("ui"))),
|
|
187
207
|
path=path,
|
|
188
208
|
)
|
|
189
209
|
|
|
@@ -208,6 +228,8 @@ def load_settings(
|
|
|
208
228
|
def settings_to_toml_dict(settings: Settings, *, reveal_secret: bool = False) -> dict[str, Any]:
|
|
209
229
|
data = _drop_empty(asdict(settings))
|
|
210
230
|
data.pop("path", None)
|
|
231
|
+
if "ui" in data:
|
|
232
|
+
data["ui"].pop("theme_configured", None)
|
|
211
233
|
api_key = settings.model.api_key
|
|
212
234
|
if api_key:
|
|
213
235
|
data["model"]["api_key"] = api_key if reveal_secret else mask_secret(api_key)
|
|
@@ -218,6 +240,99 @@ def settings_to_toml_dict(settings: Settings, *, reveal_secret: bool = False) ->
|
|
|
218
240
|
return _drop_empty(data)
|
|
219
241
|
|
|
220
242
|
|
|
243
|
+
def is_valid_ui_theme(value: str) -> bool:
|
|
244
|
+
return value in UI_THEMES
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def ui_theme_number(theme: str) -> str:
|
|
248
|
+
for number, value in UI_THEME_OPTIONS:
|
|
249
|
+
if value == theme:
|
|
250
|
+
return number
|
|
251
|
+
return "1"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def ui_theme_from_selection(value: str, *, default: str = DEFAULT_UI_THEME) -> str:
|
|
255
|
+
normalized = value.strip().lower()
|
|
256
|
+
if not normalized:
|
|
257
|
+
return default if is_valid_ui_theme(default) else DEFAULT_UI_THEME
|
|
258
|
+
if normalized in UI_THEMES:
|
|
259
|
+
return normalized
|
|
260
|
+
by_number = dict(UI_THEME_OPTIONS)
|
|
261
|
+
selected = by_number.get(normalized)
|
|
262
|
+
if selected is not None:
|
|
263
|
+
return selected
|
|
264
|
+
return default if is_valid_ui_theme(default) else DEFAULT_UI_THEME
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def write_config(
|
|
268
|
+
config_path: Path,
|
|
269
|
+
*,
|
|
270
|
+
api_key: str,
|
|
271
|
+
model: str,
|
|
272
|
+
base_url: str,
|
|
273
|
+
theme: str,
|
|
274
|
+
) -> None:
|
|
275
|
+
if not is_valid_ui_theme(theme):
|
|
276
|
+
raise ValueError("UI theme must be one of: auto, dark, light.")
|
|
277
|
+
path = config_path.expanduser()
|
|
278
|
+
if path.suffix == ".json":
|
|
279
|
+
raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
|
|
280
|
+
payload = {
|
|
281
|
+
"model": {
|
|
282
|
+
"name": model,
|
|
283
|
+
"base_url": base_url,
|
|
284
|
+
"api_key": api_key,
|
|
285
|
+
"thinking": True,
|
|
286
|
+
"reasoning_effort": "max",
|
|
287
|
+
},
|
|
288
|
+
"context": {
|
|
289
|
+
"window_tokens": DEFAULT_CONTEXT_WINDOW_TOKENS,
|
|
290
|
+
"compact_trigger_ratio": DEFAULT_COMPACT_TRIGGER_RATIO,
|
|
291
|
+
"compact_prompt_token_threshold": DEFAULT_COMPACT_PROMPT_TOKEN_THRESHOLD,
|
|
292
|
+
},
|
|
293
|
+
"logging": {
|
|
294
|
+
"debug": False,
|
|
295
|
+
},
|
|
296
|
+
"notify": {
|
|
297
|
+
"enabled": False,
|
|
298
|
+
"command": "",
|
|
299
|
+
},
|
|
300
|
+
"tools": {
|
|
301
|
+
"web_search": {
|
|
302
|
+
"searxng_url": DEFAULT_WEB_SEARCH_SEARXNG_URL,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
"ui": {
|
|
306
|
+
"theme": theme,
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
310
|
+
path.write_text(tomli_w.dumps(payload), encoding="utf-8")
|
|
311
|
+
os.chmod(path, 0o600)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def update_config_theme(config_path: Path, theme: str) -> None:
|
|
315
|
+
if not is_valid_ui_theme(theme):
|
|
316
|
+
raise ValueError("UI theme must be one of: auto, dark, light.")
|
|
317
|
+
path = config_path.expanduser()
|
|
318
|
+
if path.suffix == ".json":
|
|
319
|
+
raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
|
|
320
|
+
raw: dict[str, Any]
|
|
321
|
+
if path.exists():
|
|
322
|
+
with path.open("rb") as fh:
|
|
323
|
+
loaded = tomllib.load(fh)
|
|
324
|
+
raw = dict(loaded)
|
|
325
|
+
else:
|
|
326
|
+
raw = {}
|
|
327
|
+
ui = raw.get("ui")
|
|
328
|
+
ui_map = dict(ui) if isinstance(ui, Mapping) else {}
|
|
329
|
+
ui_map["theme"] = theme
|
|
330
|
+
raw["ui"] = ui_map
|
|
331
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
332
|
+
path.write_text(tomli_w.dumps(raw), encoding="utf-8")
|
|
333
|
+
os.chmod(path, 0o600)
|
|
334
|
+
|
|
335
|
+
|
|
221
336
|
def _drop_empty(value: Any) -> Any:
|
|
222
337
|
if isinstance(value, dict):
|
|
223
338
|
result = {}
|