diffsage 1.2.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.
- diffsage/__init__.py +0 -0
- diffsage/__main__.py +4 -0
- diffsage/cli.py +38 -0
- diffsage/commands/__init__.py +0 -0
- diffsage/commands/ask.py +62 -0
- diffsage/commands/auth.py +153 -0
- diffsage/commands/commit.py +125 -0
- diffsage/commands/config.py +216 -0
- diffsage/commands/doctor.py +18 -0
- diffsage/config/__init__.py +0 -0
- diffsage/config/defaults.py +20 -0
- diffsage/config/file_loader.py +21 -0
- diffsage/config/loader.py +8 -0
- diffsage/config/paths.py +61 -0
- diffsage/config/resolver.py +118 -0
- diffsage/config/schema.py +53 -0
- diffsage/config/scope.py +8 -0
- diffsage/config/settings.py +9 -0
- diffsage/exceptions/__init__.py +34 -0
- diffsage/exceptions/base.py +32 -0
- diffsage/exceptions/credentials.py +8 -0
- diffsage/exceptions/git.py +9 -0
- diffsage/exceptions/provider.py +17 -0
- diffsage/git/__init__.py +0 -0
- diffsage/git/client.py +155 -0
- diffsage/logging/__init__.py +0 -0
- diffsage/logging/formatter.py +0 -0
- diffsage/logging/logger.py +41 -0
- diffsage/models/__init__.py +0 -0
- diffsage/models/commit_message.py +9 -0
- diffsage/models/config.py +16 -0
- diffsage/models/credentials.py +8 -0
- diffsage/models/doctor.py +13 -0
- diffsage/models/git.py +32 -0
- diffsage/models/provider.py +20 -0
- diffsage/parsers/commit_message_parser.py +43 -0
- diffsage/prompts/__init__.py +0 -0
- diffsage/providers/__init__.py +0 -0
- diffsage/providers/base.py +8 -0
- diffsage/providers/factory.py +14 -0
- diffsage/providers/gemini_provider.py +77 -0
- diffsage/services/__init__.py +0 -0
- diffsage/services/ai_service.py +63 -0
- diffsage/services/commit_service.py +38 -0
- diffsage/services/config_service.py +109 -0
- diffsage/services/credentials_service.py +61 -0
- diffsage/services/doctor_service.py +47 -0
- diffsage/services/editor_service.py +75 -0
- diffsage/services/git_service.py +20 -0
- diffsage/services/prompt_service.py +60 -0
- diffsage/storage/__init__.py +0 -0
- diffsage/storage/base.py +0 -0
- diffsage/storage/config_repository.py +85 -0
- diffsage/storage/credentials_repository.py +121 -0
- diffsage/ui/__init__.py +0 -0
- diffsage/ui/ask_view.py +10 -0
- diffsage/ui/auth_view.py +51 -0
- diffsage/ui/base.py +15 -0
- diffsage/ui/commit_view.py +100 -0
- diffsage/ui/config_view.py +58 -0
- diffsage/ui/doctor_view.py +39 -0
- diffsage-1.2.0.dist-info/METADATA +471 -0
- diffsage-1.2.0.dist-info/RECORD +67 -0
- diffsage-1.2.0.dist-info/WHEEL +5 -0
- diffsage-1.2.0.dist-info/entry_points.txt +2 -0
- diffsage-1.2.0.dist-info/licenses/LICENSE +21 -0
- diffsage-1.2.0.dist-info/top_level.txt +1 -0
diffsage/__init__.py
ADDED
|
File without changes
|
diffsage/__main__.py
ADDED
diffsage/cli.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from diffsage.commands.ask import ask
|
|
6
|
+
from diffsage.commands.auth import app as auth_app
|
|
7
|
+
from diffsage.commands.commit import commit
|
|
8
|
+
from diffsage.commands.config import app as config_app
|
|
9
|
+
from diffsage.commands.doctor import doctor
|
|
10
|
+
from diffsage.config.loader import load_settings
|
|
11
|
+
from diffsage.logging.logger import configure_logging
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(help="DiffSage: AI-aware Git workflow toolkit")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.callback()
|
|
17
|
+
def main() -> None:
|
|
18
|
+
"""
|
|
19
|
+
Initialize DiffSage.
|
|
20
|
+
"""
|
|
21
|
+
settings = load_settings()
|
|
22
|
+
configure_logging(
|
|
23
|
+
getattr(
|
|
24
|
+
logging,
|
|
25
|
+
settings.log_level.upper(),
|
|
26
|
+
logging.INFO,
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
app.command()(doctor)
|
|
32
|
+
app.command()(ask)
|
|
33
|
+
app.command()(commit)
|
|
34
|
+
app.add_typer(
|
|
35
|
+
config_app,
|
|
36
|
+
name="config",
|
|
37
|
+
)
|
|
38
|
+
app.add_typer(auth_app, name="auth")
|
|
File without changes
|
diffsage/commands/ask.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from diffsage.config.loader import load_settings
|
|
2
|
+
from diffsage.config.paths import get_credentials_path
|
|
3
|
+
from diffsage.exceptions import (
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
ConfigError,
|
|
6
|
+
CredentialNotFoundError,
|
|
7
|
+
ProviderUnavailableError,
|
|
8
|
+
)
|
|
9
|
+
from diffsage.logging.logger import get_logger
|
|
10
|
+
from diffsage.services.ai_service import AIService
|
|
11
|
+
from diffsage.services.credentials_service import CredentialService
|
|
12
|
+
from diffsage.storage.credentials_repository import CredentialsRepository
|
|
13
|
+
from diffsage.ui.ask_view import AskView
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def ask(prompt: str) -> None:
|
|
19
|
+
"""Ask the configured AI provider a question"""
|
|
20
|
+
|
|
21
|
+
logger.info("Running ask command")
|
|
22
|
+
view = AskView()
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
settings = load_settings()
|
|
26
|
+
|
|
27
|
+
credential_path = get_credentials_path()
|
|
28
|
+
repository = CredentialsRepository(credential_path)
|
|
29
|
+
credential_service = CredentialService(repository)
|
|
30
|
+
|
|
31
|
+
service = AIService(
|
|
32
|
+
settings,
|
|
33
|
+
credential_service,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
response = service.ask(prompt)
|
|
37
|
+
view.show_response(response)
|
|
38
|
+
|
|
39
|
+
except AuthenticationError as e:
|
|
40
|
+
logger.warning(str(e))
|
|
41
|
+
view.show_error(str(e))
|
|
42
|
+
raise SystemExit(1) from None
|
|
43
|
+
|
|
44
|
+
except CredentialNotFoundError as e:
|
|
45
|
+
logger.warning(str(e))
|
|
46
|
+
view.show_error(str(e))
|
|
47
|
+
raise SystemExit(1) from None
|
|
48
|
+
|
|
49
|
+
except ProviderUnavailableError as e:
|
|
50
|
+
logger.warning(str(e))
|
|
51
|
+
view.show_error(str(e))
|
|
52
|
+
raise SystemExit(1) from None
|
|
53
|
+
|
|
54
|
+
except ConfigError as e:
|
|
55
|
+
logger.warning(str(e))
|
|
56
|
+
view.show_error(str(e))
|
|
57
|
+
raise SystemExit(1) from None
|
|
58
|
+
|
|
59
|
+
except Exception:
|
|
60
|
+
logger.exception("Unexpected error while executing ask command.")
|
|
61
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
62
|
+
raise SystemExit(1) from None
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from diffsage.config.paths import get_credentials_path
|
|
4
|
+
from diffsage.logging.logger import get_logger
|
|
5
|
+
from diffsage.services.credentials_service import CredentialService
|
|
6
|
+
from diffsage.storage.credentials_repository import CredentialsRepository
|
|
7
|
+
from diffsage.ui.auth_view import AuthView
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
help="DiffSage credential management",
|
|
13
|
+
invoke_without_command=False,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command("set")
|
|
18
|
+
def set_credential(
|
|
19
|
+
provider: str,
|
|
20
|
+
api_key: str,
|
|
21
|
+
name: str = typer.Option(
|
|
22
|
+
"default",
|
|
23
|
+
"--name",
|
|
24
|
+
help="Credential profile name.",
|
|
25
|
+
),
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Store a DiffSage provider credential."""
|
|
28
|
+
|
|
29
|
+
view = AuthView()
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
path = get_credentials_path()
|
|
33
|
+
repository = CredentialsRepository(path)
|
|
34
|
+
service = CredentialService(repository)
|
|
35
|
+
|
|
36
|
+
service.set_credential(
|
|
37
|
+
provider,
|
|
38
|
+
api_key,
|
|
39
|
+
name,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
view.show_success("Credential saved.")
|
|
43
|
+
|
|
44
|
+
except ValueError as e:
|
|
45
|
+
logger.warning(str(e))
|
|
46
|
+
view.show_error(str(e))
|
|
47
|
+
raise SystemExit(1) from None
|
|
48
|
+
|
|
49
|
+
except Exception:
|
|
50
|
+
logger.exception("Unexpected error while executing auth command.")
|
|
51
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
52
|
+
raise SystemExit(1) from None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@app.command("get")
|
|
56
|
+
def get_credential(
|
|
57
|
+
provider: str,
|
|
58
|
+
name: str = typer.Option(
|
|
59
|
+
"default",
|
|
60
|
+
"--name",
|
|
61
|
+
help="Credential profile name.",
|
|
62
|
+
),
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Get a DiffSage provider credential."""
|
|
65
|
+
|
|
66
|
+
view = AuthView()
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
path = get_credentials_path()
|
|
70
|
+
repository = CredentialsRepository(path)
|
|
71
|
+
service = CredentialService(repository)
|
|
72
|
+
|
|
73
|
+
credential = service.get_credential(
|
|
74
|
+
provider,
|
|
75
|
+
name,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if credential is None:
|
|
79
|
+
view.show_error(f"Credential not found for provider '{provider}' and profile '{name}'.")
|
|
80
|
+
raise SystemExit(1)
|
|
81
|
+
|
|
82
|
+
view.show_credential(credential)
|
|
83
|
+
|
|
84
|
+
except ValueError as e:
|
|
85
|
+
logger.warning(str(e))
|
|
86
|
+
view.show_error(str(e))
|
|
87
|
+
raise SystemExit(1) from None
|
|
88
|
+
|
|
89
|
+
except Exception:
|
|
90
|
+
logger.exception("Unexpected error while executing auth command.")
|
|
91
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
92
|
+
raise SystemExit(1) from None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.command("list")
|
|
96
|
+
def list_credentials() -> None:
|
|
97
|
+
"""List configured DiffSage credentials."""
|
|
98
|
+
|
|
99
|
+
view = AuthView()
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
path = get_credentials_path()
|
|
103
|
+
repository = CredentialsRepository(path)
|
|
104
|
+
service = CredentialService(repository)
|
|
105
|
+
|
|
106
|
+
credentials = service.list_credentials()
|
|
107
|
+
|
|
108
|
+
view.show_credentials(credentials)
|
|
109
|
+
|
|
110
|
+
except Exception:
|
|
111
|
+
logger.exception("Unexpected error while executing auth command.")
|
|
112
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
113
|
+
raise SystemExit(1) from None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@app.command("unset")
|
|
117
|
+
def unset_credential(
|
|
118
|
+
provider: str,
|
|
119
|
+
name: str = typer.Option(
|
|
120
|
+
"default",
|
|
121
|
+
"--name",
|
|
122
|
+
help="Credential profile name.",
|
|
123
|
+
),
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Unset DiffSage provider credential."""
|
|
126
|
+
|
|
127
|
+
view = AuthView()
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
path = get_credentials_path()
|
|
131
|
+
repository = CredentialsRepository(path)
|
|
132
|
+
service = CredentialService(repository)
|
|
133
|
+
|
|
134
|
+
deleted = service.delete_credential(
|
|
135
|
+
provider,
|
|
136
|
+
name,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if not deleted:
|
|
140
|
+
view.show_error(f"Credential not found for provider '{provider}' and profile '{name}'.")
|
|
141
|
+
raise SystemExit(1)
|
|
142
|
+
|
|
143
|
+
view.show_success("Credential removed.")
|
|
144
|
+
|
|
145
|
+
except ValueError as e:
|
|
146
|
+
logger.warning(str(e))
|
|
147
|
+
view.show_error(str(e))
|
|
148
|
+
raise SystemExit(1) from None
|
|
149
|
+
|
|
150
|
+
except Exception:
|
|
151
|
+
logger.exception("Unexpected error while executing auth command.")
|
|
152
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
153
|
+
raise SystemExit(1) from None
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from diffsage.config.loader import load_settings
|
|
2
|
+
from diffsage.config.paths import get_credentials_path
|
|
3
|
+
from diffsage.exceptions import (
|
|
4
|
+
ConfigError,
|
|
5
|
+
CredentialNotFoundError,
|
|
6
|
+
NoStagedChangesError,
|
|
7
|
+
NotGitRepositoryError,
|
|
8
|
+
ProviderError,
|
|
9
|
+
)
|
|
10
|
+
from diffsage.git.client import GitClient
|
|
11
|
+
from diffsage.logging.logger import get_logger
|
|
12
|
+
from diffsage.parsers.commit_message_parser import CommitMessageParser
|
|
13
|
+
from diffsage.services.ai_service import AIService
|
|
14
|
+
from diffsage.services.commit_service import CommitService
|
|
15
|
+
from diffsage.services.credentials_service import CredentialService
|
|
16
|
+
from diffsage.services.editor_service import EditorService
|
|
17
|
+
from diffsage.services.git_service import GitService
|
|
18
|
+
from diffsage.services.prompt_service import PromptService
|
|
19
|
+
from diffsage.storage.credentials_repository import CredentialsRepository
|
|
20
|
+
from diffsage.ui.commit_view import CommitView
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def generate_message(commit_service: CommitService) -> str:
|
|
26
|
+
return commit_service.generate_commit_message()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def commit() -> None:
|
|
30
|
+
"""Generate a conventional commit message"""
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
git_client = GitClient()
|
|
34
|
+
view = CommitView()
|
|
35
|
+
editor = EditorService()
|
|
36
|
+
|
|
37
|
+
settings = load_settings()
|
|
38
|
+
|
|
39
|
+
credential_path = get_credentials_path()
|
|
40
|
+
credential_repository = CredentialsRepository(credential_path)
|
|
41
|
+
credential_service = CredentialService(credential_repository)
|
|
42
|
+
|
|
43
|
+
git_service = GitService(git_client)
|
|
44
|
+
ai_service = AIService(
|
|
45
|
+
settings,
|
|
46
|
+
credential_service,
|
|
47
|
+
)
|
|
48
|
+
prompt_service = PromptService()
|
|
49
|
+
|
|
50
|
+
commit_service = CommitService(
|
|
51
|
+
git_client,
|
|
52
|
+
git_service,
|
|
53
|
+
prompt_service,
|
|
54
|
+
ai_service,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
with view.generating():
|
|
58
|
+
raw_message = generate_message(commit_service)
|
|
59
|
+
commit_message = CommitMessageParser.parse(raw_message)
|
|
60
|
+
|
|
61
|
+
view.show_generated()
|
|
62
|
+
view.show_commit(commit_message)
|
|
63
|
+
|
|
64
|
+
while True:
|
|
65
|
+
choice = view.prompt_action()
|
|
66
|
+
|
|
67
|
+
if choice in ("", "y"):
|
|
68
|
+
logger.info("User selected commit.")
|
|
69
|
+
git_client.commit(raw_message)
|
|
70
|
+
view.show_success(raw_message)
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
if choice == "e":
|
|
74
|
+
logger.info("User selected edit.")
|
|
75
|
+
raw_message = editor.edit(raw_message)
|
|
76
|
+
commit_message = CommitMessageParser.parse(raw_message)
|
|
77
|
+
view.show_commit(commit_message)
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
if choice == "r":
|
|
81
|
+
logger.info("User selected regenerate.")
|
|
82
|
+
with view.generating():
|
|
83
|
+
raw_message = generate_message(commit_service)
|
|
84
|
+
commit_message = CommitMessageParser.parse(raw_message)
|
|
85
|
+
|
|
86
|
+
view.show_generated()
|
|
87
|
+
view.show_commit(commit_message)
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
if choice == "n":
|
|
91
|
+
logger.info("User cancelled commit.")
|
|
92
|
+
view.show_cancelled()
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
view.show_invalid_option()
|
|
96
|
+
|
|
97
|
+
except NotGitRepositoryError:
|
|
98
|
+
logger.info("Command aborted: not a Git repository.")
|
|
99
|
+
view.show_not_git_repository()
|
|
100
|
+
raise SystemExit(1) from None
|
|
101
|
+
|
|
102
|
+
except NoStagedChangesError:
|
|
103
|
+
logger.info("Command aborted: no staged changes.")
|
|
104
|
+
view.show_no_staged_changes()
|
|
105
|
+
raise SystemExit(1) from None
|
|
106
|
+
|
|
107
|
+
except CredentialNotFoundError as e:
|
|
108
|
+
logger.warning(str(e))
|
|
109
|
+
view.show_error(str(e))
|
|
110
|
+
raise SystemExit(1) from None
|
|
111
|
+
|
|
112
|
+
except ProviderError as e:
|
|
113
|
+
logger.warning(str(e))
|
|
114
|
+
view.show_error(str(e))
|
|
115
|
+
raise SystemExit(1) from None
|
|
116
|
+
|
|
117
|
+
except ConfigError as e:
|
|
118
|
+
logger.warning(str(e))
|
|
119
|
+
view.show_error(str(e))
|
|
120
|
+
raise SystemExit(1) from None
|
|
121
|
+
|
|
122
|
+
except Exception:
|
|
123
|
+
logger.exception("Unexpected error while executing commit command.")
|
|
124
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
125
|
+
raise SystemExit(1) from None
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from diffsage.config.loader import load_settings
|
|
4
|
+
from diffsage.config.resolver import get_local_config_path, resolve_config_path
|
|
5
|
+
from diffsage.config.scope import ConfigScope
|
|
6
|
+
from diffsage.exceptions import InvalidConfigurationValueError, UnknownConfigurationKeyError
|
|
7
|
+
from diffsage.logging.logger import get_logger
|
|
8
|
+
from diffsage.services.config_service import ConfigService
|
|
9
|
+
from diffsage.storage.config_repository import ConfigRepository
|
|
10
|
+
from diffsage.ui.config_view import ConfigView
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="DiffSage configuration", invoke_without_command=False)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _resolve_scope(*, local: bool, global_: bool) -> ConfigScope:
|
|
18
|
+
if local and global_:
|
|
19
|
+
raise typer.BadParameter("Cannot specify both --local and --global.")
|
|
20
|
+
|
|
21
|
+
if local:
|
|
22
|
+
return ConfigScope.LOCAL
|
|
23
|
+
|
|
24
|
+
return ConfigScope.GLOBAL
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command("list")
|
|
28
|
+
def list_config(
|
|
29
|
+
local: bool = typer.Option(False, "--local"), global_: bool = typer.Option(False, "--global")
|
|
30
|
+
) -> None:
|
|
31
|
+
"""List the current DiffSage configuration."""
|
|
32
|
+
|
|
33
|
+
view = ConfigView()
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
settings = load_settings()
|
|
37
|
+
scope = _resolve_scope(
|
|
38
|
+
local=local,
|
|
39
|
+
global_=global_,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if local or global_:
|
|
43
|
+
path = resolve_config_path(scope)
|
|
44
|
+
|
|
45
|
+
repository = ConfigRepository(path)
|
|
46
|
+
service = ConfigService(settings, repository)
|
|
47
|
+
|
|
48
|
+
report = service.get_configuration_raw()
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
path = get_local_config_path()
|
|
52
|
+
|
|
53
|
+
repository = ConfigRepository(path)
|
|
54
|
+
service = ConfigService(settings, repository)
|
|
55
|
+
|
|
56
|
+
report = service.get_configuration()
|
|
57
|
+
|
|
58
|
+
view.show_path(path)
|
|
59
|
+
view.show_configuration(report)
|
|
60
|
+
|
|
61
|
+
except typer.BadParameter as e:
|
|
62
|
+
logger.warning(str(e))
|
|
63
|
+
view.show_error(str(e))
|
|
64
|
+
raise SystemExit(1) from None
|
|
65
|
+
|
|
66
|
+
except Exception:
|
|
67
|
+
logger.exception("Unexpected error while executing config command.")
|
|
68
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
69
|
+
raise SystemExit(1) from None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@app.command("get")
|
|
73
|
+
def get_config(
|
|
74
|
+
key: str,
|
|
75
|
+
local: bool = typer.Option(False, "--local"),
|
|
76
|
+
global_: bool = typer.Option(False, "--global"),
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Get the value of a DiffSage configuration."""
|
|
79
|
+
|
|
80
|
+
view = ConfigView()
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
settings = load_settings()
|
|
84
|
+
|
|
85
|
+
if local or global_:
|
|
86
|
+
scope = _resolve_scope(
|
|
87
|
+
local=local,
|
|
88
|
+
global_=global_,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
path = resolve_config_path(scope)
|
|
92
|
+
repository = ConfigRepository(path)
|
|
93
|
+
service = ConfigService(settings, repository)
|
|
94
|
+
|
|
95
|
+
report = service.get_configuration_value_raw(key.strip().lower())
|
|
96
|
+
|
|
97
|
+
else:
|
|
98
|
+
path = get_local_config_path()
|
|
99
|
+
repository = ConfigRepository(path)
|
|
100
|
+
service = ConfigService(settings, repository)
|
|
101
|
+
|
|
102
|
+
report = service.get_value(key.strip().lower())
|
|
103
|
+
|
|
104
|
+
view.show_path(path)
|
|
105
|
+
view.show_value(report)
|
|
106
|
+
|
|
107
|
+
except UnknownConfigurationKeyError as e:
|
|
108
|
+
logger.warning(str(e))
|
|
109
|
+
view.show_error(str(e))
|
|
110
|
+
raise SystemExit(1) from None
|
|
111
|
+
|
|
112
|
+
except typer.BadParameter as e:
|
|
113
|
+
logger.warning(str(e))
|
|
114
|
+
view.show_error(str(e))
|
|
115
|
+
raise SystemExit(1) from None
|
|
116
|
+
|
|
117
|
+
except Exception:
|
|
118
|
+
logger.exception("Unexpected error while executing config command.")
|
|
119
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
120
|
+
raise SystemExit(1) from None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command("set")
|
|
124
|
+
def set_config(
|
|
125
|
+
key: str,
|
|
126
|
+
value: str,
|
|
127
|
+
local: bool = typer.Option(
|
|
128
|
+
False, "--local", help="Write to the repository's local configuration."
|
|
129
|
+
),
|
|
130
|
+
global_: bool = typer.Option(
|
|
131
|
+
False, "--global", help="Write to the repository's global configuration."
|
|
132
|
+
),
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Set the value of a DiffSage configuration."""
|
|
135
|
+
|
|
136
|
+
view = ConfigView()
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
settings = load_settings()
|
|
140
|
+
|
|
141
|
+
scope = _resolve_scope(local=local, global_=global_)
|
|
142
|
+
path = resolve_config_path(scope)
|
|
143
|
+
repository = ConfigRepository(path)
|
|
144
|
+
|
|
145
|
+
service = ConfigService(settings, repository)
|
|
146
|
+
|
|
147
|
+
report = service.set_value(key.strip().lower(), value)
|
|
148
|
+
|
|
149
|
+
view.show_success(f"{scope.value.capitalize()} configuration updated.")
|
|
150
|
+
view.show_path(path)
|
|
151
|
+
view.show_configuration(report)
|
|
152
|
+
|
|
153
|
+
except UnknownConfigurationKeyError as e:
|
|
154
|
+
logger.warning(str(e))
|
|
155
|
+
view.show_error(str(e))
|
|
156
|
+
raise SystemExit(1) from None
|
|
157
|
+
|
|
158
|
+
except InvalidConfigurationValueError as e:
|
|
159
|
+
logger.warning(str(e))
|
|
160
|
+
view.show_error(str(e))
|
|
161
|
+
raise SystemExit(1) from None
|
|
162
|
+
|
|
163
|
+
except typer.BadParameter as e:
|
|
164
|
+
logger.warning(str(e))
|
|
165
|
+
view.show_error(str(e))
|
|
166
|
+
raise SystemExit(1) from None
|
|
167
|
+
|
|
168
|
+
except Exception:
|
|
169
|
+
logger.exception("Unexpected error while executing config command.")
|
|
170
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
171
|
+
raise SystemExit(1) from None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@app.command("unset")
|
|
175
|
+
def unset_config(
|
|
176
|
+
key: str,
|
|
177
|
+
local: bool = typer.Option(
|
|
178
|
+
False, "--local", help="Write to the repository's local configuration."
|
|
179
|
+
),
|
|
180
|
+
global_: bool = typer.Option(
|
|
181
|
+
False, "--global", help="Write to the repository's global configuration."
|
|
182
|
+
),
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Remove a DiffSage configuration value."""
|
|
185
|
+
|
|
186
|
+
view = ConfigView()
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
settings = load_settings()
|
|
190
|
+
|
|
191
|
+
scope = _resolve_scope(local=local, global_=global_)
|
|
192
|
+
path = resolve_config_path(scope)
|
|
193
|
+
repository = ConfigRepository(path)
|
|
194
|
+
|
|
195
|
+
service = ConfigService(settings, repository)
|
|
196
|
+
|
|
197
|
+
report = service.unset_value(key.strip().lower())
|
|
198
|
+
|
|
199
|
+
view.show_success(f"{scope.value.capitalize()} configuration removed.")
|
|
200
|
+
view.show_path(path)
|
|
201
|
+
view.show_configuration(report)
|
|
202
|
+
|
|
203
|
+
except UnknownConfigurationKeyError as e:
|
|
204
|
+
logger.warning(str(e))
|
|
205
|
+
view.show_error(str(e))
|
|
206
|
+
raise SystemExit(1) from None
|
|
207
|
+
|
|
208
|
+
except typer.BadParameter as e:
|
|
209
|
+
logger.warning(str(e))
|
|
210
|
+
view.show_error(str(e))
|
|
211
|
+
raise SystemExit(1) from None
|
|
212
|
+
|
|
213
|
+
except Exception:
|
|
214
|
+
logger.exception("Unexpected error while executing config command.")
|
|
215
|
+
view.show_error("An unexpected error occurred. Please check the log file for more details.")
|
|
216
|
+
raise SystemExit(1) from None
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from diffsage.logging.logger import get_logger
|
|
2
|
+
from diffsage.services.doctor_service import DoctorService
|
|
3
|
+
from diffsage.ui.doctor_view import DoctorView
|
|
4
|
+
|
|
5
|
+
logger = get_logger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def doctor() -> None:
|
|
9
|
+
"""Run environment diagnostics"""
|
|
10
|
+
|
|
11
|
+
logger.info("Running doctor command.")
|
|
12
|
+
|
|
13
|
+
service = DoctorService()
|
|
14
|
+
view = DoctorView()
|
|
15
|
+
|
|
16
|
+
report = service.run()
|
|
17
|
+
|
|
18
|
+
view.show_report(report)
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from diffsage.config.schema import (
|
|
2
|
+
AIConfig,
|
|
3
|
+
DiffSageConfig,
|
|
4
|
+
LoggingConfig,
|
|
5
|
+
NetworkConfig,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
DEFAULT_CONFIG = DiffSageConfig(
|
|
9
|
+
ai=AIConfig(
|
|
10
|
+
provider="gemini",
|
|
11
|
+
model="gemini-3.5-flash-lite",
|
|
12
|
+
),
|
|
13
|
+
network=NetworkConfig(
|
|
14
|
+
timeout=30,
|
|
15
|
+
max_retries=3,
|
|
16
|
+
),
|
|
17
|
+
logging=LoggingConfig(
|
|
18
|
+
level="INFO",
|
|
19
|
+
),
|
|
20
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import tomllib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from diffsage.config.schema import PartialDiffSageConfig
|
|
5
|
+
from diffsage.exceptions import ConfigError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_config(path: Path) -> PartialDiffSageConfig:
|
|
9
|
+
"""Load a configuration file."""
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
with path.open("rb") as file:
|
|
13
|
+
data = tomllib.load(file)
|
|
14
|
+
|
|
15
|
+
return PartialDiffSageConfig.model_validate(data)
|
|
16
|
+
|
|
17
|
+
except FileNotFoundError as error:
|
|
18
|
+
raise ConfigError(f"Configuration file not found: {path}") from error
|
|
19
|
+
|
|
20
|
+
except tomllib.TOMLDecodeError as error:
|
|
21
|
+
raise ConfigError(f"Invalid TOML syntax: {path}") from error
|