wendell 0.1.0__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.
- wendell-0.1.0/PKG-INFO +111 -0
- wendell-0.1.0/README.md +99 -0
- wendell-0.1.0/pyproject.toml +26 -0
- wendell-0.1.0/setup.cfg +4 -0
- wendell-0.1.0/src/wendell.egg-info/PKG-INFO +111 -0
- wendell-0.1.0/src/wendell.egg-info/SOURCES.txt +24 -0
- wendell-0.1.0/src/wendell.egg-info/dependency_links.txt +1 -0
- wendell-0.1.0/src/wendell.egg-info/entry_points.txt +3 -0
- wendell-0.1.0/src/wendell.egg-info/requires.txt +3 -0
- wendell-0.1.0/src/wendell.egg-info/top_level.txt +1 -0
- wendell-0.1.0/src/wendell_ci/__init__.py +14 -0
- wendell-0.1.0/src/wendell_ci/auth.py +91 -0
- wendell-0.1.0/src/wendell_ci/cli.py +657 -0
- wendell-0.1.0/src/wendell_ci/config.py +73 -0
- wendell-0.1.0/src/wendell_ci/gates.py +61 -0
- wendell-0.1.0/src/wendell_ci/models.py +60 -0
- wendell-0.1.0/src/wendell_ci/remote_client.py +92 -0
- wendell-0.1.0/src/wendell_ci/trace.py +61 -0
- wendell-0.1.0/src/wendell_ci/worldsim_client.py +127 -0
- wendell-0.1.0/tests/test_auth.py +24 -0
- wendell-0.1.0/tests/test_cli.py +318 -0
- wendell-0.1.0/tests/test_command_agent_integration.py +51 -0
- wendell-0.1.0/tests/test_config.py +21 -0
- wendell-0.1.0/tests/test_gates.py +48 -0
- wendell-0.1.0/tests/test_remote_client.py +146 -0
- wendell-0.1.0/tests/test_worldsim_integration.py +69 -0
wendell-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wendell
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Wendell CLI for agent world simulations, Park registration, and run uploads
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Project-URL: Homepage, https://www.wendellai.com
|
|
7
|
+
Project-URL: Repository, https://github.com/croppia/wendellai
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
12
|
+
|
|
13
|
+
# Wendell CLI
|
|
14
|
+
|
|
15
|
+
Wendell is a Python CLI for evaluating agents against Wendell-managed worlds and scenario packs.
|
|
16
|
+
|
|
17
|
+
The first version is advisory by default: it reports scores, captures traces, and returns a successful process exit unless the project explicitly enables blocking gates.
|
|
18
|
+
|
|
19
|
+
## Intended split
|
|
20
|
+
|
|
21
|
+
- Wendell system: creates worlds, versions scenario packs, owns rubrics, stores traces, and reports regressions.
|
|
22
|
+
- Wendell CI runner: runs in a repo or CI job, fetches or reads a scenario pack, invokes the customer's agent adapter, captures traces, evaluates gates, uploads results, and prints a concise summary.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv tool install wendell
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Alternative installers:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pipx install wendell
|
|
34
|
+
python3 -m pip install --user wendell
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The package installs the `wendell` command. It also installs the temporary `local-wendell` alias for existing local dogfood scripts.
|
|
38
|
+
|
|
39
|
+
## Local development
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd wendell-ci
|
|
43
|
+
python -m venv .venv
|
|
44
|
+
source .venv/bin/activate
|
|
45
|
+
pip install -e ".[dev]"
|
|
46
|
+
pytest
|
|
47
|
+
wendell --help
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Login
|
|
51
|
+
|
|
52
|
+
For local development, store an InkPass API key once:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
wendell login --api-url https://api.wendellai.com --api-key-stdin --validate
|
|
56
|
+
wendell auth status
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Credentials are stored at `~/.config/wendell/credentials.json` by default, or `$WENDELL_CONFIG_HOME/credentials.json` when set. The directory is created with `0700` permissions and the credentials file with `0600` permissions.
|
|
60
|
+
|
|
61
|
+
CI should continue to use `WENDELL_INKPASS_API_KEY`; environment variables take precedence over stored credentials.
|
|
62
|
+
|
|
63
|
+
## Minimal config
|
|
64
|
+
|
|
65
|
+
```toml
|
|
66
|
+
project = "support-agent"
|
|
67
|
+
mode = "advisory"
|
|
68
|
+
api_url = "https://api.wendell.example"
|
|
69
|
+
api_key_env = "WENDELL_INKPASS_API_KEY"
|
|
70
|
+
world = "world_support_ops_v1"
|
|
71
|
+
world_version = "2026-05-08.1"
|
|
72
|
+
scenario_pack = "smoke"
|
|
73
|
+
scenario_pack_version = "1.0.0"
|
|
74
|
+
agent_command = "python examples/simple_cli_agent.py"
|
|
75
|
+
upload_traces = true
|
|
76
|
+
|
|
77
|
+
[gates]
|
|
78
|
+
suite_min_score = 0.80
|
|
79
|
+
scenario_min_score = 0.75
|
|
80
|
+
critical_failures_allowed = 0
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The intended production flow is:
|
|
84
|
+
|
|
85
|
+
1. `local-wendell` runs on the developer machine or CI worker.
|
|
86
|
+
2. It fetches the pinned world, scenario pack, rubrics, and scoring contract from Wendell.
|
|
87
|
+
3. It invokes the agent locally through an adapter such as `agent_command`.
|
|
88
|
+
4. It captures local traces, redacts them, and uploads traces/results back to Wendell.
|
|
89
|
+
5. Advisory mode exits `0`; blocking mode exits nonzero only when explicitly enabled.
|
|
90
|
+
|
|
91
|
+
Remote uploads authenticate with an InkPass API key sent as `X-API-Key`. The key needs at least `wendell:worlds:read`, `wendell:runs:create`, and `wendell:runs:read`.
|
|
92
|
+
|
|
93
|
+
## Local worldsim dogfood config
|
|
94
|
+
|
|
95
|
+
While Wendell Cloud APIs are still taking shape, the runner can call the existing local `worldsim.services` layer directly:
|
|
96
|
+
|
|
97
|
+
```toml
|
|
98
|
+
project = "workspace-access-agent"
|
|
99
|
+
mode = "advisory"
|
|
100
|
+
world = "workspace_access_support"
|
|
101
|
+
scenario_pack = "smoke"
|
|
102
|
+
worldsim_input = "../configs/customer_inputs/workspace_access_support_input.json"
|
|
103
|
+
agent = "careful"
|
|
104
|
+
|
|
105
|
+
[gates]
|
|
106
|
+
suite_min_score = 0.80
|
|
107
|
+
scenario_min_score = 0.75
|
|
108
|
+
critical_failures_allowed = 0
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Supported local built-in agents are `careful` and `risky`.
|
wendell-0.1.0/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Wendell CLI
|
|
2
|
+
|
|
3
|
+
Wendell is a Python CLI for evaluating agents against Wendell-managed worlds and scenario packs.
|
|
4
|
+
|
|
5
|
+
The first version is advisory by default: it reports scores, captures traces, and returns a successful process exit unless the project explicitly enables blocking gates.
|
|
6
|
+
|
|
7
|
+
## Intended split
|
|
8
|
+
|
|
9
|
+
- Wendell system: creates worlds, versions scenario packs, owns rubrics, stores traces, and reports regressions.
|
|
10
|
+
- Wendell CI runner: runs in a repo or CI job, fetches or reads a scenario pack, invokes the customer's agent adapter, captures traces, evaluates gates, uploads results, and prints a concise summary.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uv tool install wendell
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Alternative installers:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pipx install wendell
|
|
22
|
+
python3 -m pip install --user wendell
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The package installs the `wendell` command. It also installs the temporary `local-wendell` alias for existing local dogfood scripts.
|
|
26
|
+
|
|
27
|
+
## Local development
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd wendell-ci
|
|
31
|
+
python -m venv .venv
|
|
32
|
+
source .venv/bin/activate
|
|
33
|
+
pip install -e ".[dev]"
|
|
34
|
+
pytest
|
|
35
|
+
wendell --help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Login
|
|
39
|
+
|
|
40
|
+
For local development, store an InkPass API key once:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
wendell login --api-url https://api.wendellai.com --api-key-stdin --validate
|
|
44
|
+
wendell auth status
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Credentials are stored at `~/.config/wendell/credentials.json` by default, or `$WENDELL_CONFIG_HOME/credentials.json` when set. The directory is created with `0700` permissions and the credentials file with `0600` permissions.
|
|
48
|
+
|
|
49
|
+
CI should continue to use `WENDELL_INKPASS_API_KEY`; environment variables take precedence over stored credentials.
|
|
50
|
+
|
|
51
|
+
## Minimal config
|
|
52
|
+
|
|
53
|
+
```toml
|
|
54
|
+
project = "support-agent"
|
|
55
|
+
mode = "advisory"
|
|
56
|
+
api_url = "https://api.wendell.example"
|
|
57
|
+
api_key_env = "WENDELL_INKPASS_API_KEY"
|
|
58
|
+
world = "world_support_ops_v1"
|
|
59
|
+
world_version = "2026-05-08.1"
|
|
60
|
+
scenario_pack = "smoke"
|
|
61
|
+
scenario_pack_version = "1.0.0"
|
|
62
|
+
agent_command = "python examples/simple_cli_agent.py"
|
|
63
|
+
upload_traces = true
|
|
64
|
+
|
|
65
|
+
[gates]
|
|
66
|
+
suite_min_score = 0.80
|
|
67
|
+
scenario_min_score = 0.75
|
|
68
|
+
critical_failures_allowed = 0
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The intended production flow is:
|
|
72
|
+
|
|
73
|
+
1. `local-wendell` runs on the developer machine or CI worker.
|
|
74
|
+
2. It fetches the pinned world, scenario pack, rubrics, and scoring contract from Wendell.
|
|
75
|
+
3. It invokes the agent locally through an adapter such as `agent_command`.
|
|
76
|
+
4. It captures local traces, redacts them, and uploads traces/results back to Wendell.
|
|
77
|
+
5. Advisory mode exits `0`; blocking mode exits nonzero only when explicitly enabled.
|
|
78
|
+
|
|
79
|
+
Remote uploads authenticate with an InkPass API key sent as `X-API-Key`. The key needs at least `wendell:worlds:read`, `wendell:runs:create`, and `wendell:runs:read`.
|
|
80
|
+
|
|
81
|
+
## Local worldsim dogfood config
|
|
82
|
+
|
|
83
|
+
While Wendell Cloud APIs are still taking shape, the runner can call the existing local `worldsim.services` layer directly:
|
|
84
|
+
|
|
85
|
+
```toml
|
|
86
|
+
project = "workspace-access-agent"
|
|
87
|
+
mode = "advisory"
|
|
88
|
+
world = "workspace_access_support"
|
|
89
|
+
scenario_pack = "smoke"
|
|
90
|
+
worldsim_input = "../configs/customer_inputs/workspace_access_support_input.json"
|
|
91
|
+
agent = "careful"
|
|
92
|
+
|
|
93
|
+
[gates]
|
|
94
|
+
suite_min_score = 0.80
|
|
95
|
+
scenario_min_score = 0.75
|
|
96
|
+
critical_failures_allowed = 0
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Supported local built-in agents are `careful` and `risky`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wendell"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Wendell CLI for agent world simulations, Park registration, and run uploads"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = []
|
|
12
|
+
license = { text = "Proprietary" }
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://www.wendellai.com"
|
|
16
|
+
Repository = "https://github.com/croppia/wendellai"
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
dev = ["pytest>=8"]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
wendell = "wendell_ci.cli:main"
|
|
23
|
+
local-wendell = "wendell_ci.cli:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["src"]
|
wendell-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wendell
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Wendell CLI for agent world simulations, Park registration, and run uploads
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Project-URL: Homepage, https://www.wendellai.com
|
|
7
|
+
Project-URL: Repository, https://github.com/croppia/wendellai
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
12
|
+
|
|
13
|
+
# Wendell CLI
|
|
14
|
+
|
|
15
|
+
Wendell is a Python CLI for evaluating agents against Wendell-managed worlds and scenario packs.
|
|
16
|
+
|
|
17
|
+
The first version is advisory by default: it reports scores, captures traces, and returns a successful process exit unless the project explicitly enables blocking gates.
|
|
18
|
+
|
|
19
|
+
## Intended split
|
|
20
|
+
|
|
21
|
+
- Wendell system: creates worlds, versions scenario packs, owns rubrics, stores traces, and reports regressions.
|
|
22
|
+
- Wendell CI runner: runs in a repo or CI job, fetches or reads a scenario pack, invokes the customer's agent adapter, captures traces, evaluates gates, uploads results, and prints a concise summary.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv tool install wendell
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Alternative installers:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pipx install wendell
|
|
34
|
+
python3 -m pip install --user wendell
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The package installs the `wendell` command. It also installs the temporary `local-wendell` alias for existing local dogfood scripts.
|
|
38
|
+
|
|
39
|
+
## Local development
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd wendell-ci
|
|
43
|
+
python -m venv .venv
|
|
44
|
+
source .venv/bin/activate
|
|
45
|
+
pip install -e ".[dev]"
|
|
46
|
+
pytest
|
|
47
|
+
wendell --help
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Login
|
|
51
|
+
|
|
52
|
+
For local development, store an InkPass API key once:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
wendell login --api-url https://api.wendellai.com --api-key-stdin --validate
|
|
56
|
+
wendell auth status
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Credentials are stored at `~/.config/wendell/credentials.json` by default, or `$WENDELL_CONFIG_HOME/credentials.json` when set. The directory is created with `0700` permissions and the credentials file with `0600` permissions.
|
|
60
|
+
|
|
61
|
+
CI should continue to use `WENDELL_INKPASS_API_KEY`; environment variables take precedence over stored credentials.
|
|
62
|
+
|
|
63
|
+
## Minimal config
|
|
64
|
+
|
|
65
|
+
```toml
|
|
66
|
+
project = "support-agent"
|
|
67
|
+
mode = "advisory"
|
|
68
|
+
api_url = "https://api.wendell.example"
|
|
69
|
+
api_key_env = "WENDELL_INKPASS_API_KEY"
|
|
70
|
+
world = "world_support_ops_v1"
|
|
71
|
+
world_version = "2026-05-08.1"
|
|
72
|
+
scenario_pack = "smoke"
|
|
73
|
+
scenario_pack_version = "1.0.0"
|
|
74
|
+
agent_command = "python examples/simple_cli_agent.py"
|
|
75
|
+
upload_traces = true
|
|
76
|
+
|
|
77
|
+
[gates]
|
|
78
|
+
suite_min_score = 0.80
|
|
79
|
+
scenario_min_score = 0.75
|
|
80
|
+
critical_failures_allowed = 0
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The intended production flow is:
|
|
84
|
+
|
|
85
|
+
1. `local-wendell` runs on the developer machine or CI worker.
|
|
86
|
+
2. It fetches the pinned world, scenario pack, rubrics, and scoring contract from Wendell.
|
|
87
|
+
3. It invokes the agent locally through an adapter such as `agent_command`.
|
|
88
|
+
4. It captures local traces, redacts them, and uploads traces/results back to Wendell.
|
|
89
|
+
5. Advisory mode exits `0`; blocking mode exits nonzero only when explicitly enabled.
|
|
90
|
+
|
|
91
|
+
Remote uploads authenticate with an InkPass API key sent as `X-API-Key`. The key needs at least `wendell:worlds:read`, `wendell:runs:create`, and `wendell:runs:read`.
|
|
92
|
+
|
|
93
|
+
## Local worldsim dogfood config
|
|
94
|
+
|
|
95
|
+
While Wendell Cloud APIs are still taking shape, the runner can call the existing local `worldsim.services` layer directly:
|
|
96
|
+
|
|
97
|
+
```toml
|
|
98
|
+
project = "workspace-access-agent"
|
|
99
|
+
mode = "advisory"
|
|
100
|
+
world = "workspace_access_support"
|
|
101
|
+
scenario_pack = "smoke"
|
|
102
|
+
worldsim_input = "../configs/customer_inputs/workspace_access_support_input.json"
|
|
103
|
+
agent = "careful"
|
|
104
|
+
|
|
105
|
+
[gates]
|
|
106
|
+
suite_min_score = 0.80
|
|
107
|
+
scenario_min_score = 0.75
|
|
108
|
+
critical_failures_allowed = 0
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Supported local built-in agents are `careful` and `risky`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/wendell.egg-info/PKG-INFO
|
|
4
|
+
src/wendell.egg-info/SOURCES.txt
|
|
5
|
+
src/wendell.egg-info/dependency_links.txt
|
|
6
|
+
src/wendell.egg-info/entry_points.txt
|
|
7
|
+
src/wendell.egg-info/requires.txt
|
|
8
|
+
src/wendell.egg-info/top_level.txt
|
|
9
|
+
src/wendell_ci/__init__.py
|
|
10
|
+
src/wendell_ci/auth.py
|
|
11
|
+
src/wendell_ci/cli.py
|
|
12
|
+
src/wendell_ci/config.py
|
|
13
|
+
src/wendell_ci/gates.py
|
|
14
|
+
src/wendell_ci/models.py
|
|
15
|
+
src/wendell_ci/remote_client.py
|
|
16
|
+
src/wendell_ci/trace.py
|
|
17
|
+
src/wendell_ci/worldsim_client.py
|
|
18
|
+
tests/test_auth.py
|
|
19
|
+
tests/test_cli.py
|
|
20
|
+
tests/test_command_agent_integration.py
|
|
21
|
+
tests/test_config.py
|
|
22
|
+
tests/test_gates.py
|
|
23
|
+
tests/test_remote_client.py
|
|
24
|
+
tests/test_worldsim_integration.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
wendell_ci
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Wendell CI runner package."""
|
|
2
|
+
|
|
3
|
+
from .config import GateConfig, RunnerConfig
|
|
4
|
+
from .gates import GateDecision, evaluate_gates
|
|
5
|
+
from .models import ScenarioResult, SuiteResult
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"GateConfig",
|
|
9
|
+
"GateDecision",
|
|
10
|
+
"RunnerConfig",
|
|
11
|
+
"ScenarioResult",
|
|
12
|
+
"SuiteResult",
|
|
13
|
+
"evaluate_gates",
|
|
14
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
CONFIG_HOME_ENV = "WENDELL_CONFIG_HOME"
|
|
11
|
+
DEFAULT_PROFILE = "default"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class StoredCredentials:
|
|
16
|
+
api_key: str
|
|
17
|
+
profile: str = DEFAULT_PROFILE
|
|
18
|
+
api_url: str | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def config_dir() -> Path:
|
|
22
|
+
configured = os.environ.get(CONFIG_HOME_ENV)
|
|
23
|
+
if configured:
|
|
24
|
+
return Path(configured).expanduser()
|
|
25
|
+
xdg_home = os.environ.get("XDG_CONFIG_HOME")
|
|
26
|
+
if xdg_home:
|
|
27
|
+
return Path(xdg_home).expanduser() / "wendell"
|
|
28
|
+
return Path.home() / ".config" / "wendell"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def credentials_path() -> Path:
|
|
32
|
+
return config_dir() / "credentials.json"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def store_credentials(credentials: StoredCredentials) -> Path:
|
|
36
|
+
directory = config_dir()
|
|
37
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
directory.chmod(0o700)
|
|
39
|
+
path = credentials_path()
|
|
40
|
+
payload: dict[str, Any] = {
|
|
41
|
+
"version": 1,
|
|
42
|
+
"profiles": {
|
|
43
|
+
credentials.profile: {
|
|
44
|
+
"api_key": credentials.api_key,
|
|
45
|
+
"api_url": credentials.api_url,
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"current_profile": credentials.profile,
|
|
49
|
+
}
|
|
50
|
+
path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
51
|
+
path.chmod(0o600)
|
|
52
|
+
return path
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def load_credentials(profile: str | None = None) -> StoredCredentials | None:
|
|
56
|
+
path = credentials_path()
|
|
57
|
+
if not path.exists():
|
|
58
|
+
return None
|
|
59
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
60
|
+
selected = profile or str(data.get("current_profile") or DEFAULT_PROFILE)
|
|
61
|
+
profiles = data.get("profiles")
|
|
62
|
+
if not isinstance(profiles, dict):
|
|
63
|
+
return None
|
|
64
|
+
profile_data = profiles.get(selected)
|
|
65
|
+
if not isinstance(profile_data, dict):
|
|
66
|
+
return None
|
|
67
|
+
api_key = profile_data.get("api_key")
|
|
68
|
+
if not isinstance(api_key, str) or not api_key:
|
|
69
|
+
return None
|
|
70
|
+
api_url = profile_data.get("api_url")
|
|
71
|
+
return StoredCredentials(
|
|
72
|
+
api_key=api_key,
|
|
73
|
+
profile=selected,
|
|
74
|
+
api_url=api_url if isinstance(api_url, str) and api_url else None,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def delete_credentials() -> bool:
|
|
79
|
+
path = credentials_path()
|
|
80
|
+
if not path.exists():
|
|
81
|
+
return False
|
|
82
|
+
path.unlink()
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def resolve_api_key(api_key_env: str) -> str | None:
|
|
87
|
+
env_value = os.environ.get(api_key_env)
|
|
88
|
+
if env_value:
|
|
89
|
+
return env_value
|
|
90
|
+
credentials = load_credentials()
|
|
91
|
+
return None if credentials is None else credentials.api_key
|