proxyagent 0.7.0__tar.gz → 0.8.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.
- {proxyagent-0.7.0 → proxyagent-0.8.0}/PKG-INFO +6 -6
- {proxyagent-0.7.0 → proxyagent-0.8.0}/README.md +5 -5
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/__init__.py +1 -1
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/ui/index.html +63 -100
- {proxyagent-0.7.0 → proxyagent-0.8.0}/pyproject.toml +1 -1
- {proxyagent-0.7.0 → proxyagent-0.8.0}/.gitignore +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/aliases.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/cli.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/config.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/crypto.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/db.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/harness.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/pricing.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/providers.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/security.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/server.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/signers.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/store.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/proxyagent/tools.py +0 -0
- {proxyagent-0.7.0 → proxyagent-0.8.0}/tests/test_proxy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxyagent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Run any agent (Claude, Codex, custom) on any machine — with no API key on the machine. A secure, self-hosted proxy for models and tools.
|
|
5
5
|
Project-URL: Homepage, https://github.com/teddyoweh/proxyagent
|
|
6
6
|
Author-email: Spawn Labs <teddy@spawnlabs.ai>
|
|
@@ -99,13 +99,13 @@ claude -p "ship it"
|
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
## The dashboard
|
|
102
|
-
`proxyagent serve` ships a
|
|
102
|
+
`proxyagent serve` ships a dashboard at `/` (reveal the admin token with
|
|
103
103
|
`proxyagent admin-token`):
|
|
104
104
|
|
|
105
|
-
- **
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- **Machine tokens** — mint (scoped/TTL), list, revoke.
|
|
105
|
+
- **Access keys** — the credentials you create. Each is a provider + an auth type
|
|
106
|
+
(Anthropic · API key, Anthropic · Bedrock, OpenAI · Azure, …); pick the type, enter the
|
|
107
|
+
key/fields, done. Listed with provider logo · auth type · masked key · remove.
|
|
108
|
+
- **Machine tokens** — mint (scoped / TTL / budget), list, revoke.
|
|
109
109
|
- **Model routing** — add/remove model remaps (e.g. `* → mock` for offline).
|
|
110
110
|
- **Activity** — live request log with usage + cost, and headline stats.
|
|
111
111
|
|
|
@@ -67,13 +67,13 @@ claude -p "ship it"
|
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
## The dashboard
|
|
70
|
-
`proxyagent serve` ships a
|
|
70
|
+
`proxyagent serve` ships a dashboard at `/` (reveal the admin token with
|
|
71
71
|
`proxyagent admin-token`):
|
|
72
72
|
|
|
73
|
-
- **
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- **Machine tokens** — mint (scoped/TTL), list, revoke.
|
|
73
|
+
- **Access keys** — the credentials you create. Each is a provider + an auth type
|
|
74
|
+
(Anthropic · API key, Anthropic · Bedrock, OpenAI · Azure, …); pick the type, enter the
|
|
75
|
+
key/fields, done. Listed with provider logo · auth type · masked key · remove.
|
|
76
|
+
- **Machine tokens** — mint (scoped / TTL / budget), list, revoke.
|
|
77
77
|
- **Model routing** — add/remove model remaps (e.g. `* → mock` for offline).
|
|
78
78
|
- **Activity** — live request log with usage + cost, and headline stats.
|
|
79
79
|
|
|
@@ -12,42 +12,33 @@
|
|
|
12
12
|
header{position:sticky;top:0;z-index:10;display:flex;align-items:center;justify-content:space-between;padding:16px 28px;background:rgba(8,9,11,.72);backdrop-filter:blur(14px)}
|
|
13
13
|
.brand{display:flex;align-items:center;gap:10px;font-weight:650;font-size:16px}
|
|
14
14
|
.logo{width:27px;height:27px;border-radius:8px;background:linear-gradient(135deg,#34d39e,#3b82f6);display:grid;place-items:center;color:#04130d;font-weight:800}
|
|
15
|
-
main{max-width:
|
|
15
|
+
main{max-width:1040px;margin:0 auto;padding:8px 28px 60px}
|
|
16
16
|
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:14px;margin-bottom:26px}
|
|
17
17
|
.card{background:var(--panel);border-radius:18px;padding:20px}
|
|
18
18
|
.stat .n{font-size:31px;font-weight:700;letter-spacing:-.02em}.stat .l{color:var(--dim);font-size:11px;text-transform:uppercase;letter-spacing:.09em;margin-top:6px}
|
|
19
19
|
.tabs{display:flex;gap:6px;margin-bottom:20px}
|
|
20
20
|
.tab{padding:8px 15px;color:var(--dim);cursor:pointer;font-weight:560;border-radius:10px}
|
|
21
|
-
.tab:hover{color:var(--txt);background:var(--panel)}
|
|
22
|
-
.
|
|
23
|
-
.
|
|
24
|
-
.prov{background:var(--panel);border-radius:18px;padding:18px;transition:background .15s,transform .15s}
|
|
25
|
-
.prov:hover{background:#1a1d23}
|
|
26
|
-
.prov .top{display:flex;align-items:center;gap:13px}
|
|
27
|
-
.tile{width:42px;height:42px;border-radius:12px;display:grid;place-items:center;flex:none;font-weight:800;font-size:18px;overflow:hidden}
|
|
28
|
-
.tile img{width:23px;height:23px;display:block}.tile .fbm{display:none}.tile.fb img{display:none}.tile.fb .fbm{display:flex;align-items:center;justify-content:center}
|
|
29
|
-
.prov h3{margin:0;font-size:15px;font-weight:640}.prov .ep{color:var(--dim);font-size:11.5px;margin-top:1px}
|
|
30
|
-
.badges{display:flex;gap:6px;flex-wrap:wrap;margin:13px 0}
|
|
21
|
+
.tab:hover,.tab.on{color:var(--txt);background:var(--panel)}
|
|
22
|
+
.tile{width:38px;height:38px;border-radius:11px;display:grid;place-items:center;flex:none;font-weight:800;font-size:16px;overflow:hidden}
|
|
23
|
+
.tile img{width:21px;height:21px;display:block}.tile .fbm{display:none}.tile.fb img{display:none}.tile.fb .fbm{display:flex;align-items:center;justify-content:center}
|
|
31
24
|
.badge{font-size:10.5px;padding:3px 9px;border-radius:8px;background:var(--soft);color:var(--dim);text-transform:uppercase;letter-spacing:.04em}
|
|
32
25
|
.badge.on{background:rgba(52,211,158,.14);color:var(--grn)}
|
|
33
|
-
|
|
34
|
-
input,select{
|
|
35
|
-
|
|
36
|
-
input::placeholder{color:#5a606b}
|
|
37
|
-
button{font:inherit;background:var(--grn);color:#04130d;border:none;border-radius:11px;padding:9px 14px;font-weight:660;cursor:pointer}button:hover{filter:brightness(1.07)}
|
|
26
|
+
input,select{font:inherit;border-radius:11px;border:none;background:var(--panel2);color:var(--txt);padding:10px 12px;outline:none;box-shadow:inset 0 0 0 1px transparent}
|
|
27
|
+
input:focus,select:focus{box-shadow:inset 0 0 0 1px rgba(52,211,158,.45)}input::placeholder{color:#5a606b}
|
|
28
|
+
button{font:inherit;background:var(--grn);color:#04130d;border:none;border-radius:11px;padding:10px 15px;font-weight:660;cursor:pointer}button:hover{filter:brightness(1.07)}
|
|
38
29
|
button.ghost{background:var(--soft);color:var(--dim)}button.ghost:hover{color:var(--txt)}
|
|
39
30
|
button.danger{background:rgba(246,120,138,.12);color:var(--red)}button.danger:hover{background:rgba(246,120,138,.2)}
|
|
40
31
|
button.sm{padding:7px 12px;font-size:12.5px;border-radius:9px}
|
|
41
32
|
.row{display:flex;gap:9px;flex-wrap:wrap;align-items:center}
|
|
42
|
-
|
|
43
|
-
table{width:100%;border-collapse:collapse}td,th{text-align:left;padding:11px 12px;font-size:13px}
|
|
33
|
+
table{width:100%;border-collapse:collapse}td,th{text-align:left;padding:11px 12px;font-size:13px;vertical-align:middle}
|
|
44
34
|
thead th{color:var(--dim);font-weight:520;font-size:11px;text-transform:uppercase;letter-spacing:.06em}
|
|
45
35
|
tbody tr{transition:background .12s}tbody tr:hover{background:#1a1d23}
|
|
46
36
|
.pill{padding:3px 10px;border-radius:8px;font-size:11px}.pill.ok{background:rgba(52,211,158,.13);color:var(--grn)}.pill.no{background:rgba(246,120,138,.13);color:var(--red)}
|
|
47
37
|
.gate{max-width:430px;margin:100px auto;text-align:center}.gate input{width:100%;margin:14px 0}
|
|
48
38
|
.tok{background:var(--panel2);padding:13px;border-radius:12px;word-break:break-all;margin-top:13px;font-size:13px}
|
|
49
|
-
.hide{display:none}.muted{color:var(--dim)}.h{display:flex;justify-content:space-between;align-items:center;margin:0 0
|
|
39
|
+
.hide{display:none}.muted{color:var(--dim)}.h{display:flex;justify-content:space-between;align-items:center;margin:0 0 14px}
|
|
50
40
|
h2{font-size:13px;text-transform:uppercase;letter-spacing:.08em;color:var(--dim);margin:0}
|
|
41
|
+
.empty{text-align:center;color:var(--dim);padding:46px}
|
|
51
42
|
</style>
|
|
52
43
|
</head>
|
|
53
44
|
<body>
|
|
@@ -66,28 +57,30 @@
|
|
|
66
57
|
</header>
|
|
67
58
|
<main>
|
|
68
59
|
<div class="stats">
|
|
60
|
+
<div class="card stat"><div class="n" id="s_keys">0</div><div class="l">Access keys</div></div>
|
|
69
61
|
<div class="card stat"><div class="n" id="s_req">0</div><div class="l">Requests</div></div>
|
|
70
|
-
<div class="card stat"><div class="n" id="s_tok">0</div><div class="l">Tokens (in/out)</div></div>
|
|
62
|
+
<div class="card stat"><div class="n" id="s_tok">0/0</div><div class="l">Tokens (in/out)</div></div>
|
|
71
63
|
<div class="card stat"><div class="n" id="s_cost" style="color:var(--grn)">$0</div><div class="l">Cost</div></div>
|
|
72
|
-
<div class="card stat"><div class="n" id="s_prov">0</div><div class="l">Providers connected</div></div>
|
|
73
64
|
</div>
|
|
74
65
|
|
|
75
66
|
<div class="tabs">
|
|
76
|
-
<div class="tab on" data-t="
|
|
77
|
-
<div class="tab" data-t="providers" onclick="tab('providers')">Model endpoints</div>
|
|
67
|
+
<div class="tab on" data-t="keys" onclick="tab('keys')">Access keys</div>
|
|
78
68
|
<div class="tab" data-t="tokens" onclick="tab('tokens')">Machine tokens</div>
|
|
79
69
|
<div class="tab" data-t="models" onclick="tab('models')">Model routing</div>
|
|
80
70
|
<div class="tab" data-t="activity" onclick="tab('activity')">Activity</div>
|
|
81
71
|
</div>
|
|
82
72
|
|
|
83
|
-
<section id="
|
|
84
|
-
<
|
|
85
|
-
<div class="
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
73
|
+
<section id="t_keys">
|
|
74
|
+
<div class="h"><h2>Access keys</h2><button onclick="openCreate()">+ Create access key</button></div>
|
|
75
|
+
<div class="card hide" id="createForm" style="margin-bottom:16px">
|
|
76
|
+
<div class="row" style="margin-bottom:11px">
|
|
77
|
+
<select id="cf_provider" onchange="onProvider()" style="flex:1"></select>
|
|
78
|
+
<select id="kind_cf" onchange="onKind('cf')" style="width:140px"></select>
|
|
79
|
+
</div>
|
|
80
|
+
<div id="ff_cf"></div>
|
|
81
|
+
<div class="row" style="margin-top:11px"><button onclick="createKey()">Create access key</button><button class="ghost" onclick="openCreate()">Cancel</button></div>
|
|
82
|
+
</div>
|
|
83
|
+
<div id="keylist"></div>
|
|
91
84
|
</section>
|
|
92
85
|
|
|
93
86
|
<section id="t_tokens" class="hide">
|
|
@@ -109,8 +102,7 @@
|
|
|
109
102
|
<div class="card" style="margin-bottom:16px">
|
|
110
103
|
<div class="h"><h2>Remap a model</h2></div>
|
|
111
104
|
<div class="row">
|
|
112
|
-
<input id="al_match" placeholder="match: * or gpt-4o"
|
|
113
|
-
<span class="muted">→</span>
|
|
105
|
+
<input id="al_match" placeholder="match: * or gpt-4o"/><span class="muted">→</span>
|
|
114
106
|
<input id="al_target" placeholder="target: mock or anthropic:claude-sonnet-4-5" style="flex:1"/>
|
|
115
107
|
<button onclick="setAlias()">Map</button>
|
|
116
108
|
</div>
|
|
@@ -132,84 +124,30 @@ async function api(p,o={}){const r=await fetch(p,{...o,headers:{...H(),...(o.hea
|
|
|
132
124
|
function val(id){return document.getElementById(id).value.trim()}
|
|
133
125
|
function saveAdmin(){const v=document.getElementById("admintok").value.trim();if(v){localStorage.setItem("pa_admin",v);boot()}}
|
|
134
126
|
function logout(){localStorage.removeItem("pa_admin");document.getElementById("app").classList.add("hide");document.getElementById("gate").classList.remove("hide")}
|
|
135
|
-
function tab(t){document.querySelectorAll(".tab").forEach(e=>e.classList.toggle("on",e.dataset.t===t));["
|
|
127
|
+
function tab(t){document.querySelectorAll(".tab").forEach(e=>e.classList.toggle("on",e.dataset.t===t));["keys","tokens","models","activity"].forEach(s=>document.getElementById("t_"+s).classList.toggle("hide",s!==t))}
|
|
136
128
|
|
|
137
|
-
//
|
|
129
|
+
// logos: real brand marks via the Simple Icons CDN, tinted; fall back to a drawn mark offline.
|
|
138
130
|
const MARK={
|
|
139
131
|
anthropic:'<path d="M9.6 3 3 21h4.1l1.2-3.5h5.9L15.4 21H20L13.4 3H9.6Zm-.4 10.5 1.9-5.4 2 5.4H9.2Z"/>',
|
|
140
132
|
openai:'<path d="M12 2.6c1.9 0 3.5 1.3 4 3a4.2 4.2 0 0 1 2.3 6.8 4.2 4.2 0 0 1-4 5.8 4.2 4.2 0 0 1-8.6-1A4.2 4.2 0 0 1 5.7 12 4.2 4.2 0 0 1 8 5.5a4.2 4.2 0 0 1 4-2.9Zm0 4.6a4.8 4.8 0 1 0 0 9.6 4.8 4.8 0 0 0 0-9.6Z"/>',
|
|
141
133
|
gemini:'<path d="M12 2c.4 4.6 3.4 7.6 8 8-4.6.4-7.6 3.4-8 8-.4-4.6-3.4-7.6-8-8 4.6-.4 7.6-3.4 8-8Z"/>',
|
|
142
|
-
groq:'<path d="M12 3a6 6 0 1 0 4.2 10.3l-2-2A3.2 3.2 0 1 1 12 6.2c.9 0 1.6.3 2.2.8L16.3 5A6 6 0 0 0 12 3Zm2 7v3.3h2.6V10H14Z"/>',
|
|
143
|
-
openrouter:'<path d="M3 8h6l3 4 3-4h6M3 16h6l3-4M21 8l-3 8h-3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
144
|
-
mistral:'<g><rect x="3" y="4" width="3.4" height="3.4"/><rect x="17.6" y="4" width="3.4" height="3.4"/><rect x="7.3" y="8.3" width="3.4" height="3.4"/><rect x="13.3" y="8.3" width="3.4" height="3.4"/><rect x="3" y="12.6" width="3.4" height="3.4"/><rect x="17.6" y="12.6" width="3.4" height="3.4"/><rect x="3" y="16.9" width="18" height="3.1"/></g>',
|
|
145
|
-
deepseek:'<path d="M4 9c3 0 4 2 7 2s4-3 8-2c-1 4-5 7-9 7-3 0-6-2-6-5 0-1 0-2 0-2Zm12 1.5a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z"/>',
|
|
146
134
|
xai:'<path d="M4 4h3.2l4 5.6L15.8 4H19l-6 8 6.2 8h-3.2l-4.4-6L7 20H4l6.4-8.4L4 4Z"/>',
|
|
147
|
-
together:'<path d="M8 5a3.5 3.5 0 1 1 0 7 3.5 3.5 0 0 1 0-7Zm8 7a3.5 3.5 0 1 1 0 7 3.5 3.5 0 0 1 0-7ZM10.5 12.5l3-1.5" fill="none" stroke="currentColor" stroke-width="2"/>',
|
|
148
135
|
};
|
|
149
|
-
// real brand logos via the Simple Icons CDN, tinted to the brand colour; falls back to
|
|
150
|
-
// the drawn mark / letter if a slug is missing or the CDN is unreachable (offline).
|
|
151
136
|
const SLUG={anthropic:"anthropic",openai:"openai",gemini:"googlegemini",groq:"groq",mistral:"mistralai",deepseek:"deepseek",xai:"x"};
|
|
152
|
-
function logo(name,color){
|
|
153
|
-
const mark=MARK[name]?`<svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor">${MARK[name]}</svg>`:`<b>${(name[0]||"?").toUpperCase()}</b>`;
|
|
154
|
-
const inner=SLUG[name]
|
|
155
|
-
?`<img src="https://cdn.simpleicons.org/${SLUG[name]}/${color.replace("#","")}" alt="${name}" onerror="this.closest('.tile').classList.add('fb')"/><span class="fbm">${mark}</span>`
|
|
156
|
-
:`<span class="fbm" style="display:flex">${mark}</span>`;
|
|
157
|
-
return `<div class="tile" style="background:${hexa(color,.14)};color:${color}">${inner}</div>`;
|
|
158
|
-
}
|
|
159
137
|
function hexa(h,a){const n=h.replace("#","");const x=parseInt(n.length===3?n.split("").map(c=>c+c).join(""):n,16);return `rgba(${(x>>16)&255},${(x>>8)&255},${x&255},${a})`}
|
|
138
|
+
function logo(name,color){color=color||"#888";
|
|
139
|
+
const mark=MARK[name]?`<svg viewBox="0 0 24 24" width="21" height="21" fill="currentColor">${MARK[name]}</svg>`:`<b>${(name[0]||"?").toUpperCase()}</b>`;
|
|
140
|
+
const inner=SLUG[name]?`<img src="https://cdn.simpleicons.org/${SLUG[name]}/${color.replace("#","")}" alt="${name}" onerror="this.closest('.tile').classList.add('fb')"/><span class="fbm">${mark}</span>`:`<span class="fbm" style="display:flex">${mark}</span>`;
|
|
141
|
+
return `<div class="tile" style="background:${hexa(color,.14)};color:${color}">${inner}</div>`}
|
|
160
142
|
|
|
161
|
-
|
|
162
|
-
try{
|
|
163
|
-
const u=await(await api("/admin/usage")).json();
|
|
164
|
-
document.getElementById("gate").classList.add("hide");document.getElementById("app").classList.remove("hide");
|
|
165
|
-
document.getElementById("s_req").textContent=u.usage.requests;
|
|
166
|
-
document.getElementById("s_tok").textContent=`${u.usage.prompt_tokens}/${u.usage.completion_tokens}`;
|
|
167
|
-
document.getElementById("s_cost").textContent="$"+(u.usage.cost_usd||0).toFixed(4);
|
|
168
|
-
document.getElementById("badge_backend").textContent=u.backend;
|
|
169
|
-
document.getElementById("enc").textContent=u.encryption?"🔒 encrypted at rest":"⚠ encryption off";
|
|
170
|
-
renderHarnesses();refreshProviders();refreshTokens();refreshAliases();refreshLogs();
|
|
171
|
-
}catch(e){document.getElementById("gateerr").textContent="Invalid admin token."}
|
|
172
|
-
}
|
|
173
|
-
async function renderHarnesses(){
|
|
174
|
-
const d=await(await api("/admin/harnesses")).json();
|
|
175
|
-
document.getElementById("harngrid").innerHTML=d.harnesses.map(h=>{
|
|
176
|
-
const chips=h.auth.map(a=>{
|
|
177
|
-
const cls=a.connected?"badge on":(a.ready?"badge":"badge");
|
|
178
|
-
const tag=a.connected?"✓ connected":(a.ready?"connect":"soon");
|
|
179
|
-
return `<span class="${cls}" title="${a.ready?'available now':'coming soon'}">${a.label} · ${tag}</span>`}).join("");
|
|
180
|
-
return `<div class="prov"><div class="top">${logo(h.provider,h.color)}<div style="flex:1"><h3>${h.label}</h3><div class="ep">runs on ${h.provider}</div></div>
|
|
181
|
-
${h.configured?'<span class="pill ok">ready</span>':'<span class="pill no">connect a key</span>'}</div>
|
|
182
|
-
<div class="badges">${chips}</div>
|
|
183
|
-
<div class="models mono" style="font-size:11px">${h.install}</div>
|
|
184
|
-
<div class="row"><button class="sm" onclick="openConnect('h_${h.name}')">+ Connect auth</button></div>
|
|
185
|
-
<div class="connect" id="c_h_${h.name}">${connectForm('h_'+h.name, h.auth.map(a=>a.mode), h.provider)}</div></div>`}).join("");
|
|
186
|
-
}
|
|
187
|
-
async function refreshProviders(){
|
|
188
|
-
const d=await(await api("/admin/catalog")).json();const g=document.getElementById("provgrid");
|
|
189
|
-
let connected=0;
|
|
190
|
-
g.innerHTML=d.providers.map(p=>{const creds=p.creds||[];const on=p.via_env||creds.length;if(on)connected++;
|
|
191
|
-
const credrows=creds.map(c=>`<div class="row" style="justify-content:space-between;gap:6px"><span class="badge on">${c.kind} · ${c.masked||""}</span><button class="danger sm" onclick="disconnect('${c.id}')">remove</button></div>`).join("");
|
|
192
|
-
return `<div class="prov"><div class="top">${logo(p.name,p.color)}<div style="flex:1"><h3>${p.label}</h3><div class="ep">${p.shape} · ${p.name}</div></div>
|
|
193
|
-
${on?'<span class="pill ok">on</span>':'<span class="pill no">off</span>'}</div>
|
|
194
|
-
<div class="badges">${p.kinds.map(k=>`<span class="badge">${k}</span>`).join("")}${p.via_env?'<span class="badge on">env</span>':''}</div>
|
|
195
|
-
<div style="display:flex;flex-direction:column;gap:6px;margin:6px 0 11px">${credrows||'<span class="muted" style="font-size:11.5px">No keys yet</span>'}</div>
|
|
196
|
-
<div class="row"><button class="sm" onclick="openConnect('${p.name}')">+ Add credential</button></div>
|
|
197
|
-
<div class="connect" id="c_${p.name}">${connectForm(p.name,p.kinds,p.name)}</div></div>`}).join("");
|
|
198
|
-
document.getElementById("s_prov").textContent=connected;
|
|
199
|
-
}
|
|
200
|
-
function openConnect(n){document.getElementById("c_"+n).classList.toggle("open")}
|
|
143
|
+
// the auth-type fields (the "provider" is essentially the auth type)
|
|
201
144
|
function fieldsFor(uid,kind){
|
|
202
|
-
if(kind==="bedrock")return `<input id="f1_${uid}" placeholder="AWS access key id"/><input id="
|
|
203
|
-
if(kind==="azure")return `<input id="f1_${uid}" placeholder="Azure endpoint URL (…/chat/completions?api-version=…)"/><input id="f2_${uid}" type="password" placeholder="api-key"/>`;
|
|
145
|
+
if(kind==="bedrock")return `<div class="row"><input id="f1_${uid}" placeholder="AWS access key id" style="flex:1"/><input id="f3_${uid}" placeholder="region (us-east-1)" style="width:150px"/></div><input id="f2_${uid}" type="password" placeholder="AWS secret access key" style="width:100%;margin-top:8px"/>`;
|
|
146
|
+
if(kind==="azure")return `<input id="f1_${uid}" placeholder="Azure endpoint URL (…/chat/completions?api-version=…)" style="width:100%"/><input id="f2_${uid}" type="password" placeholder="api-key" style="width:100%;margin-top:8px"/>`;
|
|
204
147
|
if(kind==="vertex")return `<span class="muted" style="font-size:12px">Vertex (service-account JSON) — coming next.</span>`;
|
|
205
|
-
return `<input id="f1_${uid}" type="password" placeholder="${kind==='oauth'?'OAuth access token':'API key'}"/>`;
|
|
148
|
+
return `<input id="f1_${uid}" type="password" placeholder="${kind==='oauth'?'OAuth access token':'API key'}" style="width:100%"/>`;
|
|
206
149
|
}
|
|
207
150
|
function onKind(uid){document.getElementById("ff_"+uid).innerHTML=fieldsFor(uid,document.getElementById("kind_"+uid).value)}
|
|
208
|
-
function connectForm(uid,kinds,provider){
|
|
209
|
-
return `<select id="kind_${uid}" onchange="onKind('${uid}')">${kinds.map(k=>`<option value="${k}">${k}</option>`).join("")}</select>
|
|
210
|
-
<div id="ff_${uid}">${fieldsFor(uid,kinds[0])}</div>
|
|
211
|
-
<div class="row"><button class="sm" onclick="submitCred('${uid}','${provider}')">Add credential</button><button class="ghost sm" onclick="openConnect('${uid}')">Cancel</button></div>`;
|
|
212
|
-
}
|
|
213
151
|
async function submitCred(uid,provider){
|
|
214
152
|
const kind=document.getElementById("kind_"+uid).value,g=id=>{const e=document.getElementById(id);return e?e.value.trim():""};
|
|
215
153
|
let secret,meta={};
|
|
@@ -218,10 +156,35 @@ async function submitCred(uid,provider){
|
|
|
218
156
|
else if(kind==="vertex"){return}
|
|
219
157
|
else{secret=g("f1_"+uid)}
|
|
220
158
|
if(!secret)return;
|
|
221
|
-
await api("/admin/providers",{method:"POST",body:JSON.stringify({provider,secret,kind,meta})});
|
|
222
|
-
|
|
159
|
+
await api("/admin/providers",{method:"POST",body:JSON.stringify({provider,secret,kind,meta})});refreshKeys();
|
|
160
|
+
}
|
|
161
|
+
async function disconnect(id){await api("/admin/providers/"+id,{method:"DELETE"});refreshKeys()}
|
|
162
|
+
|
|
163
|
+
let CATALOG=[];
|
|
164
|
+
async function boot(){
|
|
165
|
+
try{
|
|
166
|
+
const u=await(await api("/admin/usage")).json();
|
|
167
|
+
document.getElementById("gate").classList.add("hide");document.getElementById("app").classList.remove("hide");
|
|
168
|
+
document.getElementById("s_req").textContent=u.usage.requests;
|
|
169
|
+
document.getElementById("s_tok").textContent=`${u.usage.prompt_tokens}/${u.usage.completion_tokens}`;
|
|
170
|
+
document.getElementById("s_cost").textContent="$"+(u.usage.cost_usd||0).toFixed(4);
|
|
171
|
+
document.getElementById("badge_backend").textContent=u.backend;
|
|
172
|
+
document.getElementById("enc").textContent=u.encryption?"🔒 encrypted at rest":"⚠ encryption off";
|
|
173
|
+
refreshKeys();refreshTokens();refreshAliases();refreshLogs();
|
|
174
|
+
}catch(e){document.getElementById("gateerr").textContent="Invalid admin token."}
|
|
175
|
+
}
|
|
176
|
+
async function refreshKeys(){
|
|
177
|
+
const d=await(await api("/admin/catalog")).json();CATALOG=d.providers;
|
|
178
|
+
const keys=[];d.providers.forEach(p=>{(p.creds||[]).forEach(c=>keys.push({...c,provider:p.name,plabel:p.label,color:p.color}));if(p.via_env)keys.push({provider:p.name,plabel:p.label,color:p.color,kind:"api_key",masked:"env",id:null})});
|
|
179
|
+
document.getElementById("s_keys").textContent=keys.length;
|
|
180
|
+
document.getElementById("keylist").innerHTML=keys.length
|
|
181
|
+
?`<div class="card"><table><thead><tr><th></th><th>Provider</th><th>Auth type</th><th>Key</th><th></th></tr></thead><tbody>${keys.map(k=>`<tr><td style="width:50px">${logo(k.provider,k.color)}</td><td>${k.plabel}</td><td><span class="badge on">${k.kind}</span></td><td class="mono">${k.masked||""}</td><td>${k.id?`<button class="danger sm" onclick="disconnect('${k.id}')">remove</button>`:'<span class="muted" style="font-size:11px">from env</span>'}</td></tr>`).join("")}</tbody></table></div>`
|
|
182
|
+
:`<div class="card empty">No access keys yet.<br/><span style="font-size:12.5px">Click <b style="color:var(--txt)">+ Create access key</b> to add one.</span></div>`;
|
|
183
|
+
const ps=document.getElementById("cf_provider");if(ps&&!ps.dataset.init){ps.dataset.init="1";ps.innerHTML=CATALOG.map(p=>`<option value="${p.name}">${p.label}</option>`).join("");onProvider()}
|
|
223
184
|
}
|
|
224
|
-
|
|
185
|
+
function onProvider(){const p=CATALOG.find(x=>x.name===val("cf_provider"))||{};const ks=document.getElementById("kind_cf");ks.innerHTML=(p.kinds||["api_key"]).map(k=>`<option value="${k}">${k}</option>`).join("");onKind("cf")}
|
|
186
|
+
function openCreate(){document.getElementById("createForm").classList.toggle("hide")}
|
|
187
|
+
async function createKey(){await submitCred("cf",val("cf_provider"));document.getElementById("createForm").classList.add("hide")}
|
|
225
188
|
|
|
226
189
|
async function refreshTokens(){const d=await(await api("/admin/tokens")).json();
|
|
227
190
|
document.getElementById("toks").innerHTML=d.tokens.map(t=>`<tr><td class="mono">${t.id}</td><td>${t.label||""}</td><td class="mono">${t.masked||""}</td><td class="mono">${(t.scope||[]).join(", ")}</td><td class="mono">${t.budget_usd?('$'+(t.spent_usd||0).toFixed(4)+' / $'+(+t.budget_usd).toFixed(2)):'—'}</td><td>${t.revoked?'<span class="pill no">revoked</span>':'<span class="pill ok">active</span>'}</td><td>${t.revoked?"":`<button class="danger sm" onclick="revokeTok('${t.id}')">revoke</button>`}</td></tr>`).join("")}
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proxyagent"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
8
8
|
description = "Run any agent (Claude, Codex, custom) on any machine — with no API key on the machine. A secure, self-hosted proxy for models and tools."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|