deepflow-openagent-cli 0.1.7__tar.gz → 0.1.8__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.
Files changed (15) hide show
  1. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/PKG-INFO +91 -1
  2. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/README.md +90 -0
  3. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/pyproject.toml +1 -1
  4. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/src/deepflow_openagent_cli/__init__.py +1 -1
  5. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/src/deepflow_openagent_cli/cli.py +92 -0
  6. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/src/deepflow_openagent_cli.egg-info/PKG-INFO +91 -1
  7. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/tests/test_cloudpc_cli.py +100 -0
  8. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/setup.cfg +0 -0
  9. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/src/deepflow_openagent_cli/__main__.py +0 -0
  10. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/src/deepflow_openagent_cli.egg-info/SOURCES.txt +0 -0
  11. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/src/deepflow_openagent_cli.egg-info/dependency_links.txt +0 -0
  12. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/src/deepflow_openagent_cli.egg-info/entry_points.txt +0 -0
  13. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/src/deepflow_openagent_cli.egg-info/requires.txt +0 -0
  14. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/src/deepflow_openagent_cli.egg-info/top_level.txt +0 -0
  15. {deepflow_openagent_cli-0.1.7 → deepflow_openagent_cli-0.1.8}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepflow-openagent-cli
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Command-line client for OpenAgent OpenAPI (agents, conversations, SSE chat, tools, channels, knowledge bases).
5
5
  Author: DeepFlow AI
6
6
  Project-URL: Repository, https://github.com/DeepFlowAI/OpenAgent
@@ -280,3 +280,93 @@ After installation or Agent edits, use `cloudpc versions save` and optionally
280
280
  `versions publish` to preserve and publish the environment. Deploy the updated
281
281
  OpenAgent backend, cloudpc VM service, and bridge bundle together; update the CLI
282
282
  package as well. No database migration is required.
283
+
284
+ ### Cloud PC channels
285
+
286
+ Cloud PC channel settings use `cloudpc channels`, separately from Agent settings
287
+ (`cloudpc config`) and Web SDK channels (`channels`). These commands require an
288
+ API key with `config` scope and Cloud PC enabled for the tenant. The backend must
289
+ also include support for API key authentication on `/api/v1/cloudpc/channels`;
290
+ older backends accept administrator sessions only.
291
+
292
+ ```bash
293
+ deepflow_openagent_cli cloudpc channels list --page 1 --per-page 20
294
+ deepflow_openagent_cli cloudpc channels create --name "Cloud workspace"
295
+ deepflow_openagent_cli cloudpc channels agent-options
296
+ deepflow_openagent_cli cloudpc channels get 7
297
+ deepflow_openagent_cli docs show cloudpc.channels.update --json
298
+ ```
299
+
300
+ Update uses the existing PUT replacement contract. Read the current channel
301
+ first and prepare `channel.json` with the complete name, description, ordered
302
+ `agent_ids` and `appearance`. Convert the returned `bindings` to their `id`
303
+ values; the GET response is not itself an update payload.
304
+
305
+ ```json
306
+ {
307
+ "name": "Cloud workspace",
308
+ "description": "Team assistant",
309
+ "agent_ids": [123, 456],
310
+ "appearance": {
311
+ "pageTitle": "Workspace",
312
+ "title": "Assistant",
313
+ "embedButtonBgColor": "#18181B",
314
+ "embedButtonIconColor": "#FFFFFF",
315
+ "userMessageStyle": {
316
+ "backgroundColor": "#1A1A1A",
317
+ "borderColor": "#CCCCCC",
318
+ "textColor": "#FFFFFF",
319
+ "borderRadius": {
320
+ "topLeft": 14,
321
+ "topRight": 14,
322
+ "bottomLeft": 14,
323
+ "bottomRight": 4
324
+ }
325
+ }
326
+ },
327
+ "require_auth": false,
328
+ "file_upload": {
329
+ "enabled": true,
330
+ "allowed_extensions": [".pdf", ".txt"]
331
+ }
332
+ }
333
+ ```
334
+
335
+ ```bash
336
+ deepflow_openagent_cli cloudpc channels update 7 --json-file ./channel.json
337
+ deepflow_openagent_cli cloudpc channels get 7
338
+ ```
339
+
340
+ `--json` also accepts an inline JSON object. `name` is required; omitted
341
+ `description`, `agent_ids` and `appearance` reset to null, an empty list and
342
+ empty appearance respectively. Omitted/null `require_auth` and `file_upload`
343
+ preserve the current values. Enabling uploads requires at least one extension.
344
+ Only Cloud PCs belonging to the current tenant can be bound (up to 20).
345
+
346
+ User message styles are configured under `appearance.userMessageStyle` in the
347
+ same update payload. They apply to both main and follow-up messages, including
348
+ history and SDK embeds, after opening or refreshing the channel. They do not
349
+ change Agent replies or the admin workbench.
350
+
351
+ | Field | Value | Reset behavior |
352
+ | --- | --- | --- |
353
+ | `backgroundColor` | `#RRGGBB` | Empty string, null or omission restores each bubble's default |
354
+ | `borderColor` | `#RRGGBB`, rendered as a 1px solid border | Empty string, null or omission removes the border |
355
+ | `textColor` | `#RRGGBB`, also applied to the follow-up label | Empty string, null or omission restores the default |
356
+ | `borderRadius` | Object containing all four integer corners, 0–100 px | Null or omission restores each bubble's default |
357
+
358
+ The server trims and uppercases colors. Radius keys are `topLeft`, `topRight`,
359
+ `bottomLeft`, and `bottomRight`; partial groups, negative/fractional/out-of-range
360
+ values, strings and booleans are rejected with HTTP 422 (CLI exits nonzero).
361
+ To clear only one override, set that field to null while retaining the other
362
+ settings. To reset all user message styles, set `"userMessageStyle": null` in
363
+ the complete `channel.json`, then run the same update command. The outer PUT
364
+ still replaces `appearance` and bindings: keep all unrelated settings from the
365
+ GET response in the payload. Reading the channel returns the saved style under
366
+ `appearance.userMessageStyle`.
367
+
368
+ `deepflow_openagent_cli cloudpc channels delete 7` deletes the channel and
369
+ destroys its instances. The server entry point
370
+ `python -m app.openapi_cli cloudpc channels ...` supports the same commands.
371
+ This feature requires updating the backend and CLI; no database migration or
372
+ Cloud PC sandbox image update is needed.
@@ -259,3 +259,93 @@ After installation or Agent edits, use `cloudpc versions save` and optionally
259
259
  `versions publish` to preserve and publish the environment. Deploy the updated
260
260
  OpenAgent backend, cloudpc VM service, and bridge bundle together; update the CLI
261
261
  package as well. No database migration is required.
262
+
263
+ ### Cloud PC channels
264
+
265
+ Cloud PC channel settings use `cloudpc channels`, separately from Agent settings
266
+ (`cloudpc config`) and Web SDK channels (`channels`). These commands require an
267
+ API key with `config` scope and Cloud PC enabled for the tenant. The backend must
268
+ also include support for API key authentication on `/api/v1/cloudpc/channels`;
269
+ older backends accept administrator sessions only.
270
+
271
+ ```bash
272
+ deepflow_openagent_cli cloudpc channels list --page 1 --per-page 20
273
+ deepflow_openagent_cli cloudpc channels create --name "Cloud workspace"
274
+ deepflow_openagent_cli cloudpc channels agent-options
275
+ deepflow_openagent_cli cloudpc channels get 7
276
+ deepflow_openagent_cli docs show cloudpc.channels.update --json
277
+ ```
278
+
279
+ Update uses the existing PUT replacement contract. Read the current channel
280
+ first and prepare `channel.json` with the complete name, description, ordered
281
+ `agent_ids` and `appearance`. Convert the returned `bindings` to their `id`
282
+ values; the GET response is not itself an update payload.
283
+
284
+ ```json
285
+ {
286
+ "name": "Cloud workspace",
287
+ "description": "Team assistant",
288
+ "agent_ids": [123, 456],
289
+ "appearance": {
290
+ "pageTitle": "Workspace",
291
+ "title": "Assistant",
292
+ "embedButtonBgColor": "#18181B",
293
+ "embedButtonIconColor": "#FFFFFF",
294
+ "userMessageStyle": {
295
+ "backgroundColor": "#1A1A1A",
296
+ "borderColor": "#CCCCCC",
297
+ "textColor": "#FFFFFF",
298
+ "borderRadius": {
299
+ "topLeft": 14,
300
+ "topRight": 14,
301
+ "bottomLeft": 14,
302
+ "bottomRight": 4
303
+ }
304
+ }
305
+ },
306
+ "require_auth": false,
307
+ "file_upload": {
308
+ "enabled": true,
309
+ "allowed_extensions": [".pdf", ".txt"]
310
+ }
311
+ }
312
+ ```
313
+
314
+ ```bash
315
+ deepflow_openagent_cli cloudpc channels update 7 --json-file ./channel.json
316
+ deepflow_openagent_cli cloudpc channels get 7
317
+ ```
318
+
319
+ `--json` also accepts an inline JSON object. `name` is required; omitted
320
+ `description`, `agent_ids` and `appearance` reset to null, an empty list and
321
+ empty appearance respectively. Omitted/null `require_auth` and `file_upload`
322
+ preserve the current values. Enabling uploads requires at least one extension.
323
+ Only Cloud PCs belonging to the current tenant can be bound (up to 20).
324
+
325
+ User message styles are configured under `appearance.userMessageStyle` in the
326
+ same update payload. They apply to both main and follow-up messages, including
327
+ history and SDK embeds, after opening or refreshing the channel. They do not
328
+ change Agent replies or the admin workbench.
329
+
330
+ | Field | Value | Reset behavior |
331
+ | --- | --- | --- |
332
+ | `backgroundColor` | `#RRGGBB` | Empty string, null or omission restores each bubble's default |
333
+ | `borderColor` | `#RRGGBB`, rendered as a 1px solid border | Empty string, null or omission removes the border |
334
+ | `textColor` | `#RRGGBB`, also applied to the follow-up label | Empty string, null or omission restores the default |
335
+ | `borderRadius` | Object containing all four integer corners, 0–100 px | Null or omission restores each bubble's default |
336
+
337
+ The server trims and uppercases colors. Radius keys are `topLeft`, `topRight`,
338
+ `bottomLeft`, and `bottomRight`; partial groups, negative/fractional/out-of-range
339
+ values, strings and booleans are rejected with HTTP 422 (CLI exits nonzero).
340
+ To clear only one override, set that field to null while retaining the other
341
+ settings. To reset all user message styles, set `"userMessageStyle": null` in
342
+ the complete `channel.json`, then run the same update command. The outer PUT
343
+ still replaces `appearance` and bindings: keep all unrelated settings from the
344
+ GET response in the payload. Reading the channel returns the saved style under
345
+ `appearance.userMessageStyle`.
346
+
347
+ `deepflow_openagent_cli cloudpc channels delete 7` deletes the channel and
348
+ destroys its instances. The server entry point
349
+ `python -m app.openapi_cli cloudpc channels ...` supports the same commands.
350
+ This feature requires updating the backend and CLI; no database migration or
351
+ Cloud PC sandbox image update is needed.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepflow-openagent-cli"
7
- version = "0.1.7"
7
+ version = "0.1.8"
8
8
  description = "Command-line client for OpenAgent OpenAPI (agents, conversations, SSE chat, tools, channels, knowledge bases)."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """DeepFlow OpenAgent CLI — published PyPI package."""
2
2
 
3
- __version__ = "0.1.7"
3
+ __version__ = "0.1.8"
@@ -130,6 +130,61 @@ def _param(name: str, type_: str, required: bool, description: str) -> ParamDoc:
130
130
 
131
131
 
132
132
  ENDPOINTS: tuple[EndpointDoc, ...] = (
133
+ EndpointDoc(
134
+ key="cloudpc.channels.list", method="GET", path="/api/v1/cloudpc/channels", scope="config",
135
+ summary="List Cloud PC channels for the current tenant.",
136
+ query_params=[_param("page", "integer", False, "Page number (default 1)."), _param("per_page", "integer", False, "Page size, 1-100 (default 20).")],
137
+ response="CloudPcChannelListResponse.",
138
+ example="deepflow_openagent_cli cloudpc channels list --page 1 --per-page 20",
139
+ ),
140
+ EndpointDoc(
141
+ key="cloudpc.channels.create", method="POST", path="/api/v1/cloudpc/channels", scope="config",
142
+ summary="Create a Cloud PC channel; use update to configure bindings and appearance.",
143
+ body_fields=[_param("name", "string", True, "Channel name, 1-64 characters."), _param("description", "string", False, "Description, max 500 characters.")],
144
+ response="CloudPcChannelDetail, HTTP 201.",
145
+ example='deepflow_openagent_cli cloudpc channels create --name "Cloud workspace"',
146
+ ),
147
+ EndpointDoc(
148
+ key="cloudpc.channels.agent_options", method="GET", path="/api/v1/cloudpc/channels/agent-options", scope="config",
149
+ summary="List enabled Cloud PCs available for binding, including unpublished ones.",
150
+ response="CloudPcAgentOptionList: items with id, name, description, version and status.",
151
+ example="deepflow_openagent_cli cloudpc channels agent-options",
152
+ ),
153
+ EndpointDoc(
154
+ key="cloudpc.channels.get", method="GET", path="/api/v1/cloudpc/channels/{channel_id}", scope="config",
155
+ summary="Get Cloud PC channel settings and ordered bindings.",
156
+ path_params=[_param("channel_id", "integer", True, "Cloud PC channel ID.")],
157
+ response="CloudPcChannelDetail: id, name, description, token, appearance, bindings, require_auth, has_secret_key, file_upload and timestamps.",
158
+ example="deepflow_openagent_cli cloudpc channels get 1",
159
+ ),
160
+ EndpointDoc(
161
+ key="cloudpc.channels.update", method="PUT", path="/api/v1/cloudpc/channels/{channel_id}", scope="config",
162
+ summary="Replace channel name, description, bindings and appearance; omitted require_auth/file_upload are preserved.",
163
+ path_params=[_param("channel_id", "integer", True, "Cloud PC channel ID.")],
164
+ body_fields=[
165
+ _param("name", "string", True, "Channel name, 1-64 characters."),
166
+ _param("description", "string", False, "Description, max 500 characters; omitted resets to null."),
167
+ _param("agent_ids", "array", False, "Complete ordered list of Cloud PC Agent IDs, max 20 unique IDs; omitted clears bindings. Convert GET bindings to their id values."),
168
+ _param("appearance", "object", False, "Complete appearance: favicon, pageTitle, logo, title, embedButtonBgColor, embedButtonIconColor, userMessageStyle; omitted clears appearance. Titles max 32 characters; colors #RRGGBB."),
169
+ _param("appearance.userMessageStyle", "object|null", False, "Overrides main and follow-up user bubbles in this channel, including history and embeds. Omitted/null restores defaults; other appearance fields must still be supplied."),
170
+ _param("appearance.userMessageStyle.backgroundColor", "string|null", False, "Background #RRGGBB. Server trims and uppercases; empty string/null restores each bubble's default."),
171
+ _param("appearance.userMessageStyle.borderColor", "string|null", False, "Border #RRGGBB; 1px solid when set. Empty string/null removes the border."),
172
+ _param("appearance.userMessageStyle.textColor", "string|null", False, "Text #RRGGBB, including the follow-up label. Empty string/null restores the default."),
173
+ _param("appearance.userMessageStyle.borderRadius", "object|null", False, "All four corners are required when set: topLeft, topRight, bottomLeft, bottomRight, each an integer 0-100 px. Null restores each bubble's default; partial groups, strings, floats and booleans are rejected."),
174
+ _param("require_auth", "boolean", False, "Require signed visitor authentication; omitted/null preserves current value."),
175
+ _param("file_upload", "object", False, "enabled and allowed_extensions; enabled uploads require at least one extension. Omitted/null preserves current value."),
176
+ ],
177
+ response="CloudPcChannelDetail.",
178
+ errors=["400 invalid bindings or duplicate name", "401 invalid API key", "403 config scope missing, non-admin session or Cloud PC disabled", "404 channel not found in current tenant", "422 validation error"],
179
+ example="deepflow_openagent_cli cloudpc channels update 1 --json-file channel.json",
180
+ ),
181
+ EndpointDoc(
182
+ key="cloudpc.channels.delete", method="DELETE", path="/api/v1/cloudpc/channels/{channel_id}", scope="config",
183
+ summary="Delete a Cloud PC channel and destroy its instances.",
184
+ path_params=[_param("channel_id", "integer", True, "Cloud PC channel ID.")],
185
+ response="Deletion message.",
186
+ example="deepflow_openagent_cli cloudpc channels delete 1",
187
+ ),
133
188
  EndpointDoc(
134
189
  key="cloudpc.files.upload", method="POST",
135
190
  path="/api/v1/cloudpc/agents/{agent_id}/dev-sandbox/files/upload", scope="config",
@@ -1925,9 +1980,32 @@ def handle_cloudpc_chat(args: argparse.Namespace) -> int:
1925
1980
  raise
1926
1981
 
1927
1982
 
1983
+ def handle_cloudpc_channels(args: argparse.Namespace) -> int:
1984
+ """Manage Cloud PC channels through their dedicated API."""
1985
+ action = args.cloudpc_action
1986
+ endpoint = f"cloudpc.channels.{action.replace('-', '_')}"
1987
+ if action == "list":
1988
+ return run_request(args, endpoint, query={"page": args.page, "per_page": args.per_page})
1989
+ if action == "agent-options":
1990
+ return run_request(args, endpoint)
1991
+ if action == "create":
1992
+ return run_request(args, endpoint, body={"name": args.name, "description": args.description})
1993
+ body = None
1994
+ if action == "update":
1995
+ try:
1996
+ body = merge_payload(args)
1997
+ except (OSError, UnicodeError) as exc:
1998
+ raise CliError(f"Cannot read channel configuration file: {exc}") from exc
1999
+ if not body.get("name"):
2000
+ raise CliError("Channel update requires name in --json or --json-file.")
2001
+ return run_request(args, endpoint, {"channel_id": args.channel_id}, body=body)
2002
+
2003
+
1928
2004
  def handle_cloudpc(args: argparse.Namespace) -> int:
1929
2005
  """Manage Cloud PC configuration, development files and saved versions."""
1930
2006
  group = args.cloudpc_group
2007
+ if group == "channels":
2008
+ return handle_cloudpc_channels(args)
1931
2009
  if group == "capability":
1932
2010
  return run_request(args, "cloudpc.capability")
1933
2011
  if group == "create":
@@ -2139,6 +2217,20 @@ def build_parser() -> argparse.ArgumentParser:
2139
2217
 
2140
2218
  cloudpc = subparsers.add_parser("cloudpc", help="Cloud PC management (requires config scope).")
2141
2219
  cloudpc_sub = cloudpc.add_subparsers(dest="cloudpc_group", required=True)
2220
+ cloudpc_channels = cloudpc_sub.add_parser("channels", help="Cloud PC channel configuration.").add_subparsers(dest="cloudpc_action", required=True)
2221
+ channel_list = cloudpc_channels.add_parser("list")
2222
+ channel_list.add_argument("--page", type=int)
2223
+ channel_list.add_argument("--per-page", type=int)
2224
+ cloudpc_channels.add_parser("agent-options")
2225
+ channel_create = cloudpc_channels.add_parser("create")
2226
+ channel_create.add_argument("--name", required=True)
2227
+ channel_create.add_argument("--description")
2228
+ for action in ("get", "delete"):
2229
+ cloudpc_channels.add_parser(action).add_argument("channel_id", type=int)
2230
+ channel_update_help = "Replace name/description/bindings/appearance; omitted bindings/appearance are cleared. Omitted require_auth/file_upload are preserved. Set appearance.userMessageStyle in --json or --json-file for user bubble colors and four-corner radius; null restores defaults. See docs show cloudpc.channels.update."
2231
+ channel_update = cloudpc_channels.add_parser("update", help=channel_update_help, description=channel_update_help)
2232
+ channel_update.add_argument("channel_id", type=int)
2233
+ add_json_args(channel_update)
2142
2234
  cloudpc_sub.add_parser("capability")
2143
2235
  cloudpc_create = cloudpc_sub.add_parser("create")
2144
2236
  cloudpc_create.add_argument("--name", required=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepflow-openagent-cli
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Command-line client for OpenAgent OpenAPI (agents, conversations, SSE chat, tools, channels, knowledge bases).
5
5
  Author: DeepFlow AI
6
6
  Project-URL: Repository, https://github.com/DeepFlowAI/OpenAgent
@@ -280,3 +280,93 @@ After installation or Agent edits, use `cloudpc versions save` and optionally
280
280
  `versions publish` to preserve and publish the environment. Deploy the updated
281
281
  OpenAgent backend, cloudpc VM service, and bridge bundle together; update the CLI
282
282
  package as well. No database migration is required.
283
+
284
+ ### Cloud PC channels
285
+
286
+ Cloud PC channel settings use `cloudpc channels`, separately from Agent settings
287
+ (`cloudpc config`) and Web SDK channels (`channels`). These commands require an
288
+ API key with `config` scope and Cloud PC enabled for the tenant. The backend must
289
+ also include support for API key authentication on `/api/v1/cloudpc/channels`;
290
+ older backends accept administrator sessions only.
291
+
292
+ ```bash
293
+ deepflow_openagent_cli cloudpc channels list --page 1 --per-page 20
294
+ deepflow_openagent_cli cloudpc channels create --name "Cloud workspace"
295
+ deepflow_openagent_cli cloudpc channels agent-options
296
+ deepflow_openagent_cli cloudpc channels get 7
297
+ deepflow_openagent_cli docs show cloudpc.channels.update --json
298
+ ```
299
+
300
+ Update uses the existing PUT replacement contract. Read the current channel
301
+ first and prepare `channel.json` with the complete name, description, ordered
302
+ `agent_ids` and `appearance`. Convert the returned `bindings` to their `id`
303
+ values; the GET response is not itself an update payload.
304
+
305
+ ```json
306
+ {
307
+ "name": "Cloud workspace",
308
+ "description": "Team assistant",
309
+ "agent_ids": [123, 456],
310
+ "appearance": {
311
+ "pageTitle": "Workspace",
312
+ "title": "Assistant",
313
+ "embedButtonBgColor": "#18181B",
314
+ "embedButtonIconColor": "#FFFFFF",
315
+ "userMessageStyle": {
316
+ "backgroundColor": "#1A1A1A",
317
+ "borderColor": "#CCCCCC",
318
+ "textColor": "#FFFFFF",
319
+ "borderRadius": {
320
+ "topLeft": 14,
321
+ "topRight": 14,
322
+ "bottomLeft": 14,
323
+ "bottomRight": 4
324
+ }
325
+ }
326
+ },
327
+ "require_auth": false,
328
+ "file_upload": {
329
+ "enabled": true,
330
+ "allowed_extensions": [".pdf", ".txt"]
331
+ }
332
+ }
333
+ ```
334
+
335
+ ```bash
336
+ deepflow_openagent_cli cloudpc channels update 7 --json-file ./channel.json
337
+ deepflow_openagent_cli cloudpc channels get 7
338
+ ```
339
+
340
+ `--json` also accepts an inline JSON object. `name` is required; omitted
341
+ `description`, `agent_ids` and `appearance` reset to null, an empty list and
342
+ empty appearance respectively. Omitted/null `require_auth` and `file_upload`
343
+ preserve the current values. Enabling uploads requires at least one extension.
344
+ Only Cloud PCs belonging to the current tenant can be bound (up to 20).
345
+
346
+ User message styles are configured under `appearance.userMessageStyle` in the
347
+ same update payload. They apply to both main and follow-up messages, including
348
+ history and SDK embeds, after opening or refreshing the channel. They do not
349
+ change Agent replies or the admin workbench.
350
+
351
+ | Field | Value | Reset behavior |
352
+ | --- | --- | --- |
353
+ | `backgroundColor` | `#RRGGBB` | Empty string, null or omission restores each bubble's default |
354
+ | `borderColor` | `#RRGGBB`, rendered as a 1px solid border | Empty string, null or omission removes the border |
355
+ | `textColor` | `#RRGGBB`, also applied to the follow-up label | Empty string, null or omission restores the default |
356
+ | `borderRadius` | Object containing all four integer corners, 0–100 px | Null or omission restores each bubble's default |
357
+
358
+ The server trims and uppercases colors. Radius keys are `topLeft`, `topRight`,
359
+ `bottomLeft`, and `bottomRight`; partial groups, negative/fractional/out-of-range
360
+ values, strings and booleans are rejected with HTTP 422 (CLI exits nonzero).
361
+ To clear only one override, set that field to null while retaining the other
362
+ settings. To reset all user message styles, set `"userMessageStyle": null` in
363
+ the complete `channel.json`, then run the same update command. The outer PUT
364
+ still replaces `appearance` and bindings: keep all unrelated settings from the
365
+ GET response in the payload. Reading the channel returns the saved style under
366
+ `appearance.userMessageStyle`.
367
+
368
+ `deepflow_openagent_cli cloudpc channels delete 7` deletes the channel and
369
+ destroys its instances. The server entry point
370
+ `python -m app.openapi_cli cloudpc channels ...` supports the same commands.
371
+ This feature requires updating the backend and CLI; no database migration or
372
+ Cloud PC sandbox image update is needed.
@@ -44,6 +44,12 @@ def run(cli, *args):
44
44
 
45
45
 
46
46
  @pytest.mark.parametrize(("arguments", "method", "suffix", "body"), [
47
+ (["channels", "list", "--page", "2", "--per-page", "5"], "GET", "/channels?page=2&per_page=5", None),
48
+ (["channels", "create", "--name", "渠道", "--description", "Demo"], "POST", "/channels", {"name": "渠道", "description": "Demo"}),
49
+ (["channels", "agent-options"], "GET", "/channels/agent-options", None),
50
+ (["channels", "get", "7"], "GET", "/channels/7", None),
51
+ (["channels", "update", "7", "--json", '{"name":"渠道","agent_ids":[3,2],"appearance":{"title":"助手"},"require_auth":false,"file_upload":{"enabled":true,"allowed_extensions":[".pdf"]}}'], "PUT", "/channels/7", {"name": "渠道", "agent_ids": [3, 2], "appearance": {"title": "助手"}, "require_auth": False, "file_upload": {"enabled": True, "allowed_extensions": [".pdf"]}}),
52
+ (["channels", "delete", "7"], "DELETE", "/channels/7", None),
47
53
  (["capability"], "GET", "/capability", None),
48
54
  (["create", "--name", "Test"], "POST", "/agents", {"name": "Test", "description": None}),
49
55
  (["config", "get", "7"], "GET", "/agents/7/config", None),
@@ -79,6 +85,100 @@ def test_agents_kind_filter(cli, transport):
79
85
  assert "kind=cloudpc" in calls[0][0].full_url
80
86
 
81
87
 
88
+ def test_channel_update_from_file(cli, transport, tmp_path):
89
+ payload = {"name": "渠道", "description": None, "agent_ids": [], "appearance": {}}
90
+ source = tmp_path / "channel.json"
91
+ source.write_text(json.dumps(payload), encoding="utf-8")
92
+ assert run(cli, "cloudpc", "channels", "update", "7", "--json-file", str(source)) == 0
93
+ calls, _ = transport
94
+ assert len(calls) == 1
95
+ assert json.loads(calls[0][0].data) == payload
96
+
97
+
98
+ @pytest.mark.parametrize("payload", ["{}", "[]", "{invalid"])
99
+ def test_invalid_channel_update_does_not_send_request(cli, transport, payload):
100
+ assert run(cli, "cloudpc", "channels", "update", "7", "--json", payload) == 1
101
+ assert transport[0] == []
102
+
103
+
104
+ def test_missing_channel_file_has_clean_error(cli, transport, tmp_path, capsys):
105
+ assert run(cli, "cloudpc", "channels", "update", "7", "--json-file", str(tmp_path / "missing.json")) == 1
106
+ assert transport[0] == []
107
+ assert "Cannot read channel configuration file" in capsys.readouterr().err
108
+
109
+
110
+ def test_channel_docs_describe_replacement(cli, capsys):
111
+ assert cli.main(["docs", "show", "cloudpc.channels.update", "--json"]) == 0
112
+ doc = json.loads(capsys.readouterr().out)
113
+ assert doc["scope"] == "config"
114
+ assert doc["path"] == "/api/v1/cloudpc/channels/{channel_id}"
115
+ fields = {field["name"]: field for field in doc["body_fields"]}
116
+ assert fields["name"]["required"]
117
+ assert "omitted clears" in fields["agent_ids"]["description"]
118
+ assert "preserves" in fields["file_upload"]["description"]
119
+
120
+
121
+ @pytest.mark.parametrize("source", ["--json", "--json-file"])
122
+ @pytest.mark.parametrize("style", [
123
+ {"backgroundColor": "#123456", "borderColor": "#ABCDEF", "textColor": "#FFFFFF",
124
+ "borderRadius": {"topLeft": 0, "topRight": 100, "bottomLeft": 12, "bottomRight": 4}},
125
+ {"textColor": " #aabbcc "},
126
+ {"backgroundColor": "#123456", "borderColor": None, "textColor": "", "borderRadius": None},
127
+ None,
128
+ ])
129
+ def test_channel_message_style_update_payload(cli, transport, tmp_path, source, style):
130
+ payload = {
131
+ "name": "Workspace", "description": "Keep", "agent_ids": [3, 2],
132
+ "appearance": {"title": "Assistant", "embedButtonBgColor": "#1A1A1A", "userMessageStyle": style},
133
+ "require_auth": True, "file_upload": {"enabled": True, "allowed_extensions": [".pdf"]},
134
+ }
135
+ value = json.dumps(payload)
136
+ if source == "--json-file":
137
+ path = tmp_path / "channel.json"
138
+ path.write_text(value, encoding="utf-8")
139
+ value = str(path)
140
+ assert run(cli, "cloudpc", "channels", "update", "7", source, value) == 0
141
+ calls, _ = transport
142
+ assert len(calls) == 1
143
+ assert calls[0][0].method == "PUT"
144
+ assert json.loads(calls[0][0].data) == payload
145
+
146
+
147
+ def test_channel_message_style_read(cli, transport, capsys):
148
+ detail = {"id": 7, "appearance": {"userMessageStyle": {"textColor": "#AABBCC", "borderRadius": None}}}
149
+ transport[1]["http://test/api/v1/cloudpc/channels/7"] = json.dumps(detail).encode()
150
+ assert run(cli, "cloudpc", "channels", "get", "7") == 0
151
+ assert json.loads(capsys.readouterr().out) == detail
152
+
153
+
154
+ def test_channel_message_style_validation_failure_is_reported_without_retry(cli, transport, capsys):
155
+ url = "http://test/api/v1/cloudpc/channels/7"
156
+ body = {"detail": [{"loc": ["body", "appearance", "userMessageStyle", "borderRadius", "topRight"], "msg": "Field required"}]}
157
+ transport[1][url] = HTTPError(url, 422, "Unprocessable Entity", {}, io.BytesIO(json.dumps(body).encode()))
158
+ payload = {"name": "Workspace", "appearance": {"userMessageStyle": {"borderRadius": {"topLeft": 1}}}}
159
+ assert run(cli, "cloudpc", "channels", "update", "7", "--json", json.dumps(payload)) == 1
160
+ assert len(transport[0]) == 1
161
+ error = capsys.readouterr().err
162
+ assert "HTTP 422" in error and "userMessageStyle" in error and "topRight" in error
163
+
164
+
165
+ def test_channel_message_style_docs_and_help(cli, capsys):
166
+ assert cli.main(["docs", "show", "cloudpc.channels.update", "--json"]) == 0
167
+ fields = {field["name"]: field for field in json.loads(capsys.readouterr().out)["body_fields"]}
168
+ assert "userMessageStyle" in fields["appearance"]["description"]
169
+ prefix = "appearance.userMessageStyle"
170
+ for key in ["backgroundColor", "borderColor", "textColor", "borderRadius"]:
171
+ assert not fields[f"{prefix}.{key}"]["required"]
172
+ assert "null" in fields[f"{prefix}.{key}"]["description"].lower()
173
+ radius = fields[f"{prefix}.borderRadius"]["description"]
174
+ for key in ["topLeft", "topRight", "bottomLeft", "bottomRight", "0-100"]:
175
+ assert key in radius
176
+ with pytest.raises(SystemExit) as exc:
177
+ cli.main(["cloudpc", "channels", "update", "--help"])
178
+ assert exc.value.code == 0
179
+ assert "appearance.userMessageStyle" in capsys.readouterr().out
180
+
181
+
82
182
  @pytest.mark.parametrize(("source", "expected"), [("latest", 23), ("published", 19)])
83
183
  def test_resolve_version_and_report_actual_source(cli, transport, capsys, source, expected):
84
184
  calls, responses = transport