arkclaw-webchat-cli 0.5.3__tar.gz → 0.6.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.
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/PKG-INFO +1 -1
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/pyproject.toml +1 -1
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/flows.py +114 -46
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/identity.py +14 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/.gitignore +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/README.md +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/__init__.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/attachments.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/cli.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/config.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/control.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/core.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/doctor.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/errors.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/oauth.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/output.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/policy.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/providers.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/secrets_store.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/sts.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/transport/__init__.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/transport/a2a.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/transport/base.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/transport/openclaw.py +0 -0
- {arkclaw_webchat_cli-0.5.3 → arkclaw_webchat_cli-0.6.0}/src/ee_claw/update.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "arkclaw-webchat-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
8
8
|
description = "CLI to chat with an ArkClaw EE space's Claw over enterprise SSO — zero permanent AK/SK."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -53,7 +53,7 @@ from ee_claw.identity import (
|
|
|
53
53
|
from ee_claw.oauth import OAuthClient
|
|
54
54
|
from ee_claw.output import Emitter
|
|
55
55
|
from ee_claw.providers import ProviderContext, resolve_token_source
|
|
56
|
-
from ee_claw.sts import assume_role_with_oidc
|
|
56
|
+
from ee_claw.sts import assume_role_with_oidc, get_chat_token
|
|
57
57
|
from ee_claw.transport.a2a import A2ATransport, agent_card
|
|
58
58
|
from ee_claw.transport.base import Approver, Attachment, Transport, TurnEvent, TurnResult
|
|
59
59
|
from ee_claw.transport.openclaw import OpenClawTransport, session_key_for
|
|
@@ -116,6 +116,37 @@ def _user_identity(id_token: str | None) -> dict[str, str]:
|
|
|
116
116
|
return ident
|
|
117
117
|
|
|
118
118
|
|
|
119
|
+
def _probe_claw_access(
|
|
120
|
+
cfg: SessionConfig, id_token: str, claw_ids: list[str]
|
|
121
|
+
) -> tuple[set[str], set[str]]:
|
|
122
|
+
"""Which of ``claw_ids`` the current user can ACTUALLY chat — checked live by
|
|
123
|
+
minting a (throwaway) ChatToken for each, the only per-user check the CLI's
|
|
124
|
+
role can do. Returns (accessible, denied). A transient/non-permission error
|
|
125
|
+
keeps the claw (don't hide it on a network blip). Raises ArkclawError if
|
|
126
|
+
creds can't be obtained at all (caller falls back to the unchecked list)."""
|
|
127
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
128
|
+
|
|
129
|
+
creds = _openclaw_creds(cfg, id_token) # may raise (offline / expired / no creds)
|
|
130
|
+
identity = _user_identity(id_token)
|
|
131
|
+
region = str(cfg.region)
|
|
132
|
+
|
|
133
|
+
def check(cid: str) -> tuple[str, bool]:
|
|
134
|
+
try:
|
|
135
|
+
get_chat_token(cid, region, creds, identity=identity)
|
|
136
|
+
return cid, True
|
|
137
|
+
except ClawAccessDeniedError:
|
|
138
|
+
return cid, False
|
|
139
|
+
except ArkclawError:
|
|
140
|
+
return cid, True # transient/other — keep, don't silently drop
|
|
141
|
+
|
|
142
|
+
accessible: set[str] = set()
|
|
143
|
+
denied: set[str] = set()
|
|
144
|
+
with ThreadPoolExecutor(max_workers=min(8, max(1, len(claw_ids)))) as ex:
|
|
145
|
+
for cid, ok in ex.map(check, claw_ids):
|
|
146
|
+
(accessible if ok else denied).add(cid)
|
|
147
|
+
return accessible, denied
|
|
148
|
+
|
|
149
|
+
|
|
119
150
|
def _normalize(url: str) -> tuple[str, str]:
|
|
120
151
|
base = (url if "//" in url else "https://" + url).rstrip("/")
|
|
121
152
|
host = urllib.parse.urlparse(base).hostname or ""
|
|
@@ -131,55 +162,72 @@ def _is_userpool_address(host: str) -> bool:
|
|
|
131
162
|
|
|
132
163
|
|
|
133
164
|
def do_init(emitter: Emitter, address: str | None = None) -> dict[str, Any]:
|
|
134
|
-
"""``arkclaw init``: one-time
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"arkclaw init 是交互式的,需要终端。",
|
|
142
|
-
hint="在终端直接运行 `arkclaw init`;脚本里改用 env(ARKCLAW_CLIENT_ID/ROLE_TRN)+ flags。",
|
|
143
|
-
)
|
|
165
|
+
"""``arkclaw init``: one-time setup. The GOAL is **address-only** — when the
|
|
166
|
+
space publishes its CLI config (issuer/region/client_id/role…) via
|
|
167
|
+
``/.well-known/arkclaw-cli`` or GetAuthConfig, the user gives ONLY the
|
|
168
|
+
address and nothing is asked (works non-interactively too). Until then, it
|
|
169
|
+
prompts for the few bits discovery can't provide (the CLI's OAuth client,
|
|
170
|
+
the STS role)."""
|
|
171
|
+
interactive = not emitter.json and sys.stdin.isatty()
|
|
144
172
|
|
|
145
|
-
def
|
|
173
|
+
def need_tty() -> None:
|
|
174
|
+
if not interactive:
|
|
175
|
+
raise ValidationError(
|
|
176
|
+
"该空间尚未发布 CLI 配置,需要补充 client_id/role,而当前非交互终端。",
|
|
177
|
+
hint="在终端运行 `arkclaw init <地址>`;或脚本里用 env(ARKCLAW_CLIENT_ID/ROLE_TRN)+ flags。",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def ask(label: str, default: str | None = None, *, required: bool = False) -> str:
|
|
146
181
|
suffix = f" [{default}]" if default else ""
|
|
147
182
|
while True:
|
|
148
183
|
ans = input(f" {label}{suffix}: ").strip() or (default or "")
|
|
149
184
|
if ans or not required:
|
|
150
|
-
return ans
|
|
185
|
+
return ans
|
|
151
186
|
emitter.line(" (必填)")
|
|
152
187
|
|
|
153
|
-
|
|
154
|
-
|
|
188
|
+
if not address:
|
|
189
|
+
need_tty()
|
|
190
|
+
emitter.line("arkclaw init —— 配置一次(能自动发现的不问你)\n")
|
|
191
|
+
address = ask("空间登录地址 (https://…)", required=True)
|
|
155
192
|
base, host = _normalize(str(address))
|
|
156
193
|
disc = discover_cli_config(base) or {}
|
|
157
194
|
issuer = disc.get("issuer") or (f"https://{host}" if _is_userpool_address(host) else None)
|
|
158
195
|
region = disc.get("region") or derive_region(base)
|
|
159
|
-
if
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
"CLI
|
|
171
|
-
)
|
|
172
|
-
role_trn: object = None
|
|
173
|
-
endpoint: object = None
|
|
174
|
-
if transport == "openclaw":
|
|
175
|
-
role_trn = disc.get("role_trn") or ask("STS role TRN (trn:iam::<账号>:role/<名字>)", required=True)
|
|
196
|
+
transport = "a2a" if str(disc.get("transport") or "").lower() == "a2a" else "openclaw"
|
|
197
|
+
client_id = disc.get("client_id")
|
|
198
|
+
role_trn = disc.get("role_trn")
|
|
199
|
+
provider_trn = disc.get("provider_trn")
|
|
200
|
+
endpoint = disc.get("endpoint")
|
|
201
|
+
clawid = disc.get("clawid")
|
|
202
|
+
|
|
203
|
+
# Fully discovered = the space published this CLI's client + target plane →
|
|
204
|
+
# nothing to ask (address-only). Otherwise prompt ONLY for what's missing.
|
|
205
|
+
complete = bool(client_id) and (bool(role_trn) if transport == "openclaw" else bool(endpoint))
|
|
206
|
+
if complete:
|
|
207
|
+
emitter.line("✓ 该空间已发布 CLI 配置 —— 全部自动解析,无需手动输入。")
|
|
176
208
|
else:
|
|
177
|
-
|
|
178
|
-
|
|
209
|
+
need_tty()
|
|
210
|
+
if issuer:
|
|
211
|
+
emitter.line(" ✓ 自动发现身份池 issuer")
|
|
212
|
+
if region:
|
|
213
|
+
emitter.line(f" ✓ 区域: {region}")
|
|
214
|
+
# "webchat" = the ChatToken/wss path (internal name "openclaw"); "a2a" unchanged.
|
|
215
|
+
t_in = ask("传输方式 (webchat/a2a)", default=("a2a" if transport == "a2a" else "webchat"))
|
|
216
|
+
transport = "a2a" if t_in.lower() == "a2a" else "openclaw"
|
|
217
|
+
if not client_id:
|
|
218
|
+
client_id = ask(
|
|
219
|
+
"CLI client_id(public OAuth 客户端,问管理员;平台发布发现后免此项)", required=True
|
|
220
|
+
)
|
|
221
|
+
if transport == "openclaw" and not role_trn:
|
|
222
|
+
role_trn = ask("STS role TRN (trn:iam::<账号>:role/<名字>)", required=True)
|
|
223
|
+
elif transport == "a2a" and not endpoint:
|
|
224
|
+
endpoint = ask("A2A endpoint (https://…)", required=True)
|
|
225
|
+
if not clawid:
|
|
226
|
+
clawid = ask("默认 claw id (ci-…,可留空,登录后用 arkclaw agents 选)") or None
|
|
179
227
|
|
|
180
228
|
saved = {
|
|
181
229
|
"address": base, "issuer": issuer, "client_id": client_id, "transport": transport,
|
|
182
|
-
"region": region, "role_trn": role_trn, "provider_trn":
|
|
230
|
+
"region": region, "role_trn": role_trn, "provider_trn": provider_trn,
|
|
183
231
|
"endpoint": endpoint, "space_id": disc.get("space_id"), "clawid": clawid,
|
|
184
232
|
}
|
|
185
233
|
config_mod.save_defaults(saved)
|
|
@@ -982,12 +1030,11 @@ def do_agents(emitter: Emitter) -> dict[str, Any]:
|
|
|
982
1030
|
)
|
|
983
1031
|
elif cfg.transport == "openclaw":
|
|
984
1032
|
emitter.line(f"★ 当前登录(openclaw)· 空间 {cfg.space}")
|
|
985
|
-
#
|
|
986
|
-
#
|
|
987
|
-
#
|
|
988
|
-
#
|
|
989
|
-
#
|
|
990
|
-
# (record_session) and shows up here next time.
|
|
1033
|
+
# Candidates = the claws YOU have used from this machine (local history) +
|
|
1034
|
+
# your default — NOT a server enumeration (the space-wide list isn't
|
|
1035
|
+
# per-user scoped for the CLI's role). We then VERIFY access to each by
|
|
1036
|
+
# minting a throwaway ChatToken, so only claws you can actually chat are
|
|
1037
|
+
# shown; denied ones are pruned from history (and cleared as default).
|
|
991
1038
|
seen: dict[str, float] = {}
|
|
992
1039
|
for s in config_mod.list_sessions():
|
|
993
1040
|
cid = s.get("claw")
|
|
@@ -996,12 +1043,29 @@ def do_agents(emitter: Emitter) -> dict[str, Any]:
|
|
|
996
1043
|
seen[str(cid)] = max(seen.get(str(cid), 0.0), float(ts) if isinstance(ts, (int, float)) else 0.0)
|
|
997
1044
|
if cfg.claw and str(cfg.claw) not in seen:
|
|
998
1045
|
seen[str(cfg.claw)] = 0.0 # the default claw, even if not chatted yet
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1046
|
+
|
|
1047
|
+
claw_ids = sorted(seen, key=lambda c: seen[c], reverse=True)
|
|
1048
|
+
checked = False
|
|
1049
|
+
if claw_ids:
|
|
1050
|
+
if not emitter.json:
|
|
1051
|
+
emitter.line(" 核对访问权限中…")
|
|
1052
|
+
try:
|
|
1053
|
+
_, id_token = _load_session(cfg)
|
|
1054
|
+
accessible, denied = _probe_claw_access(cfg, id_token, claw_ids)
|
|
1055
|
+
checked = True
|
|
1056
|
+
for cid in denied:
|
|
1057
|
+
config_mod.forget_claw(str(cfg.space), cid) # stop listing it
|
|
1058
|
+
if cfg.claw == cid:
|
|
1059
|
+
config_mod.save_config(dataclasses.replace(cfg, claw=None))
|
|
1060
|
+
cfg = config_mod.load_config() or cfg
|
|
1061
|
+
current["default_claw"] = cfg.claw
|
|
1062
|
+
claw_ids = [c for c in claw_ids if c in accessible]
|
|
1063
|
+
except ArkclawError:
|
|
1064
|
+
checked = False # offline / expired / no creds → show unchecked
|
|
1065
|
+
|
|
1066
|
+
claws = [{"ClawInstanceId": cid, "last_used": seen[cid]} for cid in claw_ids]
|
|
1003
1067
|
current["claws"] = claws
|
|
1004
|
-
current["source"] = "local-history"
|
|
1068
|
+
current["source"] = "verified" if checked else "local-history-unchecked"
|
|
1005
1069
|
if claws:
|
|
1006
1070
|
emitter.table(
|
|
1007
1071
|
["Claw(可 chat)", "上次使用", "默认"],
|
|
@@ -1014,6 +1078,10 @@ def do_agents(emitter: Emitter) -> dict[str, Any]:
|
|
|
1014
1078
|
for c in claws
|
|
1015
1079
|
],
|
|
1016
1080
|
)
|
|
1081
|
+
if not checked:
|
|
1082
|
+
emitter.line(" ⚠ 未能核对权限(离线/会话过期),以上为本地历史,可能含你无权访问的。")
|
|
1083
|
+
elif checked:
|
|
1084
|
+
emitter.line(" 你在本空间没有可访问的 claw(历史里的都已无权或不存在)。")
|
|
1017
1085
|
else:
|
|
1018
1086
|
emitter.line(" 还没用过任何 claw。用 `arkclaw chat ci-xxxx` 开始,之后这里会记住它。")
|
|
1019
1087
|
|
|
@@ -118,6 +118,20 @@ def discover_cli_config(url: str) -> dict[str, object] | None:
|
|
|
118
118
|
if result.get(k):
|
|
119
119
|
out["client_id"] = result[k]
|
|
120
120
|
break
|
|
121
|
+
# The remaining CLI-plane fields (platform-added). Once the BFF surfaces
|
|
122
|
+
# these on GetAuthConfig, the user types ONLY the address — login/init
|
|
123
|
+
# resolve everything. (camelCase from the BFF → snake_case the CLI uses.)
|
|
124
|
+
for src, dst in (
|
|
125
|
+
("roleTrn", "role_trn"),
|
|
126
|
+
("providerTrn", "provider_trn"),
|
|
127
|
+
("region", "region"),
|
|
128
|
+
("spaceId", "space_id"),
|
|
129
|
+
("transport", "transport"),
|
|
130
|
+
("endpoint", "endpoint"),
|
|
131
|
+
("defaultClawId", "clawid"),
|
|
132
|
+
):
|
|
133
|
+
if result.get(src):
|
|
134
|
+
out[dst] = result[src]
|
|
121
135
|
return out or None
|
|
122
136
|
|
|
123
137
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|