akprx 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.
- akprx-0.1.0/LICENSE +21 -0
- akprx-0.1.0/PKG-INFO +233 -0
- akprx-0.1.0/README.md +179 -0
- akprx-0.1.0/akprx/__init__.py +7 -0
- akprx-0.1.0/akprx/cli/__init__.py +0 -0
- akprx-0.1.0/akprx/cli/adaptor.py +96 -0
- akprx-0.1.0/akprx/cli/call.py +61 -0
- akprx-0.1.0/akprx/cli/http.py +57 -0
- akprx-0.1.0/akprx/cli/key.py +81 -0
- akprx-0.1.0/akprx/cli/main.py +76 -0
- akprx-0.1.0/akprx/cli/status.py +80 -0
- akprx-0.1.0/akprx/config.py +35 -0
- akprx-0.1.0/akprx/server/__init__.py +0 -0
- akprx-0.1.0/akprx/server/adaptors.py +103 -0
- akprx-0.1.0/akprx/server/app.py +21 -0
- akprx-0.1.0/akprx/server/call.py +78 -0
- akprx-0.1.0/akprx/server/secrets.py +70 -0
- akprx-0.1.0/akprx/setup_cmd.py +166 -0
- akprx-0.1.0/akprx/store/__init__.py +0 -0
- akprx-0.1.0/akprx/store/adaptors.py +45 -0
- akprx-0.1.0/akprx/store/secrets.py +60 -0
- akprx-0.1.0/akprx.egg-info/PKG-INFO +233 -0
- akprx-0.1.0/akprx.egg-info/SOURCES.txt +27 -0
- akprx-0.1.0/akprx.egg-info/dependency_links.txt +1 -0
- akprx-0.1.0/akprx.egg-info/entry_points.txt +2 -0
- akprx-0.1.0/akprx.egg-info/requires.txt +4 -0
- akprx-0.1.0/akprx.egg-info/top_level.txt +1 -0
- akprx-0.1.0/pyproject.toml +46 -0
- akprx-0.1.0/setup.cfg +4 -0
akprx-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexey Floppa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
akprx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: akprx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent Key Proxy — credential broker daemon for AI agents on Linux
|
|
5
|
+
Author-email: Alexey Floppa <you@example.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Alexey Floppa
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/belka0fficial/akprx
|
|
29
|
+
Project-URL: Repository, https://github.com/belka0fficial/akprx
|
|
30
|
+
Project-URL: Issues, https://github.com/belka0fficial/akprx/issues
|
|
31
|
+
Project-URL: Changelog, https://github.com/belka0fficial/akprx/blob/main/CHANGELOG.md
|
|
32
|
+
Keywords: ai-agent,api-proxy,credentials,secrets,broker,daemon,linux
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Environment :: No Input/Output (Daemon)
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: Intended Audience :: System Administrators
|
|
37
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
38
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
39
|
+
Classifier: Programming Language :: Python :: 3
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Topic :: Security
|
|
44
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
45
|
+
Classifier: Topic :: System :: Systems Administration
|
|
46
|
+
Requires-Python: >=3.10
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
License-File: LICENSE
|
|
49
|
+
Requires-Dist: fastapi>=0.110.0
|
|
50
|
+
Requires-Dist: uvicorn>=0.29.0
|
|
51
|
+
Requires-Dist: httpx>=0.27.0
|
|
52
|
+
Requires-Dist: pydantic>=2.0.0
|
|
53
|
+
Dynamic: license-file
|
|
54
|
+
|
|
55
|
+
# akprx
|
|
56
|
+
|
|
57
|
+
**Agent Key Proxy** — credential broker daemon for AI agents on Linux.
|
|
58
|
+
|
|
59
|
+
Your agent needs to call external APIs. You don't want it holding the keys.
|
|
60
|
+
`akprx` sits in the middle: the agent asks it to make calls, it injects the credentials, returns only the result. The agent never sees a token.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
agent → POST /call → akprx injects key → GitHub API
|
|
64
|
+
↑
|
|
65
|
+
key never crosses this boundary
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## The problem
|
|
71
|
+
|
|
72
|
+
When an AI agent has direct access to API keys:
|
|
73
|
+
|
|
74
|
+
- Keys live in env vars or config files the agent can read
|
|
75
|
+
- A prompt injection attack can extract them
|
|
76
|
+
- A compromised plugin leaks every credential the agent holds
|
|
77
|
+
- You have no visibility into what the agent calls or with what
|
|
78
|
+
|
|
79
|
+
## The solution
|
|
80
|
+
|
|
81
|
+
`akprx` runs as a systemd daemon on `127.0.0.1` only — unreachable from outside the machine. The agent user can talk to it over HTTP. It cannot read the secret files. It cannot read the environment of the broker process. It gets results, never credentials.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Install
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install akprx
|
|
89
|
+
sudo akprx setup
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
That's it. `setup` creates the system user, directories, permissions, and the systemd service. Nothing else to configure.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Quickstart
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# check everything is running
|
|
100
|
+
akprx status
|
|
101
|
+
|
|
102
|
+
# store an API key (value is hidden input)
|
|
103
|
+
akprx key add GITHUB_TOKEN
|
|
104
|
+
|
|
105
|
+
# register a provider
|
|
106
|
+
akprx adaptor add
|
|
107
|
+
|
|
108
|
+
# make a call through the proxy
|
|
109
|
+
akprx call github /repos/youruser/yourrepo
|
|
110
|
+
|
|
111
|
+
# POST with a body
|
|
112
|
+
akprx call github /repos/youruser/yourrepo/issues \
|
|
113
|
+
--method POST \
|
|
114
|
+
--data '{"title": "bug report", "body": "details here"}'
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Commands
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
akprx status service health, keys, adaptors
|
|
123
|
+
|
|
124
|
+
akprx adaptor list list registered adaptors
|
|
125
|
+
akprx adaptor add register a new adaptor (interactive)
|
|
126
|
+
akprx adaptor show <name> show full config of one adaptor
|
|
127
|
+
akprx adaptor remove <name> remove an adaptor
|
|
128
|
+
|
|
129
|
+
akprx key list list stored key names (no values shown)
|
|
130
|
+
akprx key add <name> store a new key (value prompted, hidden)
|
|
131
|
+
akprx key remove <name> remove a key
|
|
132
|
+
akprx key rotate <name> replace a key value (prompted, hidden)
|
|
133
|
+
|
|
134
|
+
akprx call <adaptor> <path> GET request through adaptor
|
|
135
|
+
akprx call <adaptor> <path> --method POST --data '{...}'
|
|
136
|
+
|
|
137
|
+
akprx setup first-time setup (run with sudo)
|
|
138
|
+
akprx version print version
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## How adaptors work
|
|
144
|
+
|
|
145
|
+
An adaptor is a registered API provider. It stores:
|
|
146
|
+
|
|
147
|
+
- `base_url` — the root URL of the API
|
|
148
|
+
- `secret_env` — which env var holds the token
|
|
149
|
+
- `auth_header` / `auth_prefix` — how to inject the credential
|
|
150
|
+
- `extra_headers` — any static headers the API requires
|
|
151
|
+
|
|
152
|
+
Once registered, the agent can call any endpoint on that provider freely.
|
|
153
|
+
To add a new provider, run `akprx adaptor add` and answer the prompts.
|
|
154
|
+
|
|
155
|
+
### Example — GitHub
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
akprx adaptor add
|
|
159
|
+
# name: github
|
|
160
|
+
# base url: https://api.github.com
|
|
161
|
+
# secret env var: GITHUB_TOKEN
|
|
162
|
+
# auth header: Authorization
|
|
163
|
+
# auth prefix: Bearer
|
|
164
|
+
# extra headers:
|
|
165
|
+
# Accept: application/vnd.github+json
|
|
166
|
+
# X-GitHub-Api-Version: 2022-11-28
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## File layout
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
/srv/akprx/
|
|
175
|
+
├── adaptors.json registered providers owner: akprx:akprx 600
|
|
176
|
+
└── secrets/
|
|
177
|
+
└── secrets.env API keys owner: akprx:akprx 600
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The `akprx` system user owns all files.
|
|
181
|
+
The agent user cannot read either file.
|
|
182
|
+
Root can manage everything via sudo.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Security model
|
|
187
|
+
|
|
188
|
+
| What | Who can access |
|
|
189
|
+
|-----------------------------|----------------------------|
|
|
190
|
+
| `secrets.env` | `akprx` user only |
|
|
191
|
+
| `adaptors.json` | `akprx` user only |
|
|
192
|
+
| broker HTTP API | any local process |
|
|
193
|
+
| secret values via HTTP API | nobody — never returned |
|
|
194
|
+
| key names via HTTP API | any local process |
|
|
195
|
+
| port 8080 from outside VPS | nobody — loopback only |
|
|
196
|
+
|
|
197
|
+
The security guarantee is structural — enforced by Linux file permissions and network binding, not application-level checks.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Who this is for
|
|
202
|
+
|
|
203
|
+
- Running a self-hosted AI agent (OpenClaw, Claude Code, or similar) on a Linux VPS
|
|
204
|
+
- Want the agent to call external APIs on your behalf
|
|
205
|
+
- Don't want the agent to hold or see raw credentials
|
|
206
|
+
- Want a simple CLI to manage everything without editing config files
|
|
207
|
+
|
|
208
|
+
## Who this is NOT for
|
|
209
|
+
|
|
210
|
+
- Enterprise teams needing secret rotation, SSO, or audit compliance → use HashiCorp Vault
|
|
211
|
+
- Cloud-native setups → use AWS Secrets Manager
|
|
212
|
+
- Docker-only workflows → use OneCLI
|
|
213
|
+
- Multi-user access control → out of scope
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Requirements
|
|
218
|
+
|
|
219
|
+
- Linux (systemd-based)
|
|
220
|
+
- Python 3.10+
|
|
221
|
+
- Root access for initial setup
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT — see [LICENSE](LICENSE)
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Author
|
|
232
|
+
|
|
233
|
+
[Alexey Floppa](https://github.com/belka0fficial)
|
akprx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# akprx
|
|
2
|
+
|
|
3
|
+
**Agent Key Proxy** — credential broker daemon for AI agents on Linux.
|
|
4
|
+
|
|
5
|
+
Your agent needs to call external APIs. You don't want it holding the keys.
|
|
6
|
+
`akprx` sits in the middle: the agent asks it to make calls, it injects the credentials, returns only the result. The agent never sees a token.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
agent → POST /call → akprx injects key → GitHub API
|
|
10
|
+
↑
|
|
11
|
+
key never crosses this boundary
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## The problem
|
|
17
|
+
|
|
18
|
+
When an AI agent has direct access to API keys:
|
|
19
|
+
|
|
20
|
+
- Keys live in env vars or config files the agent can read
|
|
21
|
+
- A prompt injection attack can extract them
|
|
22
|
+
- A compromised plugin leaks every credential the agent holds
|
|
23
|
+
- You have no visibility into what the agent calls or with what
|
|
24
|
+
|
|
25
|
+
## The solution
|
|
26
|
+
|
|
27
|
+
`akprx` runs as a systemd daemon on `127.0.0.1` only — unreachable from outside the machine. The agent user can talk to it over HTTP. It cannot read the secret files. It cannot read the environment of the broker process. It gets results, never credentials.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install akprx
|
|
35
|
+
sudo akprx setup
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
That's it. `setup` creates the system user, directories, permissions, and the systemd service. Nothing else to configure.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# check everything is running
|
|
46
|
+
akprx status
|
|
47
|
+
|
|
48
|
+
# store an API key (value is hidden input)
|
|
49
|
+
akprx key add GITHUB_TOKEN
|
|
50
|
+
|
|
51
|
+
# register a provider
|
|
52
|
+
akprx adaptor add
|
|
53
|
+
|
|
54
|
+
# make a call through the proxy
|
|
55
|
+
akprx call github /repos/youruser/yourrepo
|
|
56
|
+
|
|
57
|
+
# POST with a body
|
|
58
|
+
akprx call github /repos/youruser/yourrepo/issues \
|
|
59
|
+
--method POST \
|
|
60
|
+
--data '{"title": "bug report", "body": "details here"}'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Commands
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
akprx status service health, keys, adaptors
|
|
69
|
+
|
|
70
|
+
akprx adaptor list list registered adaptors
|
|
71
|
+
akprx adaptor add register a new adaptor (interactive)
|
|
72
|
+
akprx adaptor show <name> show full config of one adaptor
|
|
73
|
+
akprx adaptor remove <name> remove an adaptor
|
|
74
|
+
|
|
75
|
+
akprx key list list stored key names (no values shown)
|
|
76
|
+
akprx key add <name> store a new key (value prompted, hidden)
|
|
77
|
+
akprx key remove <name> remove a key
|
|
78
|
+
akprx key rotate <name> replace a key value (prompted, hidden)
|
|
79
|
+
|
|
80
|
+
akprx call <adaptor> <path> GET request through adaptor
|
|
81
|
+
akprx call <adaptor> <path> --method POST --data '{...}'
|
|
82
|
+
|
|
83
|
+
akprx setup first-time setup (run with sudo)
|
|
84
|
+
akprx version print version
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## How adaptors work
|
|
90
|
+
|
|
91
|
+
An adaptor is a registered API provider. It stores:
|
|
92
|
+
|
|
93
|
+
- `base_url` — the root URL of the API
|
|
94
|
+
- `secret_env` — which env var holds the token
|
|
95
|
+
- `auth_header` / `auth_prefix` — how to inject the credential
|
|
96
|
+
- `extra_headers` — any static headers the API requires
|
|
97
|
+
|
|
98
|
+
Once registered, the agent can call any endpoint on that provider freely.
|
|
99
|
+
To add a new provider, run `akprx adaptor add` and answer the prompts.
|
|
100
|
+
|
|
101
|
+
### Example — GitHub
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
akprx adaptor add
|
|
105
|
+
# name: github
|
|
106
|
+
# base url: https://api.github.com
|
|
107
|
+
# secret env var: GITHUB_TOKEN
|
|
108
|
+
# auth header: Authorization
|
|
109
|
+
# auth prefix: Bearer
|
|
110
|
+
# extra headers:
|
|
111
|
+
# Accept: application/vnd.github+json
|
|
112
|
+
# X-GitHub-Api-Version: 2022-11-28
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## File layout
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
/srv/akprx/
|
|
121
|
+
├── adaptors.json registered providers owner: akprx:akprx 600
|
|
122
|
+
└── secrets/
|
|
123
|
+
└── secrets.env API keys owner: akprx:akprx 600
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The `akprx` system user owns all files.
|
|
127
|
+
The agent user cannot read either file.
|
|
128
|
+
Root can manage everything via sudo.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Security model
|
|
133
|
+
|
|
134
|
+
| What | Who can access |
|
|
135
|
+
|-----------------------------|----------------------------|
|
|
136
|
+
| `secrets.env` | `akprx` user only |
|
|
137
|
+
| `adaptors.json` | `akprx` user only |
|
|
138
|
+
| broker HTTP API | any local process |
|
|
139
|
+
| secret values via HTTP API | nobody — never returned |
|
|
140
|
+
| key names via HTTP API | any local process |
|
|
141
|
+
| port 8080 from outside VPS | nobody — loopback only |
|
|
142
|
+
|
|
143
|
+
The security guarantee is structural — enforced by Linux file permissions and network binding, not application-level checks.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Who this is for
|
|
148
|
+
|
|
149
|
+
- Running a self-hosted AI agent (OpenClaw, Claude Code, or similar) on a Linux VPS
|
|
150
|
+
- Want the agent to call external APIs on your behalf
|
|
151
|
+
- Don't want the agent to hold or see raw credentials
|
|
152
|
+
- Want a simple CLI to manage everything without editing config files
|
|
153
|
+
|
|
154
|
+
## Who this is NOT for
|
|
155
|
+
|
|
156
|
+
- Enterprise teams needing secret rotation, SSO, or audit compliance → use HashiCorp Vault
|
|
157
|
+
- Cloud-native setups → use AWS Secrets Manager
|
|
158
|
+
- Docker-only workflows → use OneCLI
|
|
159
|
+
- Multi-user access control → out of scope
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Requirements
|
|
164
|
+
|
|
165
|
+
- Linux (systemd-based)
|
|
166
|
+
- Python 3.10+
|
|
167
|
+
- Root access for initial setup
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT — see [LICENSE](LICENSE)
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Author
|
|
178
|
+
|
|
179
|
+
[Alexey Floppa](https://github.com/belka0fficial)
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
akprx.cli.adaptor
|
|
3
|
+
Subcommands for managing adaptors.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from akprx.cli import http
|
|
9
|
+
|
|
10
|
+
USAGE = """\
|
|
11
|
+
usage:
|
|
12
|
+
akprx adaptor list
|
|
13
|
+
akprx adaptor add
|
|
14
|
+
akprx adaptor show <name>
|
|
15
|
+
akprx adaptor remove <name>
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _list() -> None:
|
|
20
|
+
data = http.get("/adaptors")
|
|
21
|
+
if data["total"] == 0:
|
|
22
|
+
print("no adaptors registered.")
|
|
23
|
+
print("add one: akprx adaptor add")
|
|
24
|
+
return
|
|
25
|
+
for a in data["adaptors"]:
|
|
26
|
+
sym = "✓" if a["status"] == "ok" else "✗"
|
|
27
|
+
name = a["name"].ljust(16)
|
|
28
|
+
url = a["config"]["base_url"].ljust(40)
|
|
29
|
+
state = "ok" if a["config"]["secret_loaded"] else "MISSING SECRET"
|
|
30
|
+
print(f" {sym} {name} {url} {state}")
|
|
31
|
+
for issue in a.get("issues", []):
|
|
32
|
+
print(f" ! {issue}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _show(name: str) -> None:
|
|
36
|
+
http.pp(http.get(f"/adaptors/{name}"))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _add() -> None:
|
|
40
|
+
print("register a new adaptor.\n")
|
|
41
|
+
name = input(" name (e.g. github): ").strip()
|
|
42
|
+
base_url = input(" base url: ").strip()
|
|
43
|
+
secret_env = input(" secret env var name: ").strip()
|
|
44
|
+
auth_header = input(" auth header [Authorization]: ").strip() or "Authorization"
|
|
45
|
+
auth_prefix = input(" auth prefix [Bearer]: ").strip() or "Bearer"
|
|
46
|
+
|
|
47
|
+
extra: dict[str, str] = {}
|
|
48
|
+
print(" extra headers (blank name to finish):")
|
|
49
|
+
while True:
|
|
50
|
+
k = input(" header name: ").strip()
|
|
51
|
+
if not k:
|
|
52
|
+
break
|
|
53
|
+
v = input(" header value: ").strip()
|
|
54
|
+
extra[k] = v
|
|
55
|
+
|
|
56
|
+
result = http.post("/adaptors", {
|
|
57
|
+
"name": name,
|
|
58
|
+
"base_url": base_url,
|
|
59
|
+
"secret_env": secret_env,
|
|
60
|
+
"auth_header": auth_header,
|
|
61
|
+
"auth_prefix": auth_prefix,
|
|
62
|
+
"extra_headers": extra,
|
|
63
|
+
})
|
|
64
|
+
http.pp(result)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _remove(name: str) -> None:
|
|
68
|
+
result = http.delete(f"/adaptors/{name}")
|
|
69
|
+
http.pp(result)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def run(args: list[str]) -> None:
|
|
73
|
+
if not args or args[0] in ("-h", "--help", "help"):
|
|
74
|
+
print(USAGE)
|
|
75
|
+
sys.exit(0)
|
|
76
|
+
|
|
77
|
+
sub = args[0]
|
|
78
|
+
|
|
79
|
+
if sub == "list":
|
|
80
|
+
_list()
|
|
81
|
+
elif sub == "add":
|
|
82
|
+
_add()
|
|
83
|
+
elif sub == "show":
|
|
84
|
+
if len(args) < 2:
|
|
85
|
+
print("usage: akprx adaptor show <name>", file=sys.stderr)
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
_show(args[1])
|
|
88
|
+
elif sub == "remove":
|
|
89
|
+
if len(args) < 2:
|
|
90
|
+
print("usage: akprx adaptor remove <name>", file=sys.stderr)
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
_remove(args[1])
|
|
93
|
+
else:
|
|
94
|
+
print(f"akprx adaptor: unknown subcommand '{sub}'", file=sys.stderr)
|
|
95
|
+
print(USAGE, file=sys.stderr)
|
|
96
|
+
sys.exit(1)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
akprx.cli.call
|
|
3
|
+
Fire an authenticated API call through a registered adaptor.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from akprx.cli import http
|
|
10
|
+
|
|
11
|
+
USAGE = """\
|
|
12
|
+
usage:
|
|
13
|
+
akprx call <adaptor> <path>
|
|
14
|
+
akprx call <adaptor> <path> --method POST --data '{...}'
|
|
15
|
+
|
|
16
|
+
examples:
|
|
17
|
+
akprx call github /repos/belka0fficial/belka.life
|
|
18
|
+
akprx call github /repos/belka0fficial/belka.life/issues --method POST --data '{"title":"bug"}'
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run(args: list[str]) -> None:
|
|
23
|
+
if not args or args[0] in ("-h", "--help", "help"):
|
|
24
|
+
print(USAGE)
|
|
25
|
+
sys.exit(0)
|
|
26
|
+
|
|
27
|
+
if len(args) < 2:
|
|
28
|
+
print("usage: akprx call <adaptor> <path>", file=sys.stderr)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
adaptor = args[0]
|
|
32
|
+
path = args[1]
|
|
33
|
+
method = "GET"
|
|
34
|
+
data = None
|
|
35
|
+
|
|
36
|
+
i = 2
|
|
37
|
+
while i < len(args):
|
|
38
|
+
if args[i] == "--method" and i + 1 < len(args):
|
|
39
|
+
method = args[i + 1].upper()
|
|
40
|
+
i += 2
|
|
41
|
+
elif args[i] == "--data" and i + 1 < len(args):
|
|
42
|
+
data = args[i + 1]
|
|
43
|
+
i += 2
|
|
44
|
+
else:
|
|
45
|
+
i += 1
|
|
46
|
+
|
|
47
|
+
body: dict = {
|
|
48
|
+
"adaptor": adaptor,
|
|
49
|
+
"method": method,
|
|
50
|
+
"path": path,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if data:
|
|
54
|
+
try:
|
|
55
|
+
body["payload"] = json.loads(data)
|
|
56
|
+
except json.JSONDecodeError:
|
|
57
|
+
print("error: --data is not valid JSON", file=sys.stderr)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
result = http.post("/call", body)
|
|
61
|
+
http.pp(result)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
akprx.cli.http
|
|
3
|
+
Shared HTTP client for CLI commands.
|
|
4
|
+
All requests go to the local broker.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
import urllib.error
|
|
10
|
+
import urllib.request
|
|
11
|
+
|
|
12
|
+
from akprx.config import BASE_URL
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def request(method: str, path: str, body: dict | None = None) -> dict:
|
|
16
|
+
url = BASE_URL + path
|
|
17
|
+
data = json.dumps(body).encode() if body is not None else None
|
|
18
|
+
headers = {"Content-Type": "application/json"} if data else {}
|
|
19
|
+
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
20
|
+
try:
|
|
21
|
+
with urllib.request.urlopen(req) as resp:
|
|
22
|
+
return json.loads(resp.read())
|
|
23
|
+
except urllib.error.HTTPError as e:
|
|
24
|
+
raw = e.read().decode()
|
|
25
|
+
try:
|
|
26
|
+
detail = json.loads(raw).get("detail", raw)
|
|
27
|
+
except Exception:
|
|
28
|
+
detail = raw
|
|
29
|
+
print(f"error {e.code}: {detail}", file=sys.stderr)
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
except urllib.error.URLError:
|
|
32
|
+
print(
|
|
33
|
+
f"error: cannot reach akprx at {BASE_URL}\n"
|
|
34
|
+
" is the service running? try: systemctl status akprx",
|
|
35
|
+
file=sys.stderr,
|
|
36
|
+
)
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get(path: str) -> dict:
|
|
41
|
+
return request("GET", path)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def post(path: str, body: dict) -> dict:
|
|
45
|
+
return request("POST", path, body)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def put(path: str, body: dict) -> dict:
|
|
49
|
+
return request("PUT", path, body)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def delete(path: str) -> dict:
|
|
53
|
+
return request("DELETE", path)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def pp(data: dict) -> None:
|
|
57
|
+
print(json.dumps(data, indent=2))
|