pisces-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pisces/__init__.py +9 -0
- pisces/__main__.py +4 -0
- pisces/ast_visitor.py +347 -0
- pisces/cli.py +245 -0
- pisces/config_cmd.py +234 -0
- pisces/config_manager.py +208 -0
- pisces/context_manager.py +333 -0
- pisces/explain_cmd.py +608 -0
- pisces/fix_cmd.py +479 -0
- pisces/history_cmd.py +313 -0
- pisces/init_cmd.py +193 -0
- pisces/judge.py +131 -0
- pisces/llm_client.py +263 -0
- pisces/models.py +413 -0
- pisces/patch_engine.py +266 -0
- pisces/scan_cmd.py +230 -0
- pisces/scanner.py +677 -0
- pisces/tui.py +539 -0
- pisces/watcher.py +244 -0
- pisces_cli-0.1.0.dist-info/METADATA +448 -0
- pisces_cli-0.1.0.dist-info/RECORD +24 -0
- pisces_cli-0.1.0.dist-info/WHEEL +4 -0
- pisces_cli-0.1.0.dist-info/entry_points.txt +2 -0
- pisces_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
pisces/config_cmd.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pisces.config_cmd
|
|
3
|
+
~~~~~~~~~~~~~~~~~
|
|
4
|
+
Implements `pisces config` — interactive configuration wizard.
|
|
5
|
+
|
|
6
|
+
What it does:
|
|
7
|
+
- Shows current configuration (with --show)
|
|
8
|
+
- Interactively updates provider, model, repair, validation settings
|
|
9
|
+
- Validates that the chosen provider has a key available in the environment
|
|
10
|
+
- Never reads or writes API keys to pisces.toml
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from rich import box
|
|
18
|
+
from rich.prompt import Confirm, Prompt
|
|
19
|
+
from rich.table import Table
|
|
20
|
+
|
|
21
|
+
from pisces.config_manager import (
|
|
22
|
+
PROVIDER_DEFAULTS,
|
|
23
|
+
SUPPORTED_PROVIDERS,
|
|
24
|
+
PiscesConfig,
|
|
25
|
+
check_api_key_available,
|
|
26
|
+
load_config,
|
|
27
|
+
write_config,
|
|
28
|
+
)
|
|
29
|
+
from pisces.tui import (
|
|
30
|
+
console,
|
|
31
|
+
print_error,
|
|
32
|
+
print_section,
|
|
33
|
+
print_step_ok,
|
|
34
|
+
print_success,
|
|
35
|
+
print_warning,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# ─────────────────────────────────────────────────────────────
|
|
39
|
+
# Entry point
|
|
40
|
+
# ─────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def run_config(project_root: Path, *, show_only: bool = False) -> int:
|
|
44
|
+
"""
|
|
45
|
+
Entry point for `pisces config`.
|
|
46
|
+
|
|
47
|
+
Returns 0 on success, 1 on error.
|
|
48
|
+
"""
|
|
49
|
+
print_section("pisces config")
|
|
50
|
+
|
|
51
|
+
# Load existing config (requires pisces init to have been run)
|
|
52
|
+
try:
|
|
53
|
+
cfg = load_config(project_root)
|
|
54
|
+
except RuntimeError as e:
|
|
55
|
+
print_error(str(e), hint="Run 'pisces init' first.")
|
|
56
|
+
return 1
|
|
57
|
+
except ValueError as e:
|
|
58
|
+
print_error(f"Invalid configuration: {e}")
|
|
59
|
+
return 1
|
|
60
|
+
|
|
61
|
+
if show_only:
|
|
62
|
+
_print_config_table(cfg, project_root)
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
# Interactive wizard
|
|
66
|
+
_print_config_table(cfg, project_root)
|
|
67
|
+
console.print()
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
cfg = _run_wizard(cfg)
|
|
71
|
+
except KeyboardInterrupt:
|
|
72
|
+
console.print("\n[muted]Configuration cancelled.[/]")
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
# Save
|
|
76
|
+
try:
|
|
77
|
+
write_config(project_root, cfg)
|
|
78
|
+
except OSError as e:
|
|
79
|
+
print_error(f"Could not save configuration: {e}")
|
|
80
|
+
return 1
|
|
81
|
+
|
|
82
|
+
print_success("✓ Configuration saved to pisces.toml")
|
|
83
|
+
|
|
84
|
+
# Key check
|
|
85
|
+
_check_provider_key(cfg.llm.provider)
|
|
86
|
+
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ─────────────────────────────────────────────────────────────
|
|
91
|
+
# Current config display table
|
|
92
|
+
# ─────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _print_config_table(cfg: PiscesConfig, project_root: Path) -> None:
|
|
96
|
+
"""Print a rich table showing the current configuration."""
|
|
97
|
+
table = Table(
|
|
98
|
+
title="[bold]Current Configuration[/]",
|
|
99
|
+
title_justify="left",
|
|
100
|
+
box=box.ROUNDED,
|
|
101
|
+
border_style="blue",
|
|
102
|
+
header_style="bold cyan",
|
|
103
|
+
show_lines=False,
|
|
104
|
+
padding=(0, 2),
|
|
105
|
+
)
|
|
106
|
+
table.add_column("Setting", style="dim", min_width=22)
|
|
107
|
+
table.add_column("Value", style="white", min_width=30)
|
|
108
|
+
table.add_column("Source", style="muted", min_width=16)
|
|
109
|
+
|
|
110
|
+
# API key status (env var check)
|
|
111
|
+
has_key = check_api_key_available(cfg.llm.provider)
|
|
112
|
+
key_status = "[success]✓ Found in env[/]" if has_key else "[high]✗ Not set[/]"
|
|
113
|
+
|
|
114
|
+
table.add_row("llm.provider", f"[magenta]{cfg.llm.provider}[/]", "pisces.toml")
|
|
115
|
+
table.add_row("llm.model", f"[cyan]{cfg.llm.model}[/]", "pisces.toml")
|
|
116
|
+
table.add_row("API key", key_status, "env var")
|
|
117
|
+
table.add_row("repair.max_attempts", str(cfg.repair.max_attempts), "pisces.toml")
|
|
118
|
+
table.add_row(
|
|
119
|
+
"repair.require_approval",
|
|
120
|
+
"[success]true[/]" if cfg.repair.require_approval else "[high]false[/]",
|
|
121
|
+
"pisces.toml",
|
|
122
|
+
)
|
|
123
|
+
table.add_row(
|
|
124
|
+
"validation.test_command", f"[code]{cfg.validation.test_command}[/]", "pisces.toml"
|
|
125
|
+
)
|
|
126
|
+
table.add_row("Config file", str(project_root / "pisces.toml"), "—")
|
|
127
|
+
|
|
128
|
+
console.print()
|
|
129
|
+
console.print(table)
|
|
130
|
+
console.print()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ─────────────────────────────────────────────────────────────
|
|
134
|
+
# Interactive wizard
|
|
135
|
+
# ─────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _run_wizard(cfg: PiscesConfig) -> PiscesConfig:
|
|
139
|
+
"""Run an interactive prompt-based config wizard. Returns updated cfg."""
|
|
140
|
+
console.print("[bold]Configure pisces[/] [dim](<press Enter to keep current value>)[/]")
|
|
141
|
+
console.print()
|
|
142
|
+
|
|
143
|
+
# ── LLM Provider ────────────────────────────────────────
|
|
144
|
+
console.print(f"[dim]Supported providers:[/] {', '.join(SUPPORTED_PROVIDERS)}")
|
|
145
|
+
provider = (
|
|
146
|
+
Prompt.ask(
|
|
147
|
+
" [bold magenta]LLM provider[/]",
|
|
148
|
+
default=cfg.llm.provider,
|
|
149
|
+
console=console,
|
|
150
|
+
)
|
|
151
|
+
.lower()
|
|
152
|
+
.strip()
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if provider not in SUPPORTED_PROVIDERS:
|
|
156
|
+
print_warning(f"Unknown provider '{provider}'. Keeping current: {cfg.llm.provider}")
|
|
157
|
+
provider = cfg.llm.provider
|
|
158
|
+
|
|
159
|
+
# ── Model ────────────────────────────────────────────────
|
|
160
|
+
default_model = PROVIDER_DEFAULTS.get(provider, cfg.llm.model)
|
|
161
|
+
if provider != cfg.llm.provider:
|
|
162
|
+
# Provider changed → suggest new provider's default model
|
|
163
|
+
console.print(f" [dim]Default model for {provider}:[/] [cyan]{default_model}[/]")
|
|
164
|
+
|
|
165
|
+
model = Prompt.ask(
|
|
166
|
+
" [bold cyan]Model name[/]",
|
|
167
|
+
default=cfg.llm.model if provider == cfg.llm.provider else default_model,
|
|
168
|
+
console=console,
|
|
169
|
+
).strip()
|
|
170
|
+
|
|
171
|
+
# ── Max repair attempts ──────────────────────────────────
|
|
172
|
+
attempts_str = Prompt.ask(
|
|
173
|
+
" [bold]Max self-healing attempts[/]",
|
|
174
|
+
default=str(cfg.repair.max_attempts),
|
|
175
|
+
console=console,
|
|
176
|
+
).strip()
|
|
177
|
+
try:
|
|
178
|
+
max_attempts = int(attempts_str)
|
|
179
|
+
if max_attempts < 1:
|
|
180
|
+
raise ValueError
|
|
181
|
+
except ValueError:
|
|
182
|
+
print_warning(f"Invalid value '{attempts_str}'. Keeping: {cfg.repair.max_attempts}")
|
|
183
|
+
max_attempts = cfg.repair.max_attempts
|
|
184
|
+
|
|
185
|
+
# ── Require HITL approval ────────────────────────────────
|
|
186
|
+
console.print()
|
|
187
|
+
console.print(" [dim]Human approval (HITL) means pisces will ask before modifying source.[/]")
|
|
188
|
+
require_approval = Confirm.ask(
|
|
189
|
+
" [bold]Require human approval before patching?[/]",
|
|
190
|
+
default=cfg.repair.require_approval,
|
|
191
|
+
console=console,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# ── Test command ─────────────────────────────────────────
|
|
195
|
+
console.print()
|
|
196
|
+
test_command = Prompt.ask(
|
|
197
|
+
" [bold]Test command[/] [dim](<used to validate patches>)[/]",
|
|
198
|
+
default=cfg.validation.test_command,
|
|
199
|
+
console=console,
|
|
200
|
+
).strip()
|
|
201
|
+
|
|
202
|
+
# ── Apply ────────────────────────────────────────────────
|
|
203
|
+
cfg.llm.provider = provider
|
|
204
|
+
cfg.llm.model = model
|
|
205
|
+
cfg.repair.max_attempts = max_attempts
|
|
206
|
+
cfg.repair.require_approval = require_approval
|
|
207
|
+
cfg.validation.test_command = test_command
|
|
208
|
+
|
|
209
|
+
return cfg
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# ─────────────────────────────────────────────────────────────
|
|
213
|
+
# API key availability check (advisory only)
|
|
214
|
+
# ─────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _check_provider_key(provider: str) -> None:
|
|
218
|
+
"""Print a reminder if the provider's API key is not set."""
|
|
219
|
+
has_key = check_api_key_available(provider)
|
|
220
|
+
if has_key:
|
|
221
|
+
print_step_ok(f"API key for '{provider}' found in environment ✓")
|
|
222
|
+
else:
|
|
223
|
+
# Local providers don't need keys
|
|
224
|
+
if provider in ("ollama", "lmstudio"):
|
|
225
|
+
print_step_ok(f"'{provider}' is a local provider — no API key needed ✓")
|
|
226
|
+
else:
|
|
227
|
+
from pisces.config_manager import _PROVIDER_ENV_VARS
|
|
228
|
+
|
|
229
|
+
env_var = _PROVIDER_ENV_VARS.get(provider, ["?"])[0]
|
|
230
|
+
print_warning(
|
|
231
|
+
f"API key for '{provider}' not found.\n"
|
|
232
|
+
f" Set it before running 'pisces fix':\n"
|
|
233
|
+
f" export {env_var}=your_key_here"
|
|
234
|
+
)
|
pisces/config_manager.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""loads, merges, and validates pisces configuration
|
|
2
|
+
# pisces.toml should not and never contain sensitive config(eg: API key or maybe cerdentials)
|
|
3
|
+
# it may contain things like say provider name, model, settings or models
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
if sys.version_info >= (3, 11):
|
|
15
|
+
import tomllib
|
|
16
|
+
else:
|
|
17
|
+
try:
|
|
18
|
+
import tomli as tomllib
|
|
19
|
+
except ImportError as e:
|
|
20
|
+
raise ImportError("Install 'tomli' for Python < 3.11: pip install tomli") from e
|
|
21
|
+
|
|
22
|
+
"""Environment Variable names for each of the provider"""
|
|
23
|
+
_PROVIDER_ENV_VARS: dict[str, list[str]] = {
|
|
24
|
+
"openai": ["OPENAI_API_KEY"],
|
|
25
|
+
"anthropic": ["ANTHROPIC_API_KEY"],
|
|
26
|
+
"gemini": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
27
|
+
"groq": ["GROQ_API_KEY"],
|
|
28
|
+
"ollama": [], # key not needed cos local
|
|
29
|
+
"lmstudio": [], # key not needed cos local
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Supported provider and their default models
|
|
33
|
+
PROVIDER_DEFAULTS: dict[str, str] = {
|
|
34
|
+
"openai": "gpt-4o-mini",
|
|
35
|
+
"anthropic": "claude-3-5-haiku-20241022",
|
|
36
|
+
"gemini": "gemini-2.0-flash",
|
|
37
|
+
"groq": "llama-3.3-70b-versatile",
|
|
38
|
+
"ollama": "llama3.2",
|
|
39
|
+
"lmstudio": "local-model",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
SUPPORTED_PROVIDERS = list(PROVIDER_DEFAULTS.keys())
|
|
43
|
+
|
|
44
|
+
"""Config Dataclasses"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class LLMConfig:
|
|
49
|
+
provider: str = "groq"
|
|
50
|
+
model: str = "llama-3.3-70b-versatile"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class RepairConfig:
|
|
55
|
+
max_attempts: int = 3
|
|
56
|
+
require_approval: bool = True # HITL always onn by default
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class ValidationConfig:
|
|
61
|
+
test_command: str = "pytest"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class ScanConfig:
|
|
66
|
+
exclude: list[str] = field(
|
|
67
|
+
default_factory=lambda: [
|
|
68
|
+
".git",
|
|
69
|
+
".venv",
|
|
70
|
+
"venv",
|
|
71
|
+
"__pycache__",
|
|
72
|
+
"node_modules",
|
|
73
|
+
".pisces",
|
|
74
|
+
"dist",
|
|
75
|
+
"build",
|
|
76
|
+
]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class PiscesConfig:
|
|
82
|
+
llm: LLMConfig = field(default_factory=LLMConfig)
|
|
83
|
+
repair: RepairConfig = field(default_factory=RepairConfig)
|
|
84
|
+
validation: ValidationConfig = field(default_factory=ValidationConfig)
|
|
85
|
+
scan: ScanConfig = field(default_factory=ScanConfig)
|
|
86
|
+
|
|
87
|
+
_project_root: Path | None = field(default=None, repr=False, compare=False)
|
|
88
|
+
|
|
89
|
+
"""Loader"""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def load_config(project_root: Path) -> PiscesConfig:
|
|
93
|
+
"""
|
|
94
|
+
Load configuration from *project_root*/pisces.toml.
|
|
95
|
+
Falls back to defaults for any missing keys.
|
|
96
|
+
Raises RuntimeError if pisces has not been initialized.
|
|
97
|
+
"""
|
|
98
|
+
toml_path = project_root / "pisces.toml"
|
|
99
|
+
|
|
100
|
+
if not toml_path.exists():
|
|
101
|
+
raise RuntimeError(f"pisces.toml not found in {project_root}\nRun 'pisces init' first.")
|
|
102
|
+
with toml_path.open("rb") as f:
|
|
103
|
+
raw: dict[str, Any] = tomllib.load(f)
|
|
104
|
+
|
|
105
|
+
cfg = PiscesConfig()
|
|
106
|
+
cfg._project_root = project_root
|
|
107
|
+
|
|
108
|
+
# [llm]
|
|
109
|
+
llm = raw.get("llm", {})
|
|
110
|
+
if "provider" in llm:
|
|
111
|
+
cfg.llm.provider = llm["provider"].lower().strip()
|
|
112
|
+
if "model" in llm:
|
|
113
|
+
cfg.llm.model = llm["model"].strip()
|
|
114
|
+
# [repair]
|
|
115
|
+
repair = raw.get("repair", {})
|
|
116
|
+
if "max_attempts" in repair:
|
|
117
|
+
cfg.repair.max_attempts = int(repair["max_attempts"])
|
|
118
|
+
if "require_approval" in repair:
|
|
119
|
+
cfg.repair.require_approval = bool(repair["require_approval"])
|
|
120
|
+
# [validation]
|
|
121
|
+
validation = raw.get("validation", {})
|
|
122
|
+
if "test_command" in validation:
|
|
123
|
+
cfg.validation.test_command = validation["test_command"].strip()
|
|
124
|
+
# [scan]
|
|
125
|
+
scan = raw.get("scan", {})
|
|
126
|
+
if "exclude" in scan:
|
|
127
|
+
cfg.scan.exclude = [str(e) for e in scan["exclude"]]
|
|
128
|
+
_validate_config(cfg)
|
|
129
|
+
return cfg
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _validate_config(cfg: PiscesConfig) -> None:
|
|
133
|
+
"""Raise ValueError for clearly invalid config values."""
|
|
134
|
+
if cfg.llm.provider not in SUPPORTED_PROVIDERS:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
f"Unsupported provider '{cfg.llm.provider}'. "
|
|
137
|
+
f"Choose from: {', '.join(SUPPORTED_PROVIDERS)}"
|
|
138
|
+
)
|
|
139
|
+
if cfg.repair.max_attempts < 1:
|
|
140
|
+
raise ValueError("repair.max_attempts must be >= 1")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ─────────────────────────────────────────────────────────────
|
|
144
|
+
# Credential lookup — env vars ONLY
|
|
145
|
+
# ─────────────────────────────────────────────────────────────
|
|
146
|
+
def get_api_key(provider: str) -> str | None:
|
|
147
|
+
"""
|
|
148
|
+
Return the API key for *provider* from environment variables.
|
|
149
|
+
Returns None for local providers (ollama, lmstudio) that need no key.
|
|
150
|
+
Raises EnvironmentError if the key is required but missing.
|
|
151
|
+
"""
|
|
152
|
+
provider = provider.lower()
|
|
153
|
+
env_vars = _PROVIDER_ENV_VARS.get(provider, [])
|
|
154
|
+
if not env_vars:
|
|
155
|
+
return None # local provider — no key needed
|
|
156
|
+
for var in env_vars:
|
|
157
|
+
value = os.environ.get(var, "").strip()
|
|
158
|
+
if value:
|
|
159
|
+
return value
|
|
160
|
+
raise OSError(
|
|
161
|
+
f"No API key found for provider '{provider}'.\n"
|
|
162
|
+
f"Set one of these environment variables: {', '.join(env_vars)}\n\n"
|
|
163
|
+
f"Example (Linux/macOS/WSL):\n"
|
|
164
|
+
f" export {env_vars[0]}=your_key_here\n\n"
|
|
165
|
+
f"Example (Windows CMD):\n"
|
|
166
|
+
f" set {env_vars[0]}=your_key_here\n\n"
|
|
167
|
+
f"Example (Windows PowerShell):\n"
|
|
168
|
+
f" $env:{env_vars[0]}='your_key_here'"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def check_api_key_available(provider: str) -> bool:
|
|
173
|
+
"""Return True if an API key is available (or not required)."""
|
|
174
|
+
try:
|
|
175
|
+
get_api_key(provider)
|
|
176
|
+
return True
|
|
177
|
+
except OSError:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ─────────────────────────────────────────────────────────────
|
|
182
|
+
# Config writer (non-sensitive fields only)
|
|
183
|
+
# ─────────────────────────────────────────────────────────────
|
|
184
|
+
def write_config(project_root: Path, cfg: PiscesConfig) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Write *cfg* to pisces.toml.
|
|
187
|
+
NEVER writes credentials. Only writes provider name, model, and settings.
|
|
188
|
+
"""
|
|
189
|
+
toml_path = project_root / "pisces.toml"
|
|
190
|
+
# Build exclude list as TOML inline array
|
|
191
|
+
excludes = ", ".join(f'"{e}"' for e in cfg.scan.exclude)
|
|
192
|
+
content = f"""\
|
|
193
|
+
# pisces project configuration
|
|
194
|
+
# API keys MUST come from environment variables — never put them here.
|
|
195
|
+
[llm]
|
|
196
|
+
provider = "{cfg.llm.provider}"
|
|
197
|
+
model = "{cfg.llm.model}"
|
|
198
|
+
[repair]
|
|
199
|
+
max_attempts = {cfg.repair.max_attempts}
|
|
200
|
+
require_approval = {"true" if cfg.repair.require_approval else "false"}
|
|
201
|
+
[validation]
|
|
202
|
+
test_command = "{cfg.validation.test_command}"
|
|
203
|
+
[scan]
|
|
204
|
+
exclude = [
|
|
205
|
+
{excludes}
|
|
206
|
+
]
|
|
207
|
+
"""
|
|
208
|
+
toml_path.write_text(content, encoding="utf-8")
|