oy-cli 0.3.1__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oy-cli
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Tiny local coding CLI with a small tool surface
5
5
  Author: oy-cli contributors
6
6
  License-Expression: Apache-2.0
@@ -10,9 +10,9 @@ Project-URL: Issues, https://github.com/wagov-dtt/oy-cli/issues
10
10
  Requires-Python: >=3.14
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
+ Requires-Dist: boto3>=1.35.0
13
14
  Requires-Dist: defopt>=7.0.0
14
15
  Requires-Dist: httpx>=0.28.1
15
- Requires-Dist: httpx-aws-auth>=4.1.1
16
16
  Requires-Dist: markdownify>=1.2.0
17
17
  Requires-Dist: openai>=1.68.0
18
18
  Requires-Dist: rich>=14.3.0
@@ -23,6 +23,8 @@ Dynamic: license-file
23
23
 
24
24
  # oy-cli
25
25
 
26
+ [![PyPI](https://img.shields.io/pypi/v/oy-cli)](https://pypi.org/project/oy-cli/)
27
+
26
28
  **Tiny AI coding assistant for your shell.** Reads files, runs commands, makes precise edits, and stays intentionally small.
27
29
 
28
30
  ```bash
@@ -55,7 +57,6 @@ oy chat # Interactive multi-turn session
55
57
  oy audit # Security audit against OWASP ASVS/MASVS
56
58
  oy model # Show current default model
57
59
  oy model <filter> # Pick/save a model
58
- oy model --token # Print Bedrock OpenAI env exports
59
60
  oy --help # Show all commands
60
61
  ```
61
62
 
@@ -228,7 +229,7 @@ Recommended:
228
229
  - avoid exposing long-lived secrets in the environment
229
230
  - review generated changes before shipping
230
231
 
231
- **Automatic protections:** workspace-bound file access, structured edits through `apply`, sensitive header redaction in `httpx`, and secure Bedrock token generation.
232
+ **Automatic protections:** workspace-bound file access, structured edits through `apply`, sensitive header redaction in `httpx`, and native boto3 credential resolution for Bedrock.
232
233
 
233
234
  ## Links
234
235
 
@@ -1,5 +1,7 @@
1
1
  # oy-cli
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/oy-cli)](https://pypi.org/project/oy-cli/)
4
+
3
5
  **Tiny AI coding assistant for your shell.** Reads files, runs commands, makes precise edits, and stays intentionally small.
4
6
 
5
7
  ```bash
@@ -32,7 +34,6 @@ oy chat # Interactive multi-turn session
32
34
  oy audit # Security audit against OWASP ASVS/MASVS
33
35
  oy model # Show current default model
34
36
  oy model <filter> # Pick/save a model
35
- oy model --token # Print Bedrock OpenAI env exports
36
37
  oy --help # Show all commands
37
38
  ```
38
39
 
@@ -205,7 +206,7 @@ Recommended:
205
206
  - avoid exposing long-lived secrets in the environment
206
207
  - review generated changes before shipping
207
208
 
208
- **Automatic protections:** workspace-bound file access, structured edits through `apply`, sensitive header redaction in `httpx`, and secure Bedrock token generation.
209
+ **Automatic protections:** workspace-bound file access, structured edits through `apply`, sensitive header redaction in `httpx`, and native boto3 credential resolution for Bedrock.
209
210
 
210
211
  ## Links
211
212
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oy-cli
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Tiny local coding CLI with a small tool surface
5
5
  Author: oy-cli contributors
6
6
  License-Expression: Apache-2.0
@@ -10,9 +10,9 @@ Project-URL: Issues, https://github.com/wagov-dtt/oy-cli/issues
10
10
  Requires-Python: >=3.14
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
+ Requires-Dist: boto3>=1.35.0
13
14
  Requires-Dist: defopt>=7.0.0
14
15
  Requires-Dist: httpx>=0.28.1
15
- Requires-Dist: httpx-aws-auth>=4.1.1
16
16
  Requires-Dist: markdownify>=1.2.0
17
17
  Requires-Dist: openai>=1.68.0
18
18
  Requires-Dist: rich>=14.3.0
@@ -23,6 +23,8 @@ Dynamic: license-file
23
23
 
24
24
  # oy-cli
25
25
 
26
+ [![PyPI](https://img.shields.io/pypi/v/oy-cli)](https://pypi.org/project/oy-cli/)
27
+
26
28
  **Tiny AI coding assistant for your shell.** Reads files, runs commands, makes precise edits, and stays intentionally small.
27
29
 
28
30
  ```bash
@@ -55,7 +57,6 @@ oy chat # Interactive multi-turn session
55
57
  oy audit # Security audit against OWASP ASVS/MASVS
56
58
  oy model # Show current default model
57
59
  oy model <filter> # Pick/save a model
58
- oy model --token # Print Bedrock OpenAI env exports
59
60
  oy --help # Show all commands
60
61
  ```
61
62
 
@@ -228,7 +229,7 @@ Recommended:
228
229
  - avoid exposing long-lived secrets in the environment
229
230
  - review generated changes before shipping
230
231
 
231
- **Automatic protections:** workspace-bound file access, structured edits through `apply`, sensitive header redaction in `httpx`, and secure Bedrock token generation.
232
+ **Automatic protections:** workspace-bound file access, structured edits through `apply`, sensitive header redaction in `httpx`, and native boto3 credential resolution for Bedrock.
232
233
 
233
234
  ## Links
234
235
 
@@ -1,6 +1,6 @@
1
+ boto3>=1.35.0
1
2
  defopt>=7.0.0
2
3
  httpx>=0.28.1
3
- httpx-aws-auth>=4.1.1
4
4
  markdownify>=1.2.0
5
5
  openai>=1.68.0
6
6
  rich>=14.3.0
@@ -5,7 +5,6 @@ import json
5
5
  import os
6
6
  import readline
7
7
  import re
8
- import shlex
9
8
  import sys
10
9
  from pathlib import Path
11
10
  from typing import Any, Callable, Literal, TypeAlias, cast
@@ -28,7 +27,6 @@ from shim import (
28
27
  UserMessage,
29
28
  which,
30
29
  CompletionClient,
31
- bedrock_base_url,
32
30
  default_region,
33
31
  detect_available_shims,
34
32
  ensure_api_env as ensure_shim_api_env,
@@ -36,7 +34,6 @@ from shim import (
36
34
  join_model_spec,
37
35
  list_model_ids as list_shim_model_ids,
38
36
  list_models_for_shim,
39
- make_bedrock_token,
40
37
  require_api_env as require_shim_api_env,
41
38
  resolve_shim as resolve_model_shim,
42
39
  split_model_spec,
@@ -1719,36 +1716,11 @@ def resolve_model_choice(model_id=None):
1719
1716
  query = Prompt.ask("Model or filter", console=STDERR).strip()
1720
1717
 
1721
1718
 
1722
- def model(query: str | None = None, *, region: str | None = None, token: bool = False):
1723
- """Show or set the default model, or print Bedrock credentials.
1719
+ def model(query: str | None = None):
1720
+ """Show or set the default model.
1724
1721
 
1725
1722
  :param query: Exact model ID to save, or a filter string when running in a TTY.
1726
- :param region: AWS region to use with --token.
1727
- :param token: Print Bedrock-backed OpenAI credentials instead of model selection.
1728
1723
  """
1729
- if token:
1730
- chosen = default_region() if region is None else region
1731
- _print(
1732
- "status",
1733
- f"Generating Bedrock credentials for {_fmt('inline', chosen)}.",
1734
- err=True,
1735
- )
1736
- value = make_bedrock_token(chosen, cwd=Path.cwd())
1737
- _print(
1738
- value="## Bedrock Credentials\n\n"
1739
- + "Paste this into another shell if you want to reuse the current Bedrock session.\n\n"
1740
- + _fmt(
1741
- "block",
1742
- "\n".join(
1743
- [
1744
- f"export OPENAI_BASE_URL={shlex.quote(bedrock_base_url(chosen))}",
1745
- f"export OPENAI_API_KEY={shlex.quote(value)}",
1746
- ]
1747
- ),
1748
- "bash",
1749
- )
1750
- )
1751
- return 0
1752
1724
  current = _model(None)
1753
1725
  if query is None and not sys.stdin.isatty():
1754
1726
  shim = resolve_active_shim(current)
@@ -4,16 +4,16 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "oy-cli"
7
- version = "0.3.1"
7
+ version = "0.3.2"
8
8
  description = "Tiny local coding CLI with a small tool surface"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14"
11
11
  license = "Apache-2.0"
12
12
  authors = [{ name = "oy-cli contributors" }]
13
13
  dependencies = [
14
+ "boto3>=1.35.0",
14
15
  "defopt>=7.0.0",
15
16
  "httpx>=0.28.1",
16
- "httpx-aws-auth>=4.1.1",
17
17
  "markdownify>=1.2.0",
18
18
  "openai>=1.68.0",
19
19
  "rich>=14.3.0",
@@ -17,7 +17,6 @@ from pathlib import Path
17
17
  from typing import Any, Awaitable, Callable, TypeAlias
18
18
  from urllib.parse import quote
19
19
  import httpx
20
- import httpx_aws_auth
21
20
  import msgspec
22
21
  from openai import (
23
22
  APIConnectionError,
@@ -2004,84 +2003,84 @@ def _bedrock_output_blocks(data: dict[str, Any]) -> list[ContentBlock]:
2004
2003
  )
2005
2004
 
2006
2005
 
2007
- def _bedrock_model_ids(client: httpx.Client, region: str) -> list[str]:
2008
- base_url = f"https://bedrock.{region}.amazonaws.com"
2009
- return [
2010
- *_fetch_json_ids(
2011
- client,
2012
- f"{base_url}/foundation-models",
2013
- items_key="modelSummaries",
2014
- id_key="modelId",
2015
- item_filter=lambda item: (
2016
- "TEXT" in item.get("outputModalities", [])
2017
- and item["modelId"].startswith(("global.", "us."))
2018
- ),
2019
- ),
2020
- *_fetch_json_ids(
2021
- client,
2022
- f"{base_url}/inference-profiles",
2023
- items_key="inferenceProfileSummaries",
2024
- id_key="inferenceProfileId",
2025
- item_filter=lambda item: item["inferenceProfileId"].startswith(
2026
- ("global.", "us.")
2027
- ),
2028
- ),
2029
- ]
2006
+ def _boto3_bedrock_model_ids(region: str) -> list[str]:
2007
+ """List available Bedrock model IDs using boto3."""
2008
+ import boto3
2030
2009
 
2010
+ client = boto3.client("bedrock", region_name=region)
2011
+ ids: list[str] = []
2012
+ # Foundation models
2013
+ try:
2014
+ resp = client.list_foundation_models()
2015
+ for summary in resp.get("modelSummaries", []):
2016
+ model_id = summary.get("modelId", "")
2017
+ if (
2018
+ "TEXT" in summary.get("outputModalities", [])
2019
+ and model_id.startswith(("global.", "us."))
2020
+ ):
2021
+ ids.append(model_id)
2022
+ except Exception:
2023
+ pass
2024
+ # Inference profiles
2025
+ try:
2026
+ resp = client.list_inference_profiles()
2027
+ for summary in resp.get("inferenceProfileSummaries", []):
2028
+ profile_id = summary.get("inferenceProfileId", "")
2029
+ if profile_id.startswith(("global.", "us.")):
2030
+ ids.append(profile_id)
2031
+ except Exception:
2032
+ pass
2033
+ return ids
2031
2034
 
2032
- def _bedrock_converse_client(
2033
- region: str, credentials: dict[str, str]
2034
- ) -> CompletionClient:
2035
- base_url = f"https://bedrock-runtime.{region}.amazonaws.com"
2036
- aws_credentials = httpx_aws_auth.AwsCredentials(
2037
- access_key=credentials["access_key"],
2038
- secret_key=credentials["secret_key"],
2039
- session_token=credentials.get("session_token"),
2040
- )
2041
- auth = httpx_aws_auth.AwsSigV4Auth(
2042
- credentials=aws_credentials, service="bedrock", region=region
2035
+
2036
+ def _bedrock_converse_client(region: str) -> CompletionClient:
2037
+ import asyncio
2038
+ import boto3
2039
+ from botocore.config import Config as BotoConfig
2040
+
2041
+ config = BotoConfig(
2042
+ read_timeout=float(os.environ.get("OY_BEDROCK_READ_TIMEOUT", "120")),
2043
+ retries={"max_attempts": DEFAULT_RETRY_MAX_ATTEMPTS, "mode": "adaptive"},
2043
2044
  )
2045
+ rt_client = boto3.client("bedrock-runtime", region_name=region, config=config)
2044
2046
 
2045
- def make_request(
2047
+ async def chat_completion(
2046
2048
  model: str,
2047
2049
  messages: list[ChatMessage],
2048
- tools: list[ToolSpec] | None,
2049
- tool_choice: str,
2050
- ) -> HttpRequest:
2050
+ tools: list[ToolSpec] | None = None,
2051
+ tool_choice: str = "auto",
2052
+ on_retry=None,
2053
+ ) -> AssistantMessage:
2054
+ _ = on_retry
2051
2055
  bedrock_messages, system_prompts = _encode_provider_messages(
2052
2056
  messages, BEDROCK_CODEC
2053
2057
  )
2054
- payload: dict[str, Any] = {
2058
+ kwargs: dict[str, Any] = {
2059
+ "modelId": model,
2055
2060
  "messages": bedrock_messages,
2056
2061
  "inferenceConfig": {
2057
- "maxTokens": int(os.environ.get("OY_BEDROCK_MAX_OUTPUT_TOKENS", "4096"))
2062
+ "maxTokens": int(
2063
+ os.environ.get("OY_BEDROCK_MAX_OUTPUT_TOKENS", "4096")
2064
+ )
2058
2065
  },
2059
2066
  }
2060
2067
  if system_prompts:
2061
- payload["system"] = system_prompts
2068
+ kwargs["system"] = system_prompts
2062
2069
  if tools and (tool_config := _bedrock_tools(tools, tool_choice)):
2063
- payload["toolConfig"] = tool_config
2064
- return HttpRequest(
2065
- url=f"{base_url}/model/{model}/converse",
2066
- content_body=json.dumps(payload),
2067
- headers={"content-type": "application/json"},
2068
- )
2069
-
2070
- endpoint = HttpChatEndpoint(
2071
- make_client=lambda: async_http_client(auth=auth),
2072
- build_request=make_request,
2073
- decode=lambda data: _assistant_from_blocks(_bedrock_output_blocks(data)),
2074
- error=lambda resp: _http_error_message("Bedrock", resp),
2075
- timeout=BEDROCK_TIMEOUT,
2076
- bedrock=True,
2077
- )
2070
+ kwargs["toolConfig"] = tool_config
2071
+ # boto3 is synchronous; run in executor to avoid blocking the event loop
2072
+ loop = asyncio.get_running_loop()
2073
+ try:
2074
+ data = await loop.run_in_executor(None, lambda: rt_client.converse(**kwargs))
2075
+ except Exception as exc:
2076
+ raise RuntimeError(f"Bedrock converse error: {exc}") from exc
2077
+ return _assistant_from_blocks(_bedrock_output_blocks(data))
2078
2078
 
2079
2079
  def list_models() -> list[str]:
2080
- with http_client(auth=auth, timeout=30) as client:
2081
- return sorted(unique_strings(_bedrock_model_ids(client, region)))
2080
+ return sorted(unique_strings(_boto3_bedrock_model_ids(region)))
2082
2081
 
2083
- return _http_completion_client(
2084
- endpoint,
2082
+ return CompletionClient(
2083
+ chat_completion=chat_completion,
2085
2084
  list_models=list_models,
2086
2085
  )
2087
2086
 
@@ -2366,6 +2365,25 @@ def _require_aws_env(cwd: Path | None = None) -> None:
2366
2365
  load_aws_credentials(cwd, allow_login=False)
2367
2366
 
2368
2367
 
2368
+ def _require_boto3_aws_env(cwd: Path | None = None) -> None:
2369
+ """Validate that boto3 can resolve AWS credentials."""
2370
+ _ = cwd
2371
+ import boto3
2372
+ from botocore.exceptions import NoCredentialsError, PartialCredentialsError
2373
+
2374
+ session = boto3.Session()
2375
+ credentials = session.get_credentials()
2376
+ if credentials is None:
2377
+ raise RuntimeError(
2378
+ "No AWS credentials found. Configure via environment variables, "
2379
+ "~/.aws/credentials, IAM role, or AWS SSO."
2380
+ )
2381
+ try:
2382
+ credentials.get_frozen_credentials()
2383
+ except (NoCredentialsError, PartialCredentialsError) as exc:
2384
+ raise RuntimeError(f"AWS credentials incomplete: {exc}") from exc
2385
+
2386
+
2369
2387
  def _build_openai_client(
2370
2388
  region: str | None = None, cwd: Path | None = None, *, max_retries: int = 3
2371
2389
  ) -> CompletionClient:
@@ -2402,7 +2420,8 @@ def _build_gemini_client(
2402
2420
  def _build_bedrock_client(
2403
2421
  region: str | None = None, cwd: Path | None = None
2404
2422
  ) -> CompletionClient:
2405
- return _bedrock_converse_client(current_region(region), load_aws_credentials(cwd))
2423
+ _ = cwd
2424
+ return _bedrock_converse_client(current_region(region))
2406
2425
 
2407
2426
 
2408
2427
  def _build_mantle_client(
@@ -2455,9 +2474,8 @@ def _list_claude_models(
2455
2474
  def _list_bedrock_models(
2456
2475
  region: str | None = None, cwd: Path | None = None
2457
2476
  ) -> list[str]:
2458
- return _bedrock_converse_client(
2459
- current_region(region), load_aws_credentials(cwd, allow_login=False)
2460
- ).list_models()
2477
+ _ = cwd
2478
+ return _bedrock_converse_client(current_region(region)).list_models()
2461
2479
 
2462
2480
 
2463
2481
  def _list_mantle_models(
@@ -2487,7 +2505,7 @@ SHIM_SPECS: dict[str, ShimSpec] = {
2487
2505
  ),
2488
2506
  SHIM_BEDROCK: ShimSpec(
2489
2507
  name=SHIM_BEDROCK,
2490
- ensure_env=_require_aws_env,
2508
+ ensure_env=_require_boto3_aws_env,
2491
2509
  build_client=_build_bedrock_client,
2492
2510
  list_models=_list_bedrock_models,
2493
2511
  ),
File without changes
File without changes
File without changes
File without changes