arkclaw-webchat-cli 0.6.2__tar.gz → 0.9.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.6.2 → arkclaw_webchat_cli-0.9.0}/.gitignore +4 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/PKG-INFO +81 -12
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/README.md +79 -11
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/pyproject.toml +2 -1
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/attachments.py +29 -6
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/cli.py +212 -4
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/config.py +11 -1
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/control.py +4 -4
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/core.py +2 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/doctor.py +4 -2
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/errors.py +27 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/flows.py +446 -4
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/oauth.py +5 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/sts.py +144 -8
- arkclaw_webchat_cli-0.9.0/src/ee_claw/terminal_io.py +432 -0
- arkclaw_webchat_cli-0.9.0/src/ee_claw/transport/claw_terminal.py +246 -0
- arkclaw_webchat_cli-0.9.0/src/ee_claw/transport/netdisk.py +305 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/transport/openclaw.py +58 -15
- arkclaw_webchat_cli-0.9.0/src/ee_claw/transport/silk.py +175 -0
- arkclaw_webchat_cli-0.9.0/src/ee_claw/transport/silk_fs.py +252 -0
- arkclaw_webchat_cli-0.9.0/src/ee_claw/transport/silk_rpc.py +187 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/__init__.py +0 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/identity.py +0 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/output.py +0 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/policy.py +0 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/providers.py +0 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/secrets_store.py +0 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/transport/__init__.py +0 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/transport/a2a.py +0 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/transport/base.py +0 -0
- {arkclaw_webchat_cli-0.6.2 → arkclaw_webchat_cli-0.9.0}/src/ee_claw/update.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arkclaw-webchat-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: CLI to chat with an ArkClaw EE space's Claw over enterprise SSO — zero permanent AK/SK.
|
|
5
5
|
Author: ArkClaw Team
|
|
6
6
|
Keywords: arkclaw,cli,ee,openclaw,sso,sts
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Requires-Dist: keyring>=24.0
|
|
17
|
+
Requires-Dist: msgpack>=1.0
|
|
17
18
|
Requires-Dist: typer>=0.12.0
|
|
18
19
|
Requires-Dist: websockets>=12.0
|
|
19
20
|
Provides-Extra: dev
|
|
@@ -45,6 +46,9 @@ arkclaw chat ci-xxxxxxxx
|
|
|
45
46
|
- **Chat** — interactive REPL or one-shot `-m`, streaming token-by-token, with a live "what's it doing" spinner and tool-event trace.
|
|
46
47
|
- **Talk to a specific claw** — by id (`chat ci-...`) or by name (`chat 答疑助手`).
|
|
47
48
|
- **Manage a claw's files** — read/write its managed workspace files (`AGENTS.md`, `SOUL.md`, `MEMORY.md`, …) with `ls` / `pull` / `push`.
|
|
49
|
+
- **Upload & download any file** — push arbitrary files into the claw's *real* workspace filesystem and pull results back (a whole directory comes as a `.tar`) with `upload` / `download` / `mkdir` / `ls <path>`, over the Silk file service (needs a Silk-enabled claw).
|
|
50
|
+
- **Large-product storage** — a separate IDS *netdisk* plane for big artifacts/videos: `netdisk create-space` / `ls` / `upload` / `download` / `mkdir` / `rm`.
|
|
51
|
+
- **Read past conversations** — list an agent's sessions, then pull a full transcript with `arkclaw history <会话ID>` (over the `chat.history` ws RPC; `--json` to export, `-o` to a file).
|
|
48
52
|
- **Fan out** — send one message to many claws in parallel.
|
|
49
53
|
- **Be scripted** — every command takes `--json` (one clean `{ok,data,error}` envelope on stdout) and returns a typed exit code.
|
|
50
54
|
|
|
@@ -65,6 +69,9 @@ git clone <repo> && cd ee-claw
|
|
|
65
69
|
uv venv && uv pip install -e .
|
|
66
70
|
```
|
|
67
71
|
|
|
72
|
+
> **Contributing / hacking on the CLI?** Start with [`AGENTS.md`](./AGENTS.md) —
|
|
73
|
+
> dev setup, the three gates, code map, invariants, and how to add a command/provider/transport.
|
|
74
|
+
|
|
68
75
|
---
|
|
69
76
|
|
|
70
77
|
## Quick start
|
|
@@ -105,11 +112,16 @@ straight to `arkclaw login https://your-space...`. See **[Configuration](#config
|
|
|
105
112
|
| `arkclaw agents delete <agent>` | Delete a named agent by agentId (`a-...`) or display name. Asks for confirmation unless `--yes`; the `main` agent is protected. |
|
|
106
113
|
| `arkclaw chat [TARGET] [MSG]` | Chat. `TARGET` = a claw id (`ci-...`), an agent name (from `agents`), or a profile. With `MSG` → one-shot; without → interactive REPL. `-f` attach files, `-o` write the reply, `--session NAME` name the conversation, `--approve-all` auto-approve tool runs. |
|
|
107
114
|
| `arkclaw <name>` | Shortcut: `arkclaw 答疑助手` ≡ `arkclaw chat 答疑助手`. |
|
|
108
|
-
| `arkclaw ls` |
|
|
109
|
-
| `arkclaw pull <name> [local]` | Download a managed file to local disk. |
|
|
110
|
-
| `arkclaw push <local> [name]` | Write a local text file into a managed file. |
|
|
115
|
+
| `arkclaw ls [path]` | No `path` → the claw's managed brain files. With `path` → that workspace directory over Silk (the real filesystem). |
|
|
116
|
+
| `arkclaw pull <name> [local]` | Download a **managed** file to local disk. |
|
|
117
|
+
| `arkclaw push <local> [name]` | Write a local text file into a **managed** file. |
|
|
118
|
+
| `arkclaw upload <local> [remote]` | Upload **any** file into the claw's workspace filesystem (Silk). Defaults to the basename at the workspace root. |
|
|
119
|
+
| `arkclaw download <remote> [local]` | Download a workspace file from the claw (Silk); a directory comes as `<name>.tar`. |
|
|
120
|
+
| `arkclaw mkdir <path>` | Create a directory in the claw workspace (Silk; parent must exist). |
|
|
121
|
+
| `arkclaw netdisk <cmd>` | IDS netdisk (large products/videos), a separate storage plane: `create-space` / `ls` / `upload` / `download` / `mkdir` / `rm`. Configured via `IDS_*` env. |
|
|
111
122
|
| `arkclaw fanout "<msg>" --clawid ci-a --clawid ci-b` | Same message to many claws in parallel. |
|
|
112
|
-
| `arkclaw sessions [claw]` | An agent's conversations from the server (`--agent` to pick one), newest first — resume with `chat --session <会话ID>`, fork with `--new`. Falls back to the local record when offline / a2a. |
|
|
123
|
+
| `arkclaw sessions [claw]` | An agent's conversations from the server (`--agent` to pick one), newest first — resume with `chat --session <会话ID>`, read with `history <会话ID>`, fork with `--new`. Falls back to the local record when offline / a2a. |
|
|
124
|
+
| `arkclaw history <session>` | Print a past conversation's full transcript (ws `chat.history`). `<session>` = a 会话ID from `sessions`, a label, or a full `agent:…` key. Tool steps hidden unless `--show-tools`; `--json` dumps every message; `-o` writes a plain-text log. openclaw only. |
|
|
113
125
|
| `arkclaw profile save/use/list` | Named snapshots of the session config (multi-space / multi-claw). |
|
|
114
126
|
| `arkclaw doctor` | Self-check: login freshness, keychain, pool/STS/endpoint reachability. |
|
|
115
127
|
| `arkclaw schema --json` | Machine-readable command surface (for agents/tooling). |
|
|
@@ -135,12 +147,21 @@ arkclaw chat ci-xxxx -f notes.md -m "review" -o out.md # attach context, save
|
|
|
135
147
|
|
|
136
148
|
---
|
|
137
149
|
|
|
138
|
-
## Files
|
|
150
|
+
## Files & storage
|
|
151
|
+
|
|
152
|
+
The CLI moves files across **three distinct planes** — pick by what you're carrying:
|
|
153
|
+
|
|
154
|
+
| Plane | Commands | What lives there |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| **Managed brain** | `ls` · `pull` · `push` | The claw's own config files (`AGENTS.md`, `SOUL.md`, …). A fixed allow-list, not a general store. |
|
|
157
|
+
| **Workspace (Silk)** | `ls <path>` · `upload` · `download` · `mkdir` | The claw's *real* working filesystem — arbitrary files, uploads, agent outputs, subdirectories. |
|
|
158
|
+
| **Netdisk (IDS)** | `netdisk …` | A separate large-object store for big products/videos, on a dedicated storage account. |
|
|
139
159
|
|
|
140
|
-
`ls` / `pull` / `push`
|
|
141
|
-
|
|
142
|
-
`
|
|
143
|
-
|
|
160
|
+
### 1. Managed brain files — `ls` / `pull` / `push`
|
|
161
|
+
|
|
162
|
+
The claw's "brain": `AGENTS.md`, `SOUL.md`, `MEMORY.md`, `TOOLS.md`,
|
|
163
|
+
`IDENTITY.md`, `USER.md`, `HEARTBEAT.md`. A fixed allow-list — arbitrary
|
|
164
|
+
filenames are rejected; `push` only writes text.
|
|
144
165
|
|
|
145
166
|
```bash
|
|
146
167
|
arkclaw ls # list them (defaults to your default claw)
|
|
@@ -150,6 +171,51 @@ arkclaw push ./agents.md AGENTS.md # write a managed file
|
|
|
150
171
|
arkclaw ls --clawid ci-other # a different claw
|
|
151
172
|
```
|
|
152
173
|
|
|
174
|
+
### 2. Workspace filesystem — `upload` / `download` / `mkdir` / `ls <path>`
|
|
175
|
+
|
|
176
|
+
The claw's **real** working filesystem (`/root/.openclaw/workspace`), over the
|
|
177
|
+
Silk file service. Paths are workspace-relative. Use it to feed an agent input
|
|
178
|
+
files and to retrieve whatever it produces.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
arkclaw upload ./input.png # → workspace root (basename)
|
|
182
|
+
arkclaw upload ./data.csv runs/data.csv # → a subpath
|
|
183
|
+
arkclaw mkdir runs # parent must exist (non-recursive)
|
|
184
|
+
arkclaw ls runs # list a workspace directory
|
|
185
|
+
arkclaw download runs/output.mp4 ./out.mp4 # pull a result back
|
|
186
|
+
arkclaw download runs ./runs.tar # a directory downloads as a .tar
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- **Requires a Silk-enabled claw.** A claw you can chat but that isn't
|
|
190
|
+
provisioned into Silk is rejected with a clear "未启用文件服务(Silk)" error
|
|
191
|
+
(`ErrClawInstanceNotFound`); Silk runs the *same* per-user gate as chat.
|
|
192
|
+
- **Upload cap** is server-side (~25 MB per file); larger uploads return a clear
|
|
193
|
+
"文件超出上传上限" error.
|
|
194
|
+
- There is **no `rm`** here — workspace deletes aren't exposed as a command.
|
|
195
|
+
|
|
196
|
+
### 3. Netdisk (IDS) — `netdisk …`
|
|
197
|
+
|
|
198
|
+
A separate storage plane for large products/videos, on a **dedicated IDS
|
|
199
|
+
account** (not your SSO identity). Configured entirely from `IDS_*` env vars
|
|
200
|
+
(provisioned by the storage team). Create a space once, then pass its `SpaceID`:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
arkclaw netdisk create-space my-space # → prints the SpaceID
|
|
204
|
+
arkclaw netdisk mkdir outputs --space spc-...
|
|
205
|
+
arkclaw netdisk upload ./final.mp4 outputs/final.mp4 --space spc-...
|
|
206
|
+
arkclaw netdisk ls outputs/ --space spc-...
|
|
207
|
+
arkclaw netdisk download outputs/final.mp4 ./final.mp4 --space spc-...
|
|
208
|
+
arkclaw netdisk rm outputs/final.mp4 --space spc-... # asks unless --yes
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`create-space` also takes `--description`; `ls` takes `--limit N` to cap entries.
|
|
212
|
+
|
|
213
|
+
| Env | Purpose |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `IDS_BASE_URL` / `IDS_REGION` / `IDS_SERVICE` / `IDS_INSTANCE_ID` | Endpoint + routing for the IDS instance. |
|
|
216
|
+
| `IDS_AK` / `IDS_SK` (+ `IDS_ACCOUNT_ID`) | Static credentials for the storage account… |
|
|
217
|
+
| `IDS_ROLE_TRN` | …or assume a role via SSO instead of static AK/SK. |
|
|
218
|
+
|
|
153
219
|
---
|
|
154
220
|
|
|
155
221
|
## For agents / automation
|
|
@@ -186,15 +252,18 @@ values via env:
|
|
|
186
252
|
| `ARKCLAW_CLIENT_ID` | The CLI's public OAuth client id for the pool. |
|
|
187
253
|
| `ARKCLAW_ROLE_TRN` | STS role that mints the ChatToken (openclaw). |
|
|
188
254
|
| `ARKCLAW_REGION` / `ARKCLAW_SPACE_ID` | Override region / space (usually auto). |
|
|
255
|
+
| `ARKCLAW_API_HOST` / `ARKCLAW_API_SERVICE` | **Advanced/testing.** Point the control-plane calls at a non-production plane (host + SigV4 service). See the note below. |
|
|
189
256
|
|
|
190
257
|
Other config: `~/.arkclaw/defaults.json` (`init` answers, 0600), `~/.arkclaw/session.json` (routing, 0600), `~/.arkclaw/cmdpolicy.json` (tool-approval allow/deny), OS keychain (tokens).
|
|
191
258
|
|
|
192
259
|
> **Escape hatch (admin/test):** `arkclaw login <url> --transport openclaw --static-creds` uses `VOLCENGINE_ACCESS_KEY`/`SECRET_KEY` from the env instead of SSO — convenient for CI, but it's account-level keys, not identity-through.
|
|
193
260
|
|
|
261
|
+
> **Control-plane override (advanced/testing):** by default every control-plane call (chat/terminal token mints, file/fleet actions, `doctor`) targets `arkclaw.<region>.volcengineapi.com` with SigV4 service `arkclaw`. Setting `ARKCLAW_API_HOST` + `ARKCLAW_API_SERVICE` retargets them — e.g. a non-production plane. The service is also the IAM action prefix, so a non-default service needs the matching role permissions. The host receives the **signed request including live temporary credentials**, so it must be a host you trust — only `*.volcengineapi.com` is accepted.
|
|
262
|
+
|
|
194
263
|
---
|
|
195
264
|
|
|
196
265
|
## Notes
|
|
197
266
|
|
|
198
|
-
- `ls`/`pull`/`push` and a bare `chat` use your **default claw** (set at login, or the last one you opened in the browser); override with `--clawid` (or `chat ci-...`).
|
|
199
|
-
-
|
|
267
|
+
- `ls`/`pull`/`push`, `upload`/`download`/`mkdir`, and a bare `chat` use your **default claw** (set at login, or the last one you opened in the browser); override with `--clawid` (or `chat ci-...`).
|
|
268
|
+
- `pull`/`push` reach the claw's **managed** files; `upload`/`download` reach its **workspace filesystem** (Silk, arbitrary files); `netdisk …` is a **separate** storage plane on its own account.
|
|
200
269
|
- Nothing is hardcoded per space — the CLI reads what the space serves.
|
|
@@ -21,6 +21,9 @@ arkclaw chat ci-xxxxxxxx
|
|
|
21
21
|
- **Chat** — interactive REPL or one-shot `-m`, streaming token-by-token, with a live "what's it doing" spinner and tool-event trace.
|
|
22
22
|
- **Talk to a specific claw** — by id (`chat ci-...`) or by name (`chat 答疑助手`).
|
|
23
23
|
- **Manage a claw's files** — read/write its managed workspace files (`AGENTS.md`, `SOUL.md`, `MEMORY.md`, …) with `ls` / `pull` / `push`.
|
|
24
|
+
- **Upload & download any file** — push arbitrary files into the claw's *real* workspace filesystem and pull results back (a whole directory comes as a `.tar`) with `upload` / `download` / `mkdir` / `ls <path>`, over the Silk file service (needs a Silk-enabled claw).
|
|
25
|
+
- **Large-product storage** — a separate IDS *netdisk* plane for big artifacts/videos: `netdisk create-space` / `ls` / `upload` / `download` / `mkdir` / `rm`.
|
|
26
|
+
- **Read past conversations** — list an agent's sessions, then pull a full transcript with `arkclaw history <会话ID>` (over the `chat.history` ws RPC; `--json` to export, `-o` to a file).
|
|
24
27
|
- **Fan out** — send one message to many claws in parallel.
|
|
25
28
|
- **Be scripted** — every command takes `--json` (one clean `{ok,data,error}` envelope on stdout) and returns a typed exit code.
|
|
26
29
|
|
|
@@ -41,6 +44,9 @@ git clone <repo> && cd ee-claw
|
|
|
41
44
|
uv venv && uv pip install -e .
|
|
42
45
|
```
|
|
43
46
|
|
|
47
|
+
> **Contributing / hacking on the CLI?** Start with [`AGENTS.md`](./AGENTS.md) —
|
|
48
|
+
> dev setup, the three gates, code map, invariants, and how to add a command/provider/transport.
|
|
49
|
+
|
|
44
50
|
---
|
|
45
51
|
|
|
46
52
|
## Quick start
|
|
@@ -81,11 +87,16 @@ straight to `arkclaw login https://your-space...`. See **[Configuration](#config
|
|
|
81
87
|
| `arkclaw agents delete <agent>` | Delete a named agent by agentId (`a-...`) or display name. Asks for confirmation unless `--yes`; the `main` agent is protected. |
|
|
82
88
|
| `arkclaw chat [TARGET] [MSG]` | Chat. `TARGET` = a claw id (`ci-...`), an agent name (from `agents`), or a profile. With `MSG` → one-shot; without → interactive REPL. `-f` attach files, `-o` write the reply, `--session NAME` name the conversation, `--approve-all` auto-approve tool runs. |
|
|
83
89
|
| `arkclaw <name>` | Shortcut: `arkclaw 答疑助手` ≡ `arkclaw chat 答疑助手`. |
|
|
84
|
-
| `arkclaw ls` |
|
|
85
|
-
| `arkclaw pull <name> [local]` | Download a managed file to local disk. |
|
|
86
|
-
| `arkclaw push <local> [name]` | Write a local text file into a managed file. |
|
|
90
|
+
| `arkclaw ls [path]` | No `path` → the claw's managed brain files. With `path` → that workspace directory over Silk (the real filesystem). |
|
|
91
|
+
| `arkclaw pull <name> [local]` | Download a **managed** file to local disk. |
|
|
92
|
+
| `arkclaw push <local> [name]` | Write a local text file into a **managed** file. |
|
|
93
|
+
| `arkclaw upload <local> [remote]` | Upload **any** file into the claw's workspace filesystem (Silk). Defaults to the basename at the workspace root. |
|
|
94
|
+
| `arkclaw download <remote> [local]` | Download a workspace file from the claw (Silk); a directory comes as `<name>.tar`. |
|
|
95
|
+
| `arkclaw mkdir <path>` | Create a directory in the claw workspace (Silk; parent must exist). |
|
|
96
|
+
| `arkclaw netdisk <cmd>` | IDS netdisk (large products/videos), a separate storage plane: `create-space` / `ls` / `upload` / `download` / `mkdir` / `rm`. Configured via `IDS_*` env. |
|
|
87
97
|
| `arkclaw fanout "<msg>" --clawid ci-a --clawid ci-b` | Same message to many claws in parallel. |
|
|
88
|
-
| `arkclaw sessions [claw]` | An agent's conversations from the server (`--agent` to pick one), newest first — resume with `chat --session <会话ID>`, fork with `--new`. Falls back to the local record when offline / a2a. |
|
|
98
|
+
| `arkclaw sessions [claw]` | An agent's conversations from the server (`--agent` to pick one), newest first — resume with `chat --session <会话ID>`, read with `history <会话ID>`, fork with `--new`. Falls back to the local record when offline / a2a. |
|
|
99
|
+
| `arkclaw history <session>` | Print a past conversation's full transcript (ws `chat.history`). `<session>` = a 会话ID from `sessions`, a label, or a full `agent:…` key. Tool steps hidden unless `--show-tools`; `--json` dumps every message; `-o` writes a plain-text log. openclaw only. |
|
|
89
100
|
| `arkclaw profile save/use/list` | Named snapshots of the session config (multi-space / multi-claw). |
|
|
90
101
|
| `arkclaw doctor` | Self-check: login freshness, keychain, pool/STS/endpoint reachability. |
|
|
91
102
|
| `arkclaw schema --json` | Machine-readable command surface (for agents/tooling). |
|
|
@@ -111,12 +122,21 @@ arkclaw chat ci-xxxx -f notes.md -m "review" -o out.md # attach context, save
|
|
|
111
122
|
|
|
112
123
|
---
|
|
113
124
|
|
|
114
|
-
## Files
|
|
125
|
+
## Files & storage
|
|
126
|
+
|
|
127
|
+
The CLI moves files across **three distinct planes** — pick by what you're carrying:
|
|
128
|
+
|
|
129
|
+
| Plane | Commands | What lives there |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| **Managed brain** | `ls` · `pull` · `push` | The claw's own config files (`AGENTS.md`, `SOUL.md`, …). A fixed allow-list, not a general store. |
|
|
132
|
+
| **Workspace (Silk)** | `ls <path>` · `upload` · `download` · `mkdir` | The claw's *real* working filesystem — arbitrary files, uploads, agent outputs, subdirectories. |
|
|
133
|
+
| **Netdisk (IDS)** | `netdisk …` | A separate large-object store for big products/videos, on a dedicated storage account. |
|
|
115
134
|
|
|
116
|
-
`ls` / `pull` / `push`
|
|
117
|
-
|
|
118
|
-
`
|
|
119
|
-
|
|
135
|
+
### 1. Managed brain files — `ls` / `pull` / `push`
|
|
136
|
+
|
|
137
|
+
The claw's "brain": `AGENTS.md`, `SOUL.md`, `MEMORY.md`, `TOOLS.md`,
|
|
138
|
+
`IDENTITY.md`, `USER.md`, `HEARTBEAT.md`. A fixed allow-list — arbitrary
|
|
139
|
+
filenames are rejected; `push` only writes text.
|
|
120
140
|
|
|
121
141
|
```bash
|
|
122
142
|
arkclaw ls # list them (defaults to your default claw)
|
|
@@ -126,6 +146,51 @@ arkclaw push ./agents.md AGENTS.md # write a managed file
|
|
|
126
146
|
arkclaw ls --clawid ci-other # a different claw
|
|
127
147
|
```
|
|
128
148
|
|
|
149
|
+
### 2. Workspace filesystem — `upload` / `download` / `mkdir` / `ls <path>`
|
|
150
|
+
|
|
151
|
+
The claw's **real** working filesystem (`/root/.openclaw/workspace`), over the
|
|
152
|
+
Silk file service. Paths are workspace-relative. Use it to feed an agent input
|
|
153
|
+
files and to retrieve whatever it produces.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
arkclaw upload ./input.png # → workspace root (basename)
|
|
157
|
+
arkclaw upload ./data.csv runs/data.csv # → a subpath
|
|
158
|
+
arkclaw mkdir runs # parent must exist (non-recursive)
|
|
159
|
+
arkclaw ls runs # list a workspace directory
|
|
160
|
+
arkclaw download runs/output.mp4 ./out.mp4 # pull a result back
|
|
161
|
+
arkclaw download runs ./runs.tar # a directory downloads as a .tar
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **Requires a Silk-enabled claw.** A claw you can chat but that isn't
|
|
165
|
+
provisioned into Silk is rejected with a clear "未启用文件服务(Silk)" error
|
|
166
|
+
(`ErrClawInstanceNotFound`); Silk runs the *same* per-user gate as chat.
|
|
167
|
+
- **Upload cap** is server-side (~25 MB per file); larger uploads return a clear
|
|
168
|
+
"文件超出上传上限" error.
|
|
169
|
+
- There is **no `rm`** here — workspace deletes aren't exposed as a command.
|
|
170
|
+
|
|
171
|
+
### 3. Netdisk (IDS) — `netdisk …`
|
|
172
|
+
|
|
173
|
+
A separate storage plane for large products/videos, on a **dedicated IDS
|
|
174
|
+
account** (not your SSO identity). Configured entirely from `IDS_*` env vars
|
|
175
|
+
(provisioned by the storage team). Create a space once, then pass its `SpaceID`:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
arkclaw netdisk create-space my-space # → prints the SpaceID
|
|
179
|
+
arkclaw netdisk mkdir outputs --space spc-...
|
|
180
|
+
arkclaw netdisk upload ./final.mp4 outputs/final.mp4 --space spc-...
|
|
181
|
+
arkclaw netdisk ls outputs/ --space spc-...
|
|
182
|
+
arkclaw netdisk download outputs/final.mp4 ./final.mp4 --space spc-...
|
|
183
|
+
arkclaw netdisk rm outputs/final.mp4 --space spc-... # asks unless --yes
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`create-space` also takes `--description`; `ls` takes `--limit N` to cap entries.
|
|
187
|
+
|
|
188
|
+
| Env | Purpose |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `IDS_BASE_URL` / `IDS_REGION` / `IDS_SERVICE` / `IDS_INSTANCE_ID` | Endpoint + routing for the IDS instance. |
|
|
191
|
+
| `IDS_AK` / `IDS_SK` (+ `IDS_ACCOUNT_ID`) | Static credentials for the storage account… |
|
|
192
|
+
| `IDS_ROLE_TRN` | …or assume a role via SSO instead of static AK/SK. |
|
|
193
|
+
|
|
129
194
|
---
|
|
130
195
|
|
|
131
196
|
## For agents / automation
|
|
@@ -162,15 +227,18 @@ values via env:
|
|
|
162
227
|
| `ARKCLAW_CLIENT_ID` | The CLI's public OAuth client id for the pool. |
|
|
163
228
|
| `ARKCLAW_ROLE_TRN` | STS role that mints the ChatToken (openclaw). |
|
|
164
229
|
| `ARKCLAW_REGION` / `ARKCLAW_SPACE_ID` | Override region / space (usually auto). |
|
|
230
|
+
| `ARKCLAW_API_HOST` / `ARKCLAW_API_SERVICE` | **Advanced/testing.** Point the control-plane calls at a non-production plane (host + SigV4 service). See the note below. |
|
|
165
231
|
|
|
166
232
|
Other config: `~/.arkclaw/defaults.json` (`init` answers, 0600), `~/.arkclaw/session.json` (routing, 0600), `~/.arkclaw/cmdpolicy.json` (tool-approval allow/deny), OS keychain (tokens).
|
|
167
233
|
|
|
168
234
|
> **Escape hatch (admin/test):** `arkclaw login <url> --transport openclaw --static-creds` uses `VOLCENGINE_ACCESS_KEY`/`SECRET_KEY` from the env instead of SSO — convenient for CI, but it's account-level keys, not identity-through.
|
|
169
235
|
|
|
236
|
+
> **Control-plane override (advanced/testing):** by default every control-plane call (chat/terminal token mints, file/fleet actions, `doctor`) targets `arkclaw.<region>.volcengineapi.com` with SigV4 service `arkclaw`. Setting `ARKCLAW_API_HOST` + `ARKCLAW_API_SERVICE` retargets them — e.g. a non-production plane. The service is also the IAM action prefix, so a non-default service needs the matching role permissions. The host receives the **signed request including live temporary credentials**, so it must be a host you trust — only `*.volcengineapi.com` is accepted.
|
|
237
|
+
|
|
170
238
|
---
|
|
171
239
|
|
|
172
240
|
## Notes
|
|
173
241
|
|
|
174
|
-
- `ls`/`pull`/`push` and a bare `chat` use your **default claw** (set at login, or the last one you opened in the browser); override with `--clawid` (or `chat ci-...`).
|
|
175
|
-
-
|
|
242
|
+
- `ls`/`pull`/`push`, `upload`/`download`/`mkdir`, and a bare `chat` use your **default claw** (set at login, or the last one you opened in the browser); override with `--clawid` (or `chat ci-...`).
|
|
243
|
+
- `pull`/`push` reach the claw's **managed** files; `upload`/`download` reach its **workspace filesystem** (Silk, arbitrary files); `netdisk …` is a **separate** storage plane on its own account.
|
|
176
244
|
- Nothing is hardcoded per space — the CLI reads what the space serves.
|
|
@@ -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.9.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"
|
|
@@ -24,6 +24,7 @@ dependencies = [
|
|
|
24
24
|
"typer>=0.12.0",
|
|
25
25
|
"websockets>=12.0",
|
|
26
26
|
"keyring>=24.0",
|
|
27
|
+
"msgpack>=1.0",
|
|
27
28
|
]
|
|
28
29
|
|
|
29
30
|
[project.optional-dependencies]
|
|
@@ -11,11 +11,25 @@ import pathlib
|
|
|
11
11
|
from ee_claw.errors import ValidationError
|
|
12
12
|
from ee_claw.transport.base import Attachment
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# Text attachments inline into the prompt, so they stay tight; binary ones
|
|
15
|
+
# (images, pdf, …) ride the native chat.send `attachments` field and only need
|
|
16
|
+
# to fit under the server's ws payload cap (~25MB; base64 inflates ×4/3).
|
|
17
|
+
MAX_FILE_BYTES = 64 * 1024 # per text file (inlined into the prompt)
|
|
18
|
+
MAX_BINARY_FILE_BYTES = 10 * 1024 * 1024 # per binary file (native attachment)
|
|
19
|
+
MAX_TOTAL_BYTES = 12 * 1024 * 1024 # across all attachments in one turn
|
|
16
20
|
MAX_FILES = 32
|
|
17
21
|
|
|
18
22
|
|
|
23
|
+
def _is_text(content: bytes) -> bool:
|
|
24
|
+
"""UTF-8-decodable → treat as text (inline-able); otherwise binary (rides
|
|
25
|
+
the native attachment field as raw bytes)."""
|
|
26
|
+
try:
|
|
27
|
+
content.decode("utf-8")
|
|
28
|
+
return True
|
|
29
|
+
except UnicodeDecodeError:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
19
33
|
def collect_files(paths: list[str]) -> tuple[list[Attachment], list[str]]:
|
|
20
34
|
"""Read files (and directories, shallow-recursively) into attachments.
|
|
21
35
|
Returns ``(attachments, notices)`` — notices describe everything that was
|
|
@@ -54,11 +68,20 @@ def collect_files(paths: list[str]) -> tuple[list[Attachment], list[str]]:
|
|
|
54
68
|
except OSError as e:
|
|
55
69
|
notices.append(f"读不了 {f}: {e.__class__.__name__}")
|
|
56
70
|
continue
|
|
57
|
-
if
|
|
58
|
-
|
|
59
|
-
content
|
|
71
|
+
if _is_text(content):
|
|
72
|
+
# text inlines into the prompt — truncating just shortens context
|
|
73
|
+
if len(content) > MAX_FILE_BYTES:
|
|
74
|
+
notices.append(f"{f.name} 截断到 {MAX_FILE_BYTES // 1024}KB(原 {len(content)} 字节)")
|
|
75
|
+
content = content[:MAX_FILE_BYTES]
|
|
76
|
+
elif len(content) > MAX_BINARY_FILE_BYTES:
|
|
77
|
+
# NEVER truncate binary — half an image is corrupt; skip it, loudly
|
|
78
|
+
notices.append(
|
|
79
|
+
f"{f.name} 超出 {MAX_BINARY_FILE_BYTES // 1024 // 1024}MB 二进制上限,已跳过"
|
|
80
|
+
f"(原 {len(content)} 字节)"
|
|
81
|
+
)
|
|
82
|
+
continue
|
|
60
83
|
if total + len(content) > MAX_TOTAL_BYTES:
|
|
61
|
-
notices.append(f"总量超出 {MAX_TOTAL_BYTES // 1024}
|
|
84
|
+
notices.append(f"总量超出 {MAX_TOTAL_BYTES // 1024 // 1024}MB,从 {f.name} 起丢弃")
|
|
62
85
|
break
|
|
63
86
|
total += len(content)
|
|
64
87
|
mime = mimetypes.guess_type(f.name)[0] or "application/octet-stream"
|
|
@@ -15,7 +15,7 @@ from typing import Any
|
|
|
15
15
|
import typer
|
|
16
16
|
|
|
17
17
|
from ee_claw import flows
|
|
18
|
-
from ee_claw.errors import ArkclawError
|
|
18
|
+
from ee_claw.errors import ArkclawError, ValidationError
|
|
19
19
|
from ee_claw.output import Emitter
|
|
20
20
|
|
|
21
21
|
app = typer.Typer(
|
|
@@ -219,19 +219,90 @@ def sessions(
|
|
|
219
219
|
json_mode: bool = _json_opt(),
|
|
220
220
|
) -> None:
|
|
221
221
|
"""List an agent's conversations from the server (newest first). Resume one
|
|
222
|
-
with `arkclaw chat <claw> --session <会话ID>`,
|
|
222
|
+
with `arkclaw chat <claw> --session <会话ID>`, read one with
|
|
223
|
+
`arkclaw history <会话ID>`, or start fresh with `--new`."""
|
|
223
224
|
emitter = Emitter(json_mode=json_mode)
|
|
224
225
|
_run(emitter, lambda: flows.do_sessions(emitter, clawid=claw, agent=agent))
|
|
225
226
|
|
|
226
227
|
|
|
228
|
+
@app.command()
|
|
229
|
+
def history(
|
|
230
|
+
session: str = typer.Argument(
|
|
231
|
+
..., help="A 会话ID from `arkclaw sessions`, a session label, or a full agent:… key."
|
|
232
|
+
),
|
|
233
|
+
clawid: str | None = typer.Option(None, "--clawid", help="Claw instance id (ci-...)."),
|
|
234
|
+
agent: str = typer.Option("main", "--agent", help="Which agent the conversation belongs to."),
|
|
235
|
+
output: str | None = typer.Option(
|
|
236
|
+
None, "--output", "-o", help="Write the whole transcript to a file (plain text)."
|
|
237
|
+
),
|
|
238
|
+
show_tools: bool = typer.Option(
|
|
239
|
+
False, "--show-tools", help="Also show tool calls/results (default: conversation only)."
|
|
240
|
+
),
|
|
241
|
+
json_mode: bool = _json_opt(),
|
|
242
|
+
) -> None:
|
|
243
|
+
"""Read a past conversation's full transcript (ws `chat.history`). Get the
|
|
244
|
+
会话ID from `arkclaw sessions`, then `arkclaw history <会话ID>`. `--json`
|
|
245
|
+
returns every message (incl. tool steps); `-o` writes a plain-text log."""
|
|
246
|
+
emitter = Emitter(json_mode=json_mode)
|
|
247
|
+
_run(
|
|
248
|
+
emitter,
|
|
249
|
+
lambda: flows.do_history(
|
|
250
|
+
emitter, session, clawid=clawid, agent=agent, output=output, show_tools=show_tools
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
227
255
|
@app.command(name="ls")
|
|
228
256
|
def ls_files(
|
|
257
|
+
path: str | None = typer.Argument(
|
|
258
|
+
None, help="Workspace dir to list over Silk (e.g. '.', 'sub/dir'). Omit for the claw's managed brain files."
|
|
259
|
+
),
|
|
260
|
+
clawid: str | None = typer.Option(None, "--clawid", help="Claw instance id (ci-...)."),
|
|
261
|
+
json_mode: bool = _json_opt(),
|
|
262
|
+
) -> None:
|
|
263
|
+
"""List files. No PATH → the claw's managed brain files (AGENTS.md/SOUL.md/…).
|
|
264
|
+
With a PATH → that workspace directory over Silk (the real filesystem: your
|
|
265
|
+
uploads, subdirs, everything; needs a Silk-enabled claw)."""
|
|
266
|
+
emitter = Emitter(json_mode=json_mode)
|
|
267
|
+
if path is None:
|
|
268
|
+
_run(emitter, lambda: flows.do_ls(emitter, clawid=clawid))
|
|
269
|
+
else:
|
|
270
|
+
_run(emitter, lambda: flows.do_files_ls(emitter, path, clawid=clawid))
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@app.command()
|
|
274
|
+
def upload(
|
|
275
|
+
local: str = typer.Argument(..., help="Local file to upload."),
|
|
276
|
+
remote: str | None = typer.Argument(None, help="Workspace path (default: basename at the workspace root)."),
|
|
229
277
|
clawid: str | None = typer.Option(None, "--clawid", help="Claw instance id (ci-...)."),
|
|
230
278
|
json_mode: bool = _json_opt(),
|
|
231
279
|
) -> None:
|
|
232
|
-
"""
|
|
280
|
+
"""Upload any file into the claw workspace (Silk). Needs a Silk-enabled claw."""
|
|
233
281
|
emitter = Emitter(json_mode=json_mode)
|
|
234
|
-
_run(emitter, lambda: flows.
|
|
282
|
+
_run(emitter, lambda: flows.do_upload(emitter, local, remote, clawid=clawid))
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@app.command()
|
|
286
|
+
def download(
|
|
287
|
+
remote: str = typer.Argument(..., help="Workspace path to download (a dir comes as <name>.tar)."),
|
|
288
|
+
local: str | None = typer.Argument(None, help="Local path (default: same name here)."),
|
|
289
|
+
clawid: str | None = typer.Option(None, "--clawid", help="Claw instance id (ci-...)."),
|
|
290
|
+
json_mode: bool = _json_opt(),
|
|
291
|
+
) -> None:
|
|
292
|
+
"""Download a workspace file (or directory as a .tar) from the claw (Silk)."""
|
|
293
|
+
emitter = Emitter(json_mode=json_mode)
|
|
294
|
+
_run(emitter, lambda: flows.do_download(emitter, remote, local, clawid=clawid))
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@app.command()
|
|
298
|
+
def mkdir(
|
|
299
|
+
path: str = typer.Argument(..., help="Workspace directory to create (parent must exist)."),
|
|
300
|
+
clawid: str | None = typer.Option(None, "--clawid", help="Claw instance id (ci-...)."),
|
|
301
|
+
json_mode: bool = _json_opt(),
|
|
302
|
+
) -> None:
|
|
303
|
+
"""Create a directory in the claw workspace (Silk)."""
|
|
304
|
+
emitter = Emitter(json_mode=json_mode)
|
|
305
|
+
_run(emitter, lambda: flows.do_mkdir(emitter, path, clawid=clawid))
|
|
235
306
|
|
|
236
307
|
|
|
237
308
|
@app.command()
|
|
@@ -346,6 +417,88 @@ def fanout(
|
|
|
346
417
|
)
|
|
347
418
|
|
|
348
419
|
|
|
420
|
+
def _space_opt() -> str:
|
|
421
|
+
return typer.Option(..., "--space", help="Netdisk SpaceID (from `netdisk create-space`).")
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
netdisk_app = typer.Typer(
|
|
425
|
+
no_args_is_help=True,
|
|
426
|
+
help="IDS 网盘 (large products / videos): a separate storage plane. Configure "
|
|
427
|
+
"via IDS_* env vars (base_url/account/ak/sk/region/service/instance, from the storage team).",
|
|
428
|
+
)
|
|
429
|
+
app.add_typer(netdisk_app, name="netdisk")
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@netdisk_app.command("create-space")
|
|
433
|
+
def netdisk_create_space(
|
|
434
|
+
name: str = typer.Argument(..., help="Space name (unique within the instance)."),
|
|
435
|
+
description: str | None = typer.Option(None, "--description"),
|
|
436
|
+
json_mode: bool = _json_opt(),
|
|
437
|
+
) -> None:
|
|
438
|
+
"""Create a netdisk space (returns the SpaceID for later file ops)."""
|
|
439
|
+
emitter = Emitter(json_mode=json_mode)
|
|
440
|
+
_run(emitter, lambda: flows.do_netdisk_create_space(emitter, name, description=description))
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@netdisk_app.command("ls")
|
|
444
|
+
def netdisk_ls(
|
|
445
|
+
prefix: str = typer.Argument("", help="Dir prefix to list (e.g. 'outputs/'). Omit for root."),
|
|
446
|
+
space: str = _space_opt(),
|
|
447
|
+
limit: int | None = typer.Option(None, "--limit", help="Max entries to return."),
|
|
448
|
+
json_mode: bool = _json_opt(),
|
|
449
|
+
) -> None:
|
|
450
|
+
"""List a netdisk directory."""
|
|
451
|
+
emitter = Emitter(json_mode=json_mode)
|
|
452
|
+
_run(emitter, lambda: flows.do_netdisk_ls(emitter, space, prefix, limit=limit))
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
@netdisk_app.command("upload")
|
|
456
|
+
def netdisk_upload(
|
|
457
|
+
local: str = typer.Argument(..., help="Local file to upload."),
|
|
458
|
+
remote: str | None = typer.Argument(None, help="Netdisk path (default: basename)."),
|
|
459
|
+
space: str = _space_opt(),
|
|
460
|
+
json_mode: bool = _json_opt(),
|
|
461
|
+
) -> None:
|
|
462
|
+
"""Upload a file into a netdisk space."""
|
|
463
|
+
emitter = Emitter(json_mode=json_mode)
|
|
464
|
+
_run(emitter, lambda: flows.do_netdisk_upload(emitter, space, local, remote))
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
@netdisk_app.command("download")
|
|
468
|
+
def netdisk_download(
|
|
469
|
+
remote: str = typer.Argument(..., help="Netdisk file path to download."),
|
|
470
|
+
local: str | None = typer.Argument(None, help="Local path (default: same name here)."),
|
|
471
|
+
space: str = _space_opt(),
|
|
472
|
+
json_mode: bool = _json_opt(),
|
|
473
|
+
) -> None:
|
|
474
|
+
"""Download a netdisk file (streamed to disk)."""
|
|
475
|
+
emitter = Emitter(json_mode=json_mode)
|
|
476
|
+
_run(emitter, lambda: flows.do_netdisk_download(emitter, space, remote, local))
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@netdisk_app.command("mkdir")
|
|
480
|
+
def netdisk_mkdir(
|
|
481
|
+
path: str = typer.Argument(..., help="Netdisk folder path to create."),
|
|
482
|
+
space: str = _space_opt(),
|
|
483
|
+
json_mode: bool = _json_opt(),
|
|
484
|
+
) -> None:
|
|
485
|
+
"""Create a folder in a netdisk space."""
|
|
486
|
+
emitter = Emitter(json_mode=json_mode)
|
|
487
|
+
_run(emitter, lambda: flows.do_netdisk_mkdir(emitter, space, path))
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
@netdisk_app.command("rm")
|
|
491
|
+
def netdisk_rm(
|
|
492
|
+
remote: str = typer.Argument(..., help="Netdisk file path to delete."),
|
|
493
|
+
space: str = _space_opt(),
|
|
494
|
+
yes: bool = typer.Option(False, "--yes", help="Skip the confirmation prompt."),
|
|
495
|
+
json_mode: bool = _json_opt(),
|
|
496
|
+
) -> None:
|
|
497
|
+
"""Delete a netdisk file (asks for confirmation unless --yes)."""
|
|
498
|
+
emitter = Emitter(json_mode=json_mode)
|
|
499
|
+
_run(emitter, lambda: flows.do_netdisk_rm(emitter, space, remote, yes=yes))
|
|
500
|
+
|
|
501
|
+
|
|
349
502
|
claw_app = typer.Typer(
|
|
350
503
|
no_args_is_help=True,
|
|
351
504
|
help="Fleet: manage claw instances on the control plane.",
|
|
@@ -406,6 +559,61 @@ def doctor(json_mode: bool = _json_opt()) -> None:
|
|
|
406
559
|
raise typer.Exit(code if data["healthy"] else 1)
|
|
407
560
|
|
|
408
561
|
|
|
562
|
+
@app.command()
|
|
563
|
+
def terminal(
|
|
564
|
+
ci_id: str | None = typer.Argument(
|
|
565
|
+
None,
|
|
566
|
+
metavar="[CI_ID]",
|
|
567
|
+
help="Claw instance id (ci-...) to open a terminal in — the runtime of the "
|
|
568
|
+
"claw you chat with. Omit to use the logged-in claw.",
|
|
569
|
+
),
|
|
570
|
+
cmd: str | None = typer.Option(
|
|
571
|
+
None,
|
|
572
|
+
"--command",
|
|
573
|
+
"--cmd",
|
|
574
|
+
help="Command to run on attach (e.g. codex). Omit for the default shell.",
|
|
575
|
+
),
|
|
576
|
+
detach_keys: str | None = typer.Option(
|
|
577
|
+
None,
|
|
578
|
+
"--detach-keys",
|
|
579
|
+
help="Detach key sequence as `ctrl-<letter>,…` (default: ctrl-p,ctrl-q).",
|
|
580
|
+
),
|
|
581
|
+
no_pty: bool = typer.Option(
|
|
582
|
+
False,
|
|
583
|
+
"--no-pty",
|
|
584
|
+
help="(reserved) Non-interactive one-shot exec — not implemented yet.",
|
|
585
|
+
),
|
|
586
|
+
json_mode: bool = _json_opt(),
|
|
587
|
+
) -> None:
|
|
588
|
+
"""Attach an interactive PTY in the runtime of the claw you chat with
|
|
589
|
+
(`docker exec -it` style) — run codex / any TUI in your agent's own
|
|
590
|
+
environment, using the SAME login as `arkclaw chat`. A normal exit returns 0
|
|
591
|
+
— the interactive ws can't carry the remote shell's numeric exit code."""
|
|
592
|
+
emitter = Emitter(json_mode=json_mode)
|
|
593
|
+
# Custom exit (like `doctor`): do_terminal returns a process exit code that is
|
|
594
|
+
# NOT the ExitCode taxonomy.
|
|
595
|
+
try:
|
|
596
|
+
if no_pty:
|
|
597
|
+
raise ValidationError(
|
|
598
|
+
"--no-pty(非交互一次性 exec)暂未实现。",
|
|
599
|
+
hint="去掉 --no-pty 以交互式 attach 终端。",
|
|
600
|
+
)
|
|
601
|
+
code = flows.do_terminal(
|
|
602
|
+
emitter,
|
|
603
|
+
clawid=ci_id,
|
|
604
|
+
cmd=cmd,
|
|
605
|
+
detach_keys=detach_keys,
|
|
606
|
+
json_mode=json_mode,
|
|
607
|
+
)
|
|
608
|
+
except ArkclawError as exc:
|
|
609
|
+
raise typer.Exit(emitter.fail(exc)) from exc
|
|
610
|
+
except typer.Exit:
|
|
611
|
+
raise
|
|
612
|
+
except Exception as exc: # noqa: BLE001 — last-resort guard, still redacted
|
|
613
|
+
raise typer.Exit(emitter.fail(exc)) from exc
|
|
614
|
+
raise typer.Exit(code)
|
|
615
|
+
|
|
616
|
+
|
|
409
617
|
profile_app = typer.Typer(
|
|
410
618
|
no_args_is_help=True,
|
|
411
619
|
help="Profiles: named snapshots of the session config (multi-pool / multi-claw).",
|
|
@@ -268,7 +268,17 @@ def read_profile(name: str) -> SessionConfig:
|
|
|
268
268
|
path = _profile_path(name)
|
|
269
269
|
if not path.exists():
|
|
270
270
|
raise ValidationError(f"profile 不存在: {name}", hint="arkclaw profile list 查看。")
|
|
271
|
-
|
|
271
|
+
try: # a corrupt profile is a recoverable "rebuild it" condition, not a crash
|
|
272
|
+
raw = json.loads(path.read_text())
|
|
273
|
+
if not isinstance(raw, dict):
|
|
274
|
+
raise ValueError("profile is not a JSON object")
|
|
275
|
+
except (ValueError, OSError) as exc:
|
|
276
|
+
# TokenError ⊂ ValidationError → callers' graceful-fallback paths catch it.
|
|
277
|
+
# No file contents in the message (no secret leak), mirroring load_config.
|
|
278
|
+
raise TokenError(
|
|
279
|
+
f"profile 文件已损坏: {name}",
|
|
280
|
+
hint="删除后重建:arkclaw profile remove <name>,再 arkclaw login。",
|
|
281
|
+
) from exc
|
|
272
282
|
fields = SessionConfig.__dataclass_fields__
|
|
273
283
|
return SessionConfig(**{k: v for k, v in raw.items() if k in fields})
|
|
274
284
|
|