wxz-cli 1.0.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.
Files changed (40) hide show
  1. wxz_cli-1.0.0/LICENSE +21 -0
  2. wxz_cli-1.0.0/PKG-INFO +93 -0
  3. wxz_cli-1.0.0/README.md +63 -0
  4. wxz_cli-1.0.0/pyproject.toml +48 -0
  5. wxz_cli-1.0.0/setup.cfg +4 -0
  6. wxz_cli-1.0.0/src/wxz_cli/__init__.py +7 -0
  7. wxz_cli-1.0.0/src/wxz_cli/acp/__init__.py +1 -0
  8. wxz_cli-1.0.0/src/wxz_cli/acp/event_bridge.py +112 -0
  9. wxz_cli-1.0.0/src/wxz_cli/acp/handlers.py +113 -0
  10. wxz_cli-1.0.0/src/wxz_cli/acp/server.py +78 -0
  11. wxz_cli-1.0.0/src/wxz_cli/acp/session_manager.py +89 -0
  12. wxz_cli-1.0.0/src/wxz_cli/cli.py +87 -0
  13. wxz_cli-1.0.0/src/wxz_cli/commands/__init__.py +1 -0
  14. wxz_cli-1.0.0/src/wxz_cli/commands/acp_cmd.py +26 -0
  15. wxz_cli-1.0.0/src/wxz_cli/commands/auth.py +95 -0
  16. wxz_cli-1.0.0/src/wxz_cli/commands/chat.py +715 -0
  17. wxz_cli-1.0.0/src/wxz_cli/commands/code.py +106 -0
  18. wxz_cli-1.0.0/src/wxz_cli/commands/deploy.py +159 -0
  19. wxz_cli-1.0.0/src/wxz_cli/commands/domain.py +359 -0
  20. wxz_cli-1.0.0/src/wxz_cli/commands/preview.py +66 -0
  21. wxz_cli-1.0.0/src/wxz_cli/commands/projects.py +105 -0
  22. wxz_cli-1.0.0/src/wxz_cli/commands/sandbox.py +70 -0
  23. wxz_cli-1.0.0/src/wxz_cli/core/__init__.py +1 -0
  24. wxz_cli-1.0.0/src/wxz_cli/core/chat_renderer.py +781 -0
  25. wxz_cli-1.0.0/src/wxz_cli/core/config.py +91 -0
  26. wxz_cli-1.0.0/src/wxz_cli/core/output.py +68 -0
  27. wxz_cli-1.0.0/src/wxz_cli/core/pop_client.py +613 -0
  28. wxz_cli-1.0.0/src/wxz_cli/core/session.py +154 -0
  29. wxz_cli-1.0.0/src/wxz_cli/core/ui.py +224 -0
  30. wxz_cli-1.0.0/src/wxz_cli/types.py +33 -0
  31. wxz_cli-1.0.0/src/wxz_cli.egg-info/PKG-INFO +93 -0
  32. wxz_cli-1.0.0/src/wxz_cli.egg-info/SOURCES.txt +38 -0
  33. wxz_cli-1.0.0/src/wxz_cli.egg-info/dependency_links.txt +1 -0
  34. wxz_cli-1.0.0/src/wxz_cli.egg-info/entry_points.txt +2 -0
  35. wxz_cli-1.0.0/src/wxz_cli.egg-info/requires.txt +6 -0
  36. wxz_cli-1.0.0/src/wxz_cli.egg-info/top_level.txt +1 -0
  37. wxz_cli-1.0.0/tests/test_chat.py +1069 -0
  38. wxz_cli-1.0.0/tests/test_chat_integration.py +112 -0
  39. wxz_cli-1.0.0/tests/test_domain.py +327 -0
  40. wxz_cli-1.0.0/tests/test_projects.py +152 -0
wxz_cli-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 MSEA AI Staff Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
wxz_cli-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: wxz-cli
3
+ Version: 1.0.0
4
+ Summary: CLI for 万小智 (wxz) — AI-powered conversational website builder on Alibaba Cloud
5
+ Author: MSEA AI Staff Team
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/aliyun/wxz-cli
8
+ Project-URL: Repository, https://github.com/aliyun/wxz-cli.git
9
+ Project-URL: Issues, https://github.com/aliyun/wxz-cli/issues
10
+ Keywords: wxz,website-builder,alibaba-cloud,cli,ai
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: click>=8.0
24
+ Requires-Dist: rich>=13.0
25
+ Requires-Dist: httpx>=0.24.0
26
+ Requires-Dist: alibabacloud_tea_openapi>=0.3.0
27
+ Requires-Dist: alibabacloud_tea_util>=0.3.0
28
+ Requires-Dist: alibabacloud_credentials>=0.3.0
29
+ Dynamic: license-file
30
+
31
+ # wxz-cli
32
+
33
+ CLI for 万小智 (wxz) — AI-powered conversational website builder on Alibaba Cloud.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install wxz-cli
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```bash
44
+ # Login with Alibaba Cloud AK/SK
45
+ wxz login --ak <AccessKeyID> --sk <AccessKeySecret>
46
+
47
+ # Create a website via natural language
48
+ wxz chat start "帮我做一个科技公司官网"
49
+
50
+ # View project info
51
+ wxz projects info
52
+
53
+ # Deploy
54
+ wxz deploy deploy --watch
55
+ ```
56
+
57
+ ## Commands
58
+
59
+ | Command | Description |
60
+ |---------|-------------|
61
+ | `wxz login` | Authenticate with AK/SK |
62
+ | `wxz logout` | Clear local credentials |
63
+ | `wxz whoami` | Show current user info |
64
+ | `wxz chat start` | Create conversation and generate website |
65
+ | `wxz chat send` | Send follow-up message |
66
+ | `wxz chat generate` | Trigger code generation |
67
+ | `wxz chat status` | Show chat status |
68
+ | `wxz chat history` | Show chat history |
69
+ | `wxz projects list` | List all instances |
70
+ | `wxz projects use` | Bind project to directory |
71
+ | `wxz projects info` | Show project details |
72
+ | `wxz deploy deploy` | Publish website |
73
+ | `wxz deploy status` | Get publish status |
74
+ | `wxz domain bind` | Bind custom domain |
75
+ | `wxz domain dns` | Show DNS records |
76
+ | `wxz domain cert` | Set/delete SSL certificate |
77
+ | `wxz code tree` | Show sandbox directory tree |
78
+ | `wxz code cat` | Show file content |
79
+ | `wxz code rollback` | Rollback code snapshot |
80
+
81
+ ## Configuration
82
+
83
+ ### Environment Variables
84
+
85
+ | Variable | Description |
86
+ |----------|-------------|
87
+ | `WXZ_BIZ_ID` | Business/instance ID |
88
+ | `WXZ_CONVERSATION_ID` | Conversation ID |
89
+ | `WXZ_BASE_URL` | POP gateway base URL |
90
+
91
+ ## License
92
+
93
+ MIT
@@ -0,0 +1,63 @@
1
+ # wxz-cli
2
+
3
+ CLI for 万小智 (wxz) — AI-powered conversational website builder on Alibaba Cloud.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install wxz-cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Login with Alibaba Cloud AK/SK
15
+ wxz login --ak <AccessKeyID> --sk <AccessKeySecret>
16
+
17
+ # Create a website via natural language
18
+ wxz chat start "帮我做一个科技公司官网"
19
+
20
+ # View project info
21
+ wxz projects info
22
+
23
+ # Deploy
24
+ wxz deploy deploy --watch
25
+ ```
26
+
27
+ ## Commands
28
+
29
+ | Command | Description |
30
+ |---------|-------------|
31
+ | `wxz login` | Authenticate with AK/SK |
32
+ | `wxz logout` | Clear local credentials |
33
+ | `wxz whoami` | Show current user info |
34
+ | `wxz chat start` | Create conversation and generate website |
35
+ | `wxz chat send` | Send follow-up message |
36
+ | `wxz chat generate` | Trigger code generation |
37
+ | `wxz chat status` | Show chat status |
38
+ | `wxz chat history` | Show chat history |
39
+ | `wxz projects list` | List all instances |
40
+ | `wxz projects use` | Bind project to directory |
41
+ | `wxz projects info` | Show project details |
42
+ | `wxz deploy deploy` | Publish website |
43
+ | `wxz deploy status` | Get publish status |
44
+ | `wxz domain bind` | Bind custom domain |
45
+ | `wxz domain dns` | Show DNS records |
46
+ | `wxz domain cert` | Set/delete SSL certificate |
47
+ | `wxz code tree` | Show sandbox directory tree |
48
+ | `wxz code cat` | Show file content |
49
+ | `wxz code rollback` | Rollback code snapshot |
50
+
51
+ ## Configuration
52
+
53
+ ### Environment Variables
54
+
55
+ | Variable | Description |
56
+ |----------|-------------|
57
+ | `WXZ_BIZ_ID` | Business/instance ID |
58
+ | `WXZ_CONVERSATION_ID` | Conversation ID |
59
+ | `WXZ_BASE_URL` | POP gateway base URL |
60
+
61
+ ## License
62
+
63
+ MIT
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "wxz-cli"
7
+ version = "1.0.0"
8
+ description = "CLI for 万小智 (wxz) — AI-powered conversational website builder on Alibaba Cloud"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ {name = "MSEA AI Staff Team"}
14
+ ]
15
+ keywords = ["wxz", "website-builder", "alibaba-cloud", "cli", "ai"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Software Development :: Build Tools",
26
+ ]
27
+ dependencies = [
28
+ "click>=8.0",
29
+ "rich>=13.0",
30
+ "httpx>=0.24.0",
31
+ "alibabacloud_tea_openapi>=0.3.0",
32
+ "alibabacloud_tea_util>=0.3.0",
33
+ "alibabacloud_credentials>=0.3.0",
34
+ ]
35
+
36
+ [project.scripts]
37
+ wxz = "wxz_cli.cli:cli"
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/aliyun/wxz-cli"
41
+ Repository = "https://github.com/aliyun/wxz-cli.git"
42
+ Issues = "https://github.com/aliyun/wxz-cli/issues"
43
+
44
+ [tool.setuptools.packages.find]
45
+ where = ["src"]
46
+
47
+ [tool.setuptools.package-dir]
48
+ "" = "src"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ """wxz-cli — CLI for 万小智 AI 建站平台.
2
+
3
+ A command-line interface for the wxz (Wan Xiao Zhi) conversational AI
4
+ website builder on Alibaba Cloud.
5
+ """
6
+
7
+ __version__ = "1.0.0"
@@ -0,0 +1 @@
1
+ """ACP (Agent Client Protocol) support for wxz-cli."""
@@ -0,0 +1,112 @@
1
+ """Chat event to ACP event bridge.
2
+
3
+ Consumes wxz SSE chat events (from CreateAppChat) and converts them
4
+ into ACP session/update notifications.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import threading
11
+ from typing import Generator
12
+
13
+ from wxz_cli.acp.server import ACPServer
14
+ from wxz_cli.core.pop_client import PopClient
15
+
16
+
17
+ class SSEtoACPBridge:
18
+ """Consumes wxz SSE chat events and forwards them as ACP notifications."""
19
+
20
+ def __init__(self, server: ACPServer, pop_client: PopClient, session_id: str):
21
+ self.server = server
22
+ self.pop_client = pop_client
23
+ self.session_id = session_id
24
+ self.last_event_id = 0
25
+
26
+ def start_stream(
27
+ self,
28
+ sse_stream: Generator[dict, None, None],
29
+ ):
30
+ """Start consuming SSE events in a background thread."""
31
+ self._thread = threading.Thread(
32
+ target=self._consume_stream,
33
+ args=(sse_stream,),
34
+ daemon=True,
35
+ )
36
+ self._thread.start()
37
+
38
+ def _consume_stream(self, sse_stream: Generator[dict, None, None]):
39
+ """Consume SSE generator and emit ACP updates."""
40
+ for event in sse_stream:
41
+ self._emit_acp_update(event)
42
+ eid = event.get("id")
43
+ if eid is not None:
44
+ try:
45
+ self.last_event_id = int(eid)
46
+ except (ValueError, TypeError):
47
+ self.last_event_id = eid
48
+
49
+ def _emit_acp_update(self, event: dict):
50
+ """Convert a single SSE event to an ACP session/update notification."""
51
+ name = event.get("name", "")
52
+ data = event.get("data", {})
53
+ event_id = event.get("id", 0)
54
+
55
+ match name:
56
+ case "message.delta":
57
+ if isinstance(data, dict):
58
+ content = data.get("content", "")
59
+ role = data.get("role", "")
60
+ ctype = data.get("contentType", "")
61
+ if content and role == "assistant" and ctype == "text":
62
+ self.server.send_notification("session/update", {
63
+ "sessionId": self.session_id,
64
+ "sessionUpdate": "agent_message_chunk",
65
+ "text": content,
66
+ })
67
+
68
+ case "message.tool":
69
+ if isinstance(data, dict):
70
+ meta = data.get("metaData", {})
71
+ tool_name = meta.get("name", "") if isinstance(meta, dict) else ""
72
+ content = data.get("content", "")
73
+ self.server.send_notification("session/update", {
74
+ "sessionId": self.session_id,
75
+ "sessionUpdate": "tool_call",
76
+ "toolCallId": f"call_{event_id}",
77
+ "title": f"{tool_name}: {content}" if tool_name else content,
78
+ "kind": "edit",
79
+ "status": "completed",
80
+ })
81
+
82
+ case "message.interrupt":
83
+ self.server.send_notification("session/update", {
84
+ "sessionId": self.session_id,
85
+ "sessionUpdate": "agent_message_chunk",
86
+ "text": "[等待输入] AI 需要更多信息",
87
+ })
88
+
89
+ case "chat.completed":
90
+ self.server.send_notification("session/update", {
91
+ "sessionId": self.session_id,
92
+ "sessionUpdate": "plan",
93
+ "entries": [
94
+ {"content": "对话完成", "priority": "high", "status": "completed"},
95
+ ],
96
+ })
97
+
98
+ case "message.error" | "error":
99
+ if isinstance(data, dict):
100
+ content = data.get("content", "")
101
+ try:
102
+ err = json.loads(content) if isinstance(content, str) else content
103
+ msg = err.get("errorMsg", content) if isinstance(err, dict) else content
104
+ except (json.JSONDecodeError, ValueError):
105
+ msg = data.get("errorMsg", str(data))
106
+ else:
107
+ msg = str(data)
108
+ self.server.send_notification("session/update", {
109
+ "sessionId": self.session_id,
110
+ "sessionUpdate": "agent_message_chunk",
111
+ "text": f"[错误] {msg}",
112
+ })
@@ -0,0 +1,113 @@
1
+ """ACP method handlers for wxz-cli.
2
+
3
+ Registers JSON-RPC methods for the Agent Client Protocol.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+
10
+ from wxz_cli.acp.server import ACPServer
11
+ from wxz_cli.acp.session_manager import ACPSessionManager
12
+
13
+
14
+ class ACPHandlers:
15
+ """Registers ACP JSON-RPC method handlers."""
16
+
17
+ def __init__(self, server: ACPServer, session_mgr: ACPSessionManager):
18
+ self.server = server
19
+ self.session_mgr = session_mgr
20
+ self._register_all()
21
+
22
+ def _register_all(self):
23
+ @self.server.method("initialize")
24
+ def handle_initialize(params):
25
+ return {
26
+ "protocolVersion": min(params.get("protocolVersion", 1), 1),
27
+ "agentCapabilities": {
28
+ "loadSession": True,
29
+ "promptCapabilities": {"image": True, "embeddedContext": True},
30
+ "auth": {"logout": {}},
31
+ "sessionCapabilities": {"list": True, "resume": True, "close": True},
32
+ },
33
+ "agentInfo": {
34
+ "name": "wxz",
35
+ "title": "万小智 AI 建站",
36
+ "version": "1.0.0",
37
+ },
38
+ "authMethods": [
39
+ {
40
+ "id": "aliyun-ak",
41
+ "name": "阿里云 AK/SK",
42
+ "description": "使用阿里云 AccessKey 认证",
43
+ }
44
+ ],
45
+ }
46
+
47
+ @self.server.method("authenticate")
48
+ def handle_authenticate(params):
49
+ ok = self.session_mgr.authenticate()
50
+ if not ok:
51
+ raise RuntimeError("Authentication required: set ALIBABACLOUD_ACCESS_KEY_ID and ALIBABACLOUD_ACCESS_KEY_SECRET, or run 'wxz login'")
52
+ return {}
53
+
54
+ @self.server.method("session/new")
55
+ def handle_session_new(params):
56
+ session_id = self.session_mgr.create_session(params.get("cwd", ""))
57
+ return {
58
+ "sessionId": session_id,
59
+ "configOptions": [
60
+ {
61
+ "id": "mode",
62
+ "name": "建站模式",
63
+ "description": "控制 AI 的行为方式",
64
+ "category": "mode",
65
+ "type": "select",
66
+ "currentValue": "build",
67
+ "options": [
68
+ {"value": "build", "name": "建站模式", "description": "AI 生成完整网站代码并部署"},
69
+ {"value": "ask", "name": "咨询模式", "description": "只回答问题,不修改代码"},
70
+ ],
71
+ }
72
+ ],
73
+ }
74
+
75
+ @self.server.method("session/load")
76
+ def handle_session_load(params):
77
+ self.session_mgr.load_session(params["sessionId"], params.get("cwd", ""))
78
+ return {}
79
+
80
+ @self.server.method("session/list")
81
+ def handle_session_list(params):
82
+ # Return empty list for now; can be extended to query real instances
83
+ return {"sessions": []}
84
+
85
+ @self.server.method("session/prompt")
86
+ def handle_prompt(params):
87
+ session_id = params["sessionId"]
88
+ prompt_text = self._extract_text(params.get("prompt", []))
89
+ self.session_mgr.handle_prompt(session_id, prompt_text)
90
+ # In a full implementation, this would start polling
91
+ # ListAIStaffChatEvents and emit session/update notifications.
92
+ return {"stopReason": "end_turn"}
93
+
94
+ @self.server.method("session/cancel")
95
+ def handle_cancel(params):
96
+ self.session_mgr.cancel_session(params["sessionId"])
97
+ return {}
98
+
99
+ @self.server.method("logout")
100
+ def handle_logout(params):
101
+ self.session_mgr.logout()
102
+ return {}
103
+
104
+ def _extract_text(self, prompt_blocks: list) -> str:
105
+ """Extract text from ACP content blocks."""
106
+ parts = []
107
+ for block in prompt_blocks:
108
+ if block.get("type") == "text":
109
+ parts.append(block.get("text", ""))
110
+ elif block.get("type") == "resource":
111
+ resource = block.get("resource", {})
112
+ parts.append(f"[参考文件: {resource.get('uri', '')}]")
113
+ return "\n".join(parts)
@@ -0,0 +1,78 @@
1
+ """JSON-RPC 2.0 server over stdio for ACP mode."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ import logging
8
+ from typing import Callable
9
+
10
+ logger = logging.getLogger("wxz.acp")
11
+
12
+
13
+ class ACPServer:
14
+ """JSON-RPC 2.0 server based on stdio transport."""
15
+
16
+ def __init__(self):
17
+ self._methods: dict[str, Callable] = {}
18
+ self._initialized = False
19
+
20
+ def method(self, name: str):
21
+ """Decorator to register a JSON-RPC method handler."""
22
+ def decorator(func):
23
+ self._methods[name] = func
24
+ return func
25
+ return decorator
26
+
27
+ def run(self):
28
+ """Main loop: read JSON-RPC messages from stdin and dispatch."""
29
+ for line in sys.stdin:
30
+ line = line.strip()
31
+ if not line:
32
+ continue
33
+ try:
34
+ msg = json.loads(line)
35
+ except json.JSONDecodeError:
36
+ self._send_error(None, -32700, "Parse error")
37
+ continue
38
+
39
+ method_name = msg.get("method")
40
+ msg_id = msg.get("id") # None for notifications
41
+ params = msg.get("params", {})
42
+
43
+ if method_name == "initialize":
44
+ self._initialized = True
45
+
46
+ if not self._initialized and method_name != "initialize":
47
+ if msg_id is not None:
48
+ self._send_error(msg_id, -32002, "Server not initialized")
49
+ continue
50
+
51
+ if method_name in self._methods:
52
+ try:
53
+ result = self._methods[method_name](params)
54
+ if msg_id is not None:
55
+ self._send_result(msg_id, result)
56
+ except Exception as e:
57
+ logger.exception("Error handling %s", method_name)
58
+ if msg_id is not None:
59
+ self._send_error(msg_id, -32603, str(e))
60
+ else:
61
+ if msg_id is not None:
62
+ self._send_error(msg_id, -32601, f"Method not found: {method_name}")
63
+
64
+ def send_notification(self, method: str, params: dict):
65
+ """Send a JSON-RPC notification (no id, no response needed)."""
66
+ msg = {"jsonrpc": "2.0", "method": method, "params": params}
67
+ sys.stdout.write(json.dumps(msg, ensure_ascii=False) + "\n")
68
+ sys.stdout.flush()
69
+
70
+ def _send_result(self, msg_id, result):
71
+ msg = {"jsonrpc": "2.0", "id": msg_id, "result": result}
72
+ sys.stdout.write(json.dumps(msg, ensure_ascii=False) + "\n")
73
+ sys.stdout.flush()
74
+
75
+ def _send_error(self, msg_id, code, message):
76
+ msg = {"jsonrpc": "2.0", "id": msg_id, "error": {"code": code, "message": message}}
77
+ sys.stdout.write(json.dumps(msg, ensure_ascii=False) + "\n")
78
+ sys.stdout.flush()
@@ -0,0 +1,89 @@
1
+ """ACP session manager for wxz-cli.
2
+
3
+ Manages ACP sessions mapped to wxz conversations.
4
+ Uses PopClient with alibabacloud SDK for API calls.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from typing import Any, Optional
11
+
12
+ from wxz_cli.core.session import Session
13
+ from wxz_cli.core.pop_client import PopClient
14
+
15
+
16
+ class ACPSessionManager:
17
+ """Manages ACP sessions and their mapping to wxz conversations."""
18
+
19
+ def __init__(self, session: Session):
20
+ self.session = session
21
+ self._pop_client: Optional[PopClient] = None
22
+ self._sessions: dict[str, dict[str, Any]] = {}
23
+
24
+ def _get_client(self) -> PopClient:
25
+ if self._pop_client is None:
26
+ self._pop_client = PopClient(
27
+ access_key_id=self.session.access_key_id or None,
28
+ access_key_secret=self.session.access_key_secret or None,
29
+ security_token=self.session.security_token or None,
30
+ region=self.session.region_id,
31
+ )
32
+ return self._pop_client
33
+
34
+ def authenticate(self) -> bool:
35
+ """Authenticate using session credentials or env vars."""
36
+ # Check if we have credentials from session or environment
37
+ ak = self.session.access_key_id
38
+ sk = self.session.access_key_secret
39
+ if not ak or not sk:
40
+ # The PopClient will use the default credential chain
41
+ # (env vars, shared config, ECS role) if no explicit credentials
42
+ pass
43
+ return True # PopClient handles credential resolution internally
44
+
45
+ def create_session(self, cwd: str) -> str:
46
+ """Create a new wxz conversation and return session ID."""
47
+ client = self._get_client()
48
+ resp = client.create_ai_staff_conversation(text=f"New project in {cwd}")
49
+ module = resp.get("Module", resp)
50
+ session_id = module.get("ConversationId", "")
51
+ self._sessions[session_id] = {
52
+ "cwd": cwd,
53
+ "conversation_id": session_id,
54
+ "biz_id": module.get("SiteId"),
55
+ "chat_id": module.get("ChatId"),
56
+ "bot_id": module.get("BotId", "Zero2"),
57
+ }
58
+ return session_id
59
+
60
+ def load_session(self, session_id: str, cwd: str) -> None:
61
+ """Load an existing session."""
62
+ self._sessions[session_id] = {
63
+ "cwd": cwd,
64
+ "conversation_id": session_id,
65
+ }
66
+
67
+ def get_session(self, session_id: str) -> Optional[dict[str, Any]]:
68
+ return self._sessions.get(session_id)
69
+
70
+ def handle_prompt(self, session_id: str, prompt_text: str):
71
+ """Handle a user prompt in an ACP session."""
72
+ sess = self.get_session(session_id)
73
+ if not sess:
74
+ raise RuntimeError(f"Session not found: {session_id}")
75
+ # Actual prompt handling is done by the caller (event bridge + poll)
76
+ return sess
77
+
78
+ def cancel_session(self, session_id: str) -> None:
79
+ self._sessions.pop(session_id, None)
80
+
81
+ def logout(self) -> None:
82
+ self.session.access_key_id = ""
83
+ self.session.access_key_secret = ""
84
+ self.session.save()
85
+
86
+ def close(self) -> None:
87
+ if self._pop_client:
88
+ self._pop_client.close()
89
+ self._pop_client = None