git-copilot-commit 0.4.4__tar.gz → 0.4.6__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.
Files changed (18) hide show
  1. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/PKG-INFO +8 -1
  2. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/README.md +6 -0
  3. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/pyproject.toml +1 -0
  4. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/src/git_copilot_commit/cli.py +103 -8
  5. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/src/git_copilot_commit/github_copilot.py +165 -33
  6. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/uv.lock +11 -0
  7. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/.github/workflows/ci.yml +0 -0
  8. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/.gitignore +0 -0
  9. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/.justfile +0 -0
  10. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/.python-version +0 -0
  11. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/LICENSE +0 -0
  12. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/src/git_copilot_commit/__init__.py +0 -0
  13. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/src/git_copilot_commit/git.py +0 -0
  14. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/src/git_copilot_commit/prompts/commit-message-generator-prompt.md +0 -0
  15. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/src/git_copilot_commit/py.typed +0 -0
  16. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/src/git_copilot_commit/settings.py +0 -0
  17. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/src/git_copilot_commit/version.py +0 -0
  18. {git_copilot_commit-0.4.4 → git_copilot_commit-0.4.6}/vhs/demo.vhs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-copilot-commit
3
- Version: 0.4.4
3
+ Version: 0.4.6
4
4
  Summary: Automatically generate and commit changes using copilot
5
5
  Author-email: Dheepak Krishnamurthy <1813121+kdheepak@users.noreply.github.com>
6
6
  License-File: LICENSE
@@ -8,6 +8,7 @@ Requires-Python: >=3.12
8
8
  Requires-Dist: httpx>=0.28.0
9
9
  Requires-Dist: platformdirs>=4.0.0
10
10
  Requires-Dist: rich>=14.0.0
11
+ Requires-Dist: truststore>=0.10.4
11
12
  Requires-Dist: typer>=0.16.0
12
13
  Description-Content-Type: text/markdown
13
14
 
@@ -77,6 +78,12 @@ pipx install git-copilot-commit
77
78
  uvx git-copilot-commit authenticate
78
79
  ```
79
80
 
81
+ If your cached GitHub token is revoked or expires, refresh it with:
82
+
83
+ ```bash
84
+ uvx git-copilot-commit authenticate --force
85
+ ```
86
+
80
87
  2. Make changes in your repository.
81
88
 
82
89
  3. Generate and commit:
@@ -64,6 +64,12 @@ pipx install git-copilot-commit
64
64
  uvx git-copilot-commit authenticate
65
65
  ```
66
66
 
67
+ If your cached GitHub token is revoked or expires, refresh it with:
68
+
69
+ ```bash
70
+ uvx git-copilot-commit authenticate --force
71
+ ```
72
+
67
73
  2. Make changes in your repository.
68
74
 
69
75
  3. Generate and commit:
@@ -12,6 +12,7 @@ dependencies = [
12
12
  "rich>=14.0.0",
13
13
  "typer>=0.16.0",
14
14
  "platformdirs>=4.0.0",
15
+ "truststore>=0.10.4",
15
16
  ]
16
17
 
17
18
  [project.scripts]
@@ -3,6 +3,7 @@ git-copilot-commit - AI-powered Git commit assistant
3
3
  """
4
4
 
5
5
  from pathlib import Path
6
+ from typing import Annotated
6
7
 
7
8
  import rich
8
9
  import typer
@@ -18,6 +19,27 @@ from . import github_copilot
18
19
  console = Console()
19
20
  app = typer.Typer(help=__doc__, add_completion=False)
20
21
 
22
+ CA_BUNDLE_HELP = (
23
+ "Path to a custom CA bundle (PEM). Use this to test internal / company CAs."
24
+ )
25
+ NATIVE_TLS_HELP = (
26
+ "Use the OS's native certificate store via 'truststore' for httpx instead of "
27
+ "the Python bundle. Ignored if --ca-bundle or --insecure is used."
28
+ )
29
+
30
+ CaBundleOption = Annotated[
31
+ str | None,
32
+ typer.Option("--ca-bundle", metavar="PATH", help=CA_BUNDLE_HELP),
33
+ ]
34
+ InsecureOption = Annotated[
35
+ bool,
36
+ typer.Option("--insecure", help="Disable SSL certificate verification."),
37
+ ]
38
+ NativeTlsOption = Annotated[
39
+ bool,
40
+ typer.Option("--native-tls/--no-native-tls", help=NATIVE_TLS_HELP),
41
+ ]
42
+
21
43
 
22
44
  def version_callback(value: bool):
23
45
  if value:
@@ -84,8 +106,24 @@ def load_system_prompt() -> str:
84
106
  raise typer.Exit(1)
85
107
 
86
108
 
109
+ def build_http_client_config(
110
+ *,
111
+ ca_bundle: str | None,
112
+ insecure: bool,
113
+ native_tls: bool,
114
+ ) -> github_copilot.HttpClientConfig:
115
+ return github_copilot.HttpClientConfig(
116
+ native_tls=native_tls,
117
+ insecure=insecure,
118
+ ca_bundle=ca_bundle,
119
+ )
120
+
121
+
87
122
  def generate_commit_message(
88
- repo: GitRepository, model: str | None = None, context: str = ""
123
+ repo: GitRepository,
124
+ model: str | None = None,
125
+ context: str = "",
126
+ http_client_config: github_copilot.HttpClientConfig | None = None,
89
127
  ) -> str:
90
128
  """Generate a conventional commit message using Copilot API."""
91
129
 
@@ -127,6 +165,7 @@ def generate_commit_message(
127
165
  {prompt}
128
166
  """,
129
167
  model=model,
168
+ http_client_config=http_client_config,
130
169
  )
131
170
 
132
171
 
@@ -151,6 +190,32 @@ def commit_with_retry_no_verify(
151
190
  raise typer.Exit(1)
152
191
 
153
192
 
193
+ @app.command("authenticate")
194
+ @app.command("login", hidden=True)
195
+ def authenticate(
196
+ force: bool = typer.Option(
197
+ False, "--force", help="Replace cached GitHub Copilot credentials"
198
+ ),
199
+ ca_bundle: CaBundleOption = None,
200
+ insecure: InsecureOption = False,
201
+ native_tls: NativeTlsOption = False,
202
+ ):
203
+ """Authenticate with GitHub Copilot and cache credentials locally."""
204
+ http_client_config = build_http_client_config(
205
+ ca_bundle=ca_bundle,
206
+ insecure=insecure,
207
+ native_tls=native_tls,
208
+ )
209
+ try:
210
+ github_copilot.login(
211
+ force=force,
212
+ http_client_config=http_client_config,
213
+ )
214
+ except github_copilot.CopilotError as exc:
215
+ console.print(f"[red]Authentication failed: {exc}[/red]")
216
+ raise typer.Exit(1)
217
+
218
+
154
219
  @app.command()
155
220
  def commit(
156
221
  all_files: bool = typer.Option(
@@ -168,6 +233,9 @@ def commit(
168
233
  "-c",
169
234
  help="Optional user-provided context to guide commit message",
170
235
  ),
236
+ ca_bundle: CaBundleOption = None,
237
+ insecure: InsecureOption = False,
238
+ native_tls: NativeTlsOption = False,
171
239
  ):
172
240
  """
173
241
  Generate commit message based on changes in the current git repository and commit them.
@@ -180,11 +248,24 @@ def commit(
180
248
 
181
249
  try:
182
250
  existing_credentials = github_copilot.load_credentials()
183
- except Exception as _:
251
+ except github_copilot.CopilotError:
184
252
  existing_credentials = None
185
253
 
254
+ http_client_config = build_http_client_config(
255
+ ca_bundle=ca_bundle,
256
+ insecure=insecure,
257
+ native_tls=native_tls,
258
+ )
259
+
186
260
  if existing_credentials is None:
187
- github_copilot.login()
261
+ try:
262
+ github_copilot.login(
263
+ force=True,
264
+ http_client_config=http_client_config,
265
+ )
266
+ except github_copilot.CopilotError as exc:
267
+ console.print(f"[red]Authentication failed: {exc}[/red]")
268
+ raise typer.Exit(1)
188
269
 
189
270
  # Load settings and use default model if none provided
190
271
  settings = Settings()
@@ -228,11 +309,25 @@ def commit(
228
309
  Panel(context.strip(), title="User Context", border_style="magenta")
229
310
  )
230
311
 
231
- # Generate or use provided commit message
232
- with console.status(
233
- "[yellow]Generating commit message based on [bold]`git diff --staged`[/] ...[/yellow]"
234
- ):
235
- commit_message = generate_commit_message(repo, model, context=context)
312
+ try:
313
+ github_copilot.ensure_auth_ready(
314
+ model=model,
315
+ http_client_config=http_client_config,
316
+ )
317
+
318
+ # Generate or use provided commit message
319
+ with console.status(
320
+ "[yellow]Generating commit message based on [bold]`git diff --staged`[/] ...[/yellow]"
321
+ ):
322
+ commit_message = generate_commit_message(
323
+ repo,
324
+ model,
325
+ context=context,
326
+ http_client_config=http_client_config,
327
+ )
328
+ except github_copilot.CopilotError as exc:
329
+ console.print(f"[red]Could not generate a commit message: {exc}[/red]")
330
+ raise typer.Exit(1)
236
331
 
237
332
  console.print("[yellow]Generated commit message.[/yellow]")
238
333
 
@@ -10,15 +10,15 @@ import uuid
10
10
  from dataclasses import dataclass
11
11
  from datetime import datetime
12
12
  from pathlib import Path
13
- from typing import Any
13
+ from typing import Any, Callable, TypeVar
14
14
 
15
15
  import httpx
16
- import typer
17
16
  from rich.console import Console
18
17
  from rich.panel import Panel
19
18
  from rich.table import Table
20
19
 
21
20
  APP_NAME = "github-copilot-commit"
21
+ CLI_AUTH_COMMAND = "git-copilot-commit authenticate"
22
22
  DEFAULT_GITHUB_DOMAIN = "github.com"
23
23
  USER_AGENT = "GitHubCopilotChat/0.35.0"
24
24
  EDITOR_VERSION = "vscode/1.107.0"
@@ -41,20 +41,25 @@ DEFAULT_MODEL_PREFERENCES = (
41
41
  "gpt-4o",
42
42
  )
43
43
 
44
- app = typer.Typer(
45
- add_completion=False,
46
- no_args_is_help=True,
47
- help="General-purpose GitHub Copilot CLI.",
48
- )
49
-
50
44
  console = Console()
51
- console_err = Console(stderr=True)
45
+ T = TypeVar("T")
52
46
 
53
47
 
54
48
  class CopilotError(RuntimeError):
55
49
  pass
56
50
 
57
51
 
52
+ class CopilotHttpError(CopilotError):
53
+ def __init__(
54
+ self, status_code: int, reason_phrase: str, detail: str | None = None
55
+ ) -> None:
56
+ self.status_code = status_code
57
+ self.reason_phrase = reason_phrase
58
+ self.detail = detail
59
+ suffix = f": {detail}" if detail else ""
60
+ super().__init__(f"{status_code} {reason_phrase}{suffix}")
61
+
62
+
58
63
  @dataclass(slots=True)
59
64
  class DeviceCodeResponse:
60
65
  device_code: str
@@ -151,6 +156,25 @@ class CopilotModel:
151
156
  )
152
157
 
153
158
 
159
+ @dataclass(frozen=True, slots=True)
160
+ class HttpClientConfig:
161
+ native_tls: bool = False
162
+ insecure: bool = False
163
+ ca_bundle: str | None = None
164
+
165
+ @property
166
+ def use_native_tls(self) -> bool:
167
+ return self.native_tls and not self.insecure and self.ca_bundle is None
168
+
169
+ @property
170
+ def verify(self) -> bool | str:
171
+ if self.insecure:
172
+ return False
173
+ if self.ca_bundle:
174
+ return self.ca_bundle
175
+ return True
176
+
177
+
154
178
  @dataclass(slots=True)
155
179
  class GitHubViewer:
156
180
  login: str
@@ -259,8 +283,34 @@ def get_github_copilot_base_url(
259
283
  return "https://api.individual.githubcopilot.com"
260
284
 
261
285
 
262
- def make_http_client() -> httpx.Client:
286
+ _NATIVE_TLS_ENABLED = False
287
+
288
+
289
+ def _maybe_enable_native_tls(native_tls: bool) -> None:
290
+ """
291
+ Globally switch httpx/requests to use the OS certificate store via truststore.
292
+ Safe to call multiple times; it no-ops after the first.
293
+ """
294
+ global _NATIVE_TLS_ENABLED
295
+ if not native_tls or _NATIVE_TLS_ENABLED:
296
+ return
297
+
298
+ try:
299
+ import truststore
300
+
301
+ truststore.inject_into_ssl()
302
+ except Exception as _:
303
+ return
304
+
305
+ _NATIVE_TLS_ENABLED = True
306
+
307
+
308
+ def make_http_client(http_client_config: HttpClientConfig | None = None) -> httpx.Client:
309
+ config = http_client_config or HttpClientConfig()
310
+ _maybe_enable_native_tls(config.use_native_tls)
311
+
263
312
  return httpx.Client(
313
+ verify=config.verify,
264
314
  follow_redirects=True,
265
315
  timeout=httpx.Timeout(30.0, connect=10.0),
266
316
  )
@@ -290,8 +340,7 @@ def request_json(
290
340
  detail = response.text.strip()
291
341
  if len(detail) > 400:
292
342
  detail = f"{detail[:397]}..."
293
- suffix = f": {detail}" if detail else ""
294
- raise CopilotError(f"{response.status_code} {response.reason_phrase}{suffix}")
343
+ raise CopilotHttpError(response.status_code, response.reason_phrase, detail)
295
344
 
296
345
  content_type = response.headers.get("content-type", "")
297
346
  if "application/json" not in content_type:
@@ -364,10 +413,9 @@ def iter_sse_events(response: httpx.Response, url: str):
364
413
  yield payload
365
414
 
366
415
 
367
- def load_credentials() -> CopilotCredentials | None:
368
- path = credentials_path()
416
+ def read_json_object(path: Path) -> dict[str, Any] | None:
369
417
  if not path.exists():
370
- return
418
+ return None
371
419
 
372
420
  try:
373
421
  raw = json.loads(path.read_text(encoding="utf-8"))
@@ -379,11 +427,23 @@ def load_credentials() -> CopilotCredentials | None:
379
427
  if not isinstance(raw, dict):
380
428
  raise CopilotError(f"Cached credentials in {path} are not a JSON object.")
381
429
 
430
+ return raw
431
+
432
+
433
+ def load_stored_credentials_from_path(path: Path) -> CopilotCredentials | None:
434
+ raw = read_json_object(path)
435
+ if raw is None:
436
+ return None
437
+
382
438
  return CopilotCredentials.from_dict(raw)
383
439
 
384
440
 
441
+ def load_credentials() -> CopilotCredentials | None:
442
+ return load_stored_credentials_from_path(credentials_path())
443
+
444
+
385
445
  def save_credentials(credentials: CopilotCredentials) -> Path:
386
- path = credentials_path()
446
+ path = credentials_path().expanduser()
387
447
  path.parent.mkdir(parents=True, exist_ok=True)
388
448
  payload = json.dumps(credentials.to_dict(), indent=2, sort_keys=True)
389
449
  path.write_text(f"{payload}\n", encoding="utf-8")
@@ -573,7 +633,7 @@ def ensure_fresh_credentials(client: httpx.Client) -> CopilotCredentials:
573
633
  credentials = load_credentials()
574
634
  if credentials is None:
575
635
  raise CopilotError(
576
- f"No cached Copilot credentials found. Run `{Path(__file__).name} auth login` first."
636
+ f"No cached Copilot credentials found. Run `{CLI_AUTH_COMMAND}` first."
577
637
  )
578
638
 
579
639
  if not credentials.is_expired():
@@ -588,6 +648,23 @@ def ensure_fresh_credentials(client: httpx.Client) -> CopilotCredentials:
588
648
  return refreshed
589
649
 
590
650
 
651
+ def should_reauthenticate(exc: CopilotError) -> bool:
652
+ if isinstance(exc, CopilotHttpError):
653
+ return exc.status_code == 401
654
+
655
+ message = str(exc)
656
+ retryable_prefixes = (
657
+ "No cached Copilot credentials found.",
658
+ "Cached GitHub access token is missing or invalid.",
659
+ "Cached Copilot token is missing or invalid.",
660
+ "Cached Copilot expiration timestamp is missing or invalid.",
661
+ "Cached enterprise domain is invalid.",
662
+ "Unable to read cached credentials from ",
663
+ "Cached credentials in ",
664
+ )
665
+ return any(message.startswith(prefix) for prefix in retryable_prefixes)
666
+
667
+
591
668
  def copilot_request_headers(
592
669
  access_token: str,
593
670
  *,
@@ -886,9 +963,8 @@ def responses_completion(
886
963
  detail = response.read().decode("utf-8", errors="replace").strip()
887
964
  if len(detail) > 400:
888
965
  detail = f"{detail[:397]}..."
889
- suffix = f": {detail}" if detail else ""
890
- raise CopilotError(
891
- f"{response.status_code} {response.reason_phrase}{suffix}"
966
+ raise CopilotHttpError(
967
+ response.status_code, response.reason_phrase, detail
892
968
  )
893
969
 
894
970
  content_type = response.headers.get("content-type", "")
@@ -1101,20 +1177,31 @@ def print_login_summary(
1101
1177
  console.print(Panel.fit(table, title="Login Summary"))
1102
1178
 
1103
1179
 
1104
- def login(enterprise_domain: str | None = None, force: bool = False) -> None:
1180
+ def login(
1181
+ enterprise_domain: str | None = None,
1182
+ force: bool = False,
1183
+ *,
1184
+ http_client_config: HttpClientConfig | None = None,
1185
+ ) -> None:
1105
1186
  """Authenticate with GitHub and cache Copilot credentials locally."""
1106
1187
  normalized_domain = normalize_domain(enterprise_domain)
1107
1188
  if enterprise_domain and not normalized_domain:
1108
1189
  raise CopilotError("Invalid GitHub Enterprise hostname.")
1109
1190
 
1110
- existing = load_credentials()
1191
+ existing: CopilotCredentials | None = None
1192
+ try:
1193
+ existing = load_credentials()
1194
+ except CopilotError:
1195
+ if not force:
1196
+ raise
1197
+
1111
1198
  if existing and not force:
1112
1199
  raise CopilotError(
1113
1200
  f"Cached credentials already exist at {credentials_path()}. Re-run with --force to replace them."
1114
1201
  )
1115
1202
 
1116
1203
  domain = normalized_domain or DEFAULT_GITHUB_DOMAIN
1117
- with make_http_client() as client:
1204
+ with make_http_client(http_client_config) as client:
1118
1205
  device = start_device_flow(client, domain)
1119
1206
 
1120
1207
  console.print(
@@ -1162,16 +1249,61 @@ def login(enterprise_domain: str | None = None, force: bool = False) -> None:
1162
1249
  console.print(f"[yellow]Warning:[/yellow] {warning}")
1163
1250
 
1164
1251
 
1165
- def ask(prompt: str, model: str | None = None) -> str:
1166
- """Send a prompt to GitHub Copilot and print the reply."""
1167
- with make_http_client() as client:
1252
+ def _ask_once(client: httpx.Client, prompt: str, model: str | None = None) -> str:
1253
+ credentials = ensure_fresh_credentials(client)
1254
+ all_models = list_models(client, credentials)
1255
+
1256
+ selected_model = pick_model(all_models, model)
1257
+ return complete_text_prompt(
1258
+ client,
1259
+ credentials,
1260
+ model=selected_model,
1261
+ prompt=prompt,
1262
+ )
1263
+
1264
+
1265
+ def _with_reauthentication(
1266
+ action: Callable[[httpx.Client], T],
1267
+ *,
1268
+ http_client_config: HttpClientConfig | None = None,
1269
+ ) -> T:
1270
+ try:
1271
+ with make_http_client(http_client_config) as client:
1272
+ return action(client)
1273
+ except CopilotError as exc:
1274
+ if not should_reauthenticate(exc):
1275
+ raise
1276
+
1277
+ console.print(
1278
+ "[yellow]Cached GitHub Copilot credentials are missing or no longer valid. Starting authentication...[/yellow]"
1279
+ )
1280
+ login(force=True, http_client_config=http_client_config)
1281
+
1282
+ with make_http_client(http_client_config) as client:
1283
+ return action(client)
1284
+
1285
+
1286
+ def ensure_auth_ready(
1287
+ model: str | None = None,
1288
+ *,
1289
+ http_client_config: HttpClientConfig | None = None,
1290
+ ) -> None:
1291
+ def validate(client: httpx.Client) -> None:
1168
1292
  credentials = ensure_fresh_credentials(client)
1169
1293
  all_models = list_models(client, credentials)
1294
+ pick_model(all_models, model)
1170
1295
 
1171
- selected_model = pick_model(all_models, model)
1172
- return complete_text_prompt(
1173
- client,
1174
- credentials,
1175
- model=selected_model,
1176
- prompt=prompt,
1177
- )
1296
+ _with_reauthentication(validate, http_client_config=http_client_config)
1297
+
1298
+
1299
+ def ask(
1300
+ prompt: str,
1301
+ model: str | None = None,
1302
+ *,
1303
+ http_client_config: HttpClientConfig | None = None,
1304
+ ) -> str:
1305
+ """Send a prompt to GitHub Copilot and print the reply."""
1306
+ return _with_reauthentication(
1307
+ lambda client: _ask_once(client, prompt, model),
1308
+ http_client_config=http_client_config,
1309
+ )
@@ -53,6 +53,7 @@ dependencies = [
53
53
  { name = "httpx" },
54
54
  { name = "platformdirs" },
55
55
  { name = "rich" },
56
+ { name = "truststore" },
56
57
  { name = "typer" },
57
58
  ]
58
59
 
@@ -61,6 +62,7 @@ requires-dist = [
61
62
  { name = "httpx", specifier = ">=0.28.0" },
62
63
  { name = "platformdirs", specifier = ">=4.0.0" },
63
64
  { name = "rich", specifier = ">=14.0.0" },
65
+ { name = "truststore", specifier = ">=0.10.4" },
64
66
  { name = "typer", specifier = ">=0.16.0" },
65
67
  ]
66
68
 
@@ -180,6 +182,15 @@ wheels = [
180
182
  { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
181
183
  ]
182
184
 
185
+ [[package]]
186
+ name = "truststore"
187
+ version = "0.10.4"
188
+ source = { registry = "https://pypi.org/simple" }
189
+ sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
190
+ wheels = [
191
+ { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
192
+ ]
193
+
183
194
  [[package]]
184
195
  name = "typer"
185
196
  version = "0.16.0"