esclak 0.0.5__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. esclak-0.0.5/.gitignore +44 -0
  2. esclak-0.0.5/PKG-INFO +171 -0
  3. esclak-0.0.5/README.md +162 -0
  4. esclak-0.0.5/pyproject.toml +34 -0
  5. esclak-0.0.5/src/clak/__init__.py +3 -0
  6. esclak-0.0.5/src/clak/__main__.py +4 -0
  7. esclak-0.0.5/src/clak/cli.py +60 -0
  8. esclak-0.0.5/src/clak/commands/__init__.py +1 -0
  9. esclak-0.0.5/src/clak/commands/_registry.py +40 -0
  10. esclak-0.0.5/src/clak/commands/clear.py +21 -0
  11. esclak-0.0.5/src/clak/commands/quit.py +17 -0
  12. esclak-0.0.5/src/clak/commands/status.py +21 -0
  13. esclak-0.0.5/src/clak/commands/types.py +71 -0
  14. esclak-0.0.5/src/clak/commands/version.py +22 -0
  15. esclak-0.0.5/src/clak/runtime/__init__.py +0 -0
  16. esclak-0.0.5/src/clak/runtime/ask/__init__.py +12 -0
  17. esclak-0.0.5/src/clak/runtime/ask/answer.py +34 -0
  18. esclak-0.0.5/src/clak/runtime/ask/request.py +25 -0
  19. esclak-0.0.5/src/clak/runtime/headless.py +101 -0
  20. esclak-0.0.5/src/clak/runtime/params.py +48 -0
  21. esclak-0.0.5/src/clak/runtime/session.py +113 -0
  22. esclak-0.0.5/src/clak/runtime/slash.py +72 -0
  23. esclak-0.0.5/src/clak/runtime/step_list.py +146 -0
  24. esclak-0.0.5/src/clak/scripts/__init__.py +5 -0
  25. esclak-0.0.5/src/clak/scripts/_registry.py +63 -0
  26. esclak-0.0.5/src/clak/scripts/pack_game_res.py +103 -0
  27. esclak-0.0.5/src/clak/scripts/select_server.py +75 -0
  28. esclak-0.0.5/src/clak/tui/__init__.py +0 -0
  29. esclak-0.0.5/src/clak/tui/app.py +155 -0
  30. esclak-0.0.5/src/clak/tui/event.py +28 -0
  31. esclak-0.0.5/src/clak/tui/screen.py +130 -0
  32. esclak-0.0.5/src/clak/tui/state.py +425 -0
  33. esclak-0.0.5/src/clak/tui/theme.py +10 -0
  34. esclak-0.0.5/src/clak/tui/views/__init__.py +0 -0
  35. esclak-0.0.5/src/clak/tui/views/ask.py +324 -0
  36. esclak-0.0.5/src/clak/tui/views/ask_controller.py +193 -0
  37. esclak-0.0.5/src/clak/tui/views/command_menu.py +58 -0
  38. esclak-0.0.5/src/clak/tui/views/context.py +22 -0
  39. esclak-0.0.5/src/clak/tui/views/history.py +108 -0
  40. esclak-0.0.5/src/clak/tui/views/input.py +233 -0
@@ -0,0 +1,44 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .eggs/
11
+ *.egg
12
+ pip-wheel-metadata/
13
+
14
+ # 虚拟环境
15
+ .venv/
16
+ venv/
17
+ ENV/
18
+
19
+ # 测试与类型检查
20
+ .pytest_cache/
21
+ .mypy_cache/
22
+ .ruff_cache/
23
+ .coverage
24
+ htmlcov/
25
+ .tox/
26
+ .nox/
27
+
28
+ # 环境与密钥
29
+ .env
30
+ .env.*
31
+ !.env.example
32
+
33
+ # IDE / 编辑器
34
+ .idea/
35
+ .vscode/
36
+ *.swp
37
+ *.swo
38
+ *~
39
+ .DS_Store
40
+
41
+ # pyinstaller 构建产物(build.sh 产出上级目录的 clak 二进制)
42
+ /clak
43
+ /clak.spec
44
+
esclak-0.0.5/PKG-INFO ADDED
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: esclak
3
+ Version: 0.0.5
4
+ Summary: 固定流程脚本 TUI 与无头执行
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: textual>=8.0.0
7
+ Requires-Dist: wcwidth>=0.2.13
8
+ Description-Content-Type: text/markdown
9
+
10
+ # clak
11
+
12
+ 固定流程脚本 TUI 与无头执行(`StepList` + `ScriptSession`)。
13
+
14
+ CLAK = **C**ommand-**L**ine **A**utomation **K**it。
15
+
16
+ ## 能力
17
+
18
+ - history / slash 菜单 / 多行输入 / context / ask(单选、多选、过滤)
19
+ - `/` 补全
20
+ - 脚本:`/pack-game-res`(版本 + 打包目标)、`/select-server`(目标服务器)
21
+ - 无头:`clak exec /pack-game-res --use-defaults` 或 `--set field=value`
22
+
23
+ ## 目录结构
24
+
25
+ ```
26
+ src/clak/
27
+ ├── cli.py # argparse 入口
28
+ ├── runtime/ # 脚本运行时(与 UI 解耦)
29
+ │ ├── step_list.py # 步骤执行器
30
+ │ ├── session.py # 会话 + ask 网关
31
+ │ ├── params.py # 无头 --set 参数
32
+ │ ├── headless.py # 无头执行入口
33
+ │ ├── slash.py # SlashEngine 分发
34
+ │ └── ask/ # ask 协议(request/answer)
35
+ ├── commands/ # 纯 slash 命令
36
+ │ ├── types.py # SlashCommand / ScriptEntry / SlashEntry
37
+ │ ├── _registry.py # 聚合 builtin + 脚本
38
+ │ └── version/status/clear/quit.py
39
+ ├── scripts/ # 业务脚本(自动发现)
40
+ │ ├── _registry.py # @script 装饰器 + 扫描注册
41
+ │ └── pack_game_res/select_server.py
42
+ └── tui/ # 渲染层
43
+ ├── app.py / state.py / event.py / theme.py / screen.py
44
+ └── views/ # 渲染单元平铺
45
+ ```
46
+
47
+ 依赖链单向:`tui → runtime/slash → commands/_registry → scripts/_registry → runtime`(数据结构)。
48
+
49
+ ## 安装与运行
50
+
51
+ 需要 Python ≥ 3.10。依赖 `textual` + `wcwidth`。
52
+
53
+ ### 用 uv
54
+
55
+ ```bash
56
+ uv sync
57
+ uv run clak # 打开 TUI
58
+ uv run clak exec /version # 无头执行
59
+ uv run clak exec /pack-game-res --set version=1.0 --set targets=base,cn
60
+ ```
61
+
62
+ ### 不用 uv(pip + python)
63
+
64
+ ```bash
65
+ pip install -e . # 装依赖 + 入口命令
66
+ python -m clak # 打开 TUI
67
+ python -m clak exec /version
68
+ python -m clak exec /pack-game-res --use-defaults
69
+ ```
70
+
71
+ 若依赖已装(`pip install textual wcwidth`),可直接跑无需安装:
72
+
73
+ ```bash
74
+ python3 -m clak # 直接运行
75
+ python3 -m clak exec /version
76
+ python3 -m pytest # 跑测试
77
+ ```
78
+
79
+ 入口命令:`clak`(pip install 后可用)。PyPI 包名为 `esclak`(绕开 `clack` 相似性),import 名为 `clak`(`from clak... import`)。
80
+
81
+ ```bash
82
+ pip install esclak # 装包,得到 clak 命令
83
+ ```
84
+
85
+ ## 构建单文件二进制(组内分发)
86
+
87
+ 无需 Python 环境即可分发,用 pyinstaller 打成单文件:
88
+
89
+ ```bash
90
+ ./build.sh # 产出上级目录的 clak 二进制
91
+ ```
92
+
93
+ 部署到游戏代码仓库时,目录约定:
94
+
95
+ ```
96
+ tools/
97
+ ├── clak_src/ # clak 源码(入库)
98
+ │ ├── build.sh
99
+ │ └── src/...
100
+ └── clak # 构建产物(gitignore,不入库)
101
+ ```
102
+
103
+ 组员拉代码后,在 `tools/clak_src/` 执行 `./build.sh` 即生成 `tools/clak`,直接 `./tools/clak` 使用。
104
+
105
+ > 跨平台需分别构建:macOS 上构建得到 macOS 二进制,Linux 同理。onefile 模式首次启动会解压到临时目录,约慢几百毫秒。
106
+
107
+ ## 无头执行
108
+
109
+ ```bash
110
+ clak exec /<script> [--set field=value ...] [--use-defaults]
111
+ ```
112
+
113
+ - `--set version=1.0`:传入脚本参数(可重复)
114
+ - `--use-defaults`:未 `--set` 的 ask 字段用 `AskRequest.default`,无交互批量执行
115
+ - 缺参且未开 `--use-defaults` 时报错退出码 2
116
+
117
+ ## 扩展脚本
118
+
119
+ 在 `src/clak/scripts/` 新建模块,用 `@script` 装饰器注册,启动时自动扫描,**无需改框架任何文件**:
120
+
121
+ ```python
122
+ # scripts/my_script.py
123
+ from clak.runtime.ask import AskChoice, AskMode, AskRequest
124
+ from clak.runtime.session import ScriptSession
125
+ from clak.scripts import script
126
+
127
+
128
+ @script("/my-script", "我的脚本说明")
129
+ def run(session: ScriptSession) -> None:
130
+ state = {"name": ""}
131
+
132
+ def step_ask_name() -> int:
133
+ def on_answer(answer) -> None:
134
+ state["name"] = answer.primary
135
+ session.log(f"已选: {state['name']}")
136
+
137
+ return session.ask_step(
138
+ AskRequest(
139
+ field_id="name",
140
+ prompt="选择名称",
141
+ default="default",
142
+ choices=(AskChoice(label="默认", value="default"),),
143
+ ),
144
+ on_answer,
145
+ )
146
+
147
+ def step_done() -> int:
148
+ session.log(f"my-script: 完成 ({state['name']})")
149
+ return 0
150
+
151
+ step_list = session.step_list
152
+ session.bind_step_end()
153
+ step_list.clear()
154
+ step_list.add(True, step_ask_name, "ask_name")
155
+ step_list.add(True, step_done, "done")
156
+ session.run()
157
+ ```
158
+
159
+ 加完即可用:`clak exec /my-script --set name=default`。
160
+
161
+ ## 扩展 slash 命令(非脚本)
162
+
163
+ 在 `src/clak/commands/` 新建模块,定义 `COMMAND = SlashCommand(...)`,加入 `commands/_registry.py` 的 `_BUILTINS`。
164
+
165
+ ## 测试
166
+
167
+ ```bash
168
+ python -m pytest # 或 uv run pytest
169
+ ```
170
+
171
+ 测试覆盖:reducer、headless、pack_game_res、select_server、ask(view/multi/navigation)、view_lifecycle、tui_script、exec_list、key_from_textual。
esclak-0.0.5/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # clak
2
+
3
+ 固定流程脚本 TUI 与无头执行(`StepList` + `ScriptSession`)。
4
+
5
+ CLAK = **C**ommand-**L**ine **A**utomation **K**it。
6
+
7
+ ## 能力
8
+
9
+ - history / slash 菜单 / 多行输入 / context / ask(单选、多选、过滤)
10
+ - `/` 补全
11
+ - 脚本:`/pack-game-res`(版本 + 打包目标)、`/select-server`(目标服务器)
12
+ - 无头:`clak exec /pack-game-res --use-defaults` 或 `--set field=value`
13
+
14
+ ## 目录结构
15
+
16
+ ```
17
+ src/clak/
18
+ ├── cli.py # argparse 入口
19
+ ├── runtime/ # 脚本运行时(与 UI 解耦)
20
+ │ ├── step_list.py # 步骤执行器
21
+ │ ├── session.py # 会话 + ask 网关
22
+ │ ├── params.py # 无头 --set 参数
23
+ │ ├── headless.py # 无头执行入口
24
+ │ ├── slash.py # SlashEngine 分发
25
+ │ └── ask/ # ask 协议(request/answer)
26
+ ├── commands/ # 纯 slash 命令
27
+ │ ├── types.py # SlashCommand / ScriptEntry / SlashEntry
28
+ │ ├── _registry.py # 聚合 builtin + 脚本
29
+ │ └── version/status/clear/quit.py
30
+ ├── scripts/ # 业务脚本(自动发现)
31
+ │ ├── _registry.py # @script 装饰器 + 扫描注册
32
+ │ └── pack_game_res/select_server.py
33
+ └── tui/ # 渲染层
34
+ ├── app.py / state.py / event.py / theme.py / screen.py
35
+ └── views/ # 渲染单元平铺
36
+ ```
37
+
38
+ 依赖链单向:`tui → runtime/slash → commands/_registry → scripts/_registry → runtime`(数据结构)。
39
+
40
+ ## 安装与运行
41
+
42
+ 需要 Python ≥ 3.10。依赖 `textual` + `wcwidth`。
43
+
44
+ ### 用 uv
45
+
46
+ ```bash
47
+ uv sync
48
+ uv run clak # 打开 TUI
49
+ uv run clak exec /version # 无头执行
50
+ uv run clak exec /pack-game-res --set version=1.0 --set targets=base,cn
51
+ ```
52
+
53
+ ### 不用 uv(pip + python)
54
+
55
+ ```bash
56
+ pip install -e . # 装依赖 + 入口命令
57
+ python -m clak # 打开 TUI
58
+ python -m clak exec /version
59
+ python -m clak exec /pack-game-res --use-defaults
60
+ ```
61
+
62
+ 若依赖已装(`pip install textual wcwidth`),可直接跑无需安装:
63
+
64
+ ```bash
65
+ python3 -m clak # 直接运行
66
+ python3 -m clak exec /version
67
+ python3 -m pytest # 跑测试
68
+ ```
69
+
70
+ 入口命令:`clak`(pip install 后可用)。PyPI 包名为 `esclak`(绕开 `clack` 相似性),import 名为 `clak`(`from clak... import`)。
71
+
72
+ ```bash
73
+ pip install esclak # 装包,得到 clak 命令
74
+ ```
75
+
76
+ ## 构建单文件二进制(组内分发)
77
+
78
+ 无需 Python 环境即可分发,用 pyinstaller 打成单文件:
79
+
80
+ ```bash
81
+ ./build.sh # 产出上级目录的 clak 二进制
82
+ ```
83
+
84
+ 部署到游戏代码仓库时,目录约定:
85
+
86
+ ```
87
+ tools/
88
+ ├── clak_src/ # clak 源码(入库)
89
+ │ ├── build.sh
90
+ │ └── src/...
91
+ └── clak # 构建产物(gitignore,不入库)
92
+ ```
93
+
94
+ 组员拉代码后,在 `tools/clak_src/` 执行 `./build.sh` 即生成 `tools/clak`,直接 `./tools/clak` 使用。
95
+
96
+ > 跨平台需分别构建:macOS 上构建得到 macOS 二进制,Linux 同理。onefile 模式首次启动会解压到临时目录,约慢几百毫秒。
97
+
98
+ ## 无头执行
99
+
100
+ ```bash
101
+ clak exec /<script> [--set field=value ...] [--use-defaults]
102
+ ```
103
+
104
+ - `--set version=1.0`:传入脚本参数(可重复)
105
+ - `--use-defaults`:未 `--set` 的 ask 字段用 `AskRequest.default`,无交互批量执行
106
+ - 缺参且未开 `--use-defaults` 时报错退出码 2
107
+
108
+ ## 扩展脚本
109
+
110
+ 在 `src/clak/scripts/` 新建模块,用 `@script` 装饰器注册,启动时自动扫描,**无需改框架任何文件**:
111
+
112
+ ```python
113
+ # scripts/my_script.py
114
+ from clak.runtime.ask import AskChoice, AskMode, AskRequest
115
+ from clak.runtime.session import ScriptSession
116
+ from clak.scripts import script
117
+
118
+
119
+ @script("/my-script", "我的脚本说明")
120
+ def run(session: ScriptSession) -> None:
121
+ state = {"name": ""}
122
+
123
+ def step_ask_name() -> int:
124
+ def on_answer(answer) -> None:
125
+ state["name"] = answer.primary
126
+ session.log(f"已选: {state['name']}")
127
+
128
+ return session.ask_step(
129
+ AskRequest(
130
+ field_id="name",
131
+ prompt="选择名称",
132
+ default="default",
133
+ choices=(AskChoice(label="默认", value="default"),),
134
+ ),
135
+ on_answer,
136
+ )
137
+
138
+ def step_done() -> int:
139
+ session.log(f"my-script: 完成 ({state['name']})")
140
+ return 0
141
+
142
+ step_list = session.step_list
143
+ session.bind_step_end()
144
+ step_list.clear()
145
+ step_list.add(True, step_ask_name, "ask_name")
146
+ step_list.add(True, step_done, "done")
147
+ session.run()
148
+ ```
149
+
150
+ 加完即可用:`clak exec /my-script --set name=default`。
151
+
152
+ ## 扩展 slash 命令(非脚本)
153
+
154
+ 在 `src/clak/commands/` 新建模块,定义 `COMMAND = SlashCommand(...)`,加入 `commands/_registry.py` 的 `_BUILTINS`。
155
+
156
+ ## 测试
157
+
158
+ ```bash
159
+ python -m pytest # 或 uv run pytest
160
+ ```
161
+
162
+ 测试覆盖:reducer、headless、pack_game_res、select_server、ask(view/multi/navigation)、view_lifecycle、tui_script、exec_list、key_from_textual。
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "esclak"
3
+ dynamic = ["version"]
4
+ description = "固定流程脚本 TUI 与无头执行"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "textual>=8.0.0",
9
+ "wcwidth>=0.2.13",
10
+ ]
11
+
12
+ [project.scripts]
13
+ clak = "clak.cli:main"
14
+
15
+ [build-system]
16
+ requires = ["hatchling"]
17
+ build-backend = "hatchling.build"
18
+
19
+ [tool.hatch.version]
20
+ path = "src/clak/__init__.py"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["src/clak"]
24
+
25
+ [tool.hatch.build.targets.sdist]
26
+ include = ["src/clak", "README.md"]
27
+
28
+ [dependency-groups]
29
+ dev = [
30
+ "pytest>=8.0",
31
+ ]
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = ["tests"]
@@ -0,0 +1,3 @@
1
+ """clak:固定流程脚本壳。"""
2
+
3
+ __version__ = "0.0.5"
@@ -0,0 +1,4 @@
1
+ from clak.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ from clak.runtime.headless import HeadlessError, emit, run_line
7
+ from clak.tui.app import run_tui
8
+
9
+
10
+ def build_parser() -> argparse.ArgumentParser:
11
+ parser = argparse.ArgumentParser(prog="clak", description="固定流程脚本 TUI")
12
+ sub = parser.add_subparsers(dest="command")
13
+
14
+ sub.add_parser("tui", help="打开交互 TUI(默认)")
15
+
16
+ exec_parser = sub.add_parser("exec", help="无头执行一条 slash 或 prompt")
17
+ exec_parser.add_argument(
18
+ "line",
19
+ help='要执行的行,例如 "/version" 或 "/pack-game-res"',
20
+ )
21
+ exec_parser.add_argument(
22
+ "--set",
23
+ action="append",
24
+ default=[],
25
+ metavar="FIELD=VALUE",
26
+ help="脚本参数(可重复),如 --set version=1.0 --set targets=base,cn",
27
+ )
28
+ exec_parser.add_argument(
29
+ "--use-defaults",
30
+ action="store_true",
31
+ help="未 --set 的 ask 字段使用 AskRequest.default(无交互批量)",
32
+ )
33
+ return parser
34
+
35
+
36
+ def main(argv: list[str] | None = None) -> None:
37
+ parser = build_parser()
38
+ args = parser.parse_args(argv)
39
+ command = args.command
40
+
41
+ if command == "exec":
42
+ from clak.runtime.params import ScriptInputs
43
+
44
+ try:
45
+ inputs = ScriptInputs.parse_set_flags(
46
+ args.set,
47
+ allow_defaults=args.use_defaults or not args.set,
48
+ )
49
+ run = run_line(args.line, script_inputs=inputs)
50
+ except (HeadlessError, ValueError) as error:
51
+ print(f"clak: {error}", file=sys.stderr)
52
+ code = error.code if isinstance(error, HeadlessError) else 2
53
+ raise SystemExit(code) from error
54
+ raise SystemExit(emit(run))
55
+
56
+ if command in (None, "tui"):
57
+ run_tui()
58
+ return
59
+
60
+ parser.error(f"未知子命令: {command}")
@@ -0,0 +1 @@
1
+ # 各 slash 子命令见同目录模块;聚合在 registry。
@@ -0,0 +1,40 @@
1
+ from clak.commands import (
2
+ clear,
3
+ quit,
4
+ status,
5
+ version,
6
+ )
7
+ from clak.commands.types import ScriptEntry, SlashCommand, SlashEntry
8
+ from clak.runtime.session import ScriptSession
9
+ from clak.scripts._registry import all_scripts
10
+
11
+ _BUILTINS: tuple[SlashCommand, ...] = (
12
+ clear.COMMAND,
13
+ version.COMMAND,
14
+ status.COMMAND,
15
+ quit.COMMAND,
16
+ )
17
+
18
+ COMMANDS: tuple[SlashEntry, ...] = _BUILTINS + all_scripts()
19
+
20
+
21
+ def command_for_token(token: str) -> SlashEntry | None:
22
+ needle = token.lower()
23
+ for cmd in COMMANDS:
24
+ if needle in cmd.all_names():
25
+ return cmd
26
+ return None
27
+
28
+
29
+ def script_command(script_id: str) -> ScriptEntry | None:
30
+ for cmd in all_scripts():
31
+ if cmd.script_id == script_id:
32
+ return cmd
33
+ return None
34
+
35
+
36
+ def run_script(session: ScriptSession, script_id: str) -> None:
37
+ cmd = script_command(script_id)
38
+ if not cmd:
39
+ raise ValueError(f"未知脚本: {script_id}")
40
+ cmd.script_run(session)
@@ -0,0 +1,21 @@
1
+ from clak.commands.types import (
2
+ SlashAction,
3
+ SlashCommand,
4
+ SlashDispatchResult,
5
+ )
6
+
7
+
8
+ def dispatch() -> SlashDispatchResult:
9
+ return SlashDispatchResult(
10
+ action=SlashAction.NEW_SESSION,
11
+ message="已清空 history",
12
+ status="Session cleared",
13
+ )
14
+
15
+
16
+ COMMAND = SlashCommand(
17
+ "/clear",
18
+ ("/new",),
19
+ "清空 history",
20
+ handler=dispatch,
21
+ )
@@ -0,0 +1,17 @@
1
+ from clak.commands.types import (
2
+ SlashAction,
3
+ SlashCommand,
4
+ SlashDispatchResult,
5
+ )
6
+
7
+
8
+ def dispatch() -> SlashDispatchResult:
9
+ return SlashDispatchResult(action=SlashAction.QUIT, status="再见")
10
+
11
+
12
+ COMMAND = SlashCommand(
13
+ "/quit",
14
+ ("/exit",),
15
+ "退出 TUI",
16
+ handler=dispatch,
17
+ )
@@ -0,0 +1,21 @@
1
+ from clak.commands.types import (
2
+ SlashAction,
3
+ SlashCommand,
4
+ SlashDispatchResult,
5
+ )
6
+
7
+
8
+ def dispatch() -> SlashDispatchResult:
9
+ return SlashDispatchResult(
10
+ action=SlashAction.SHOW_STATUS,
11
+ message="clak 脚本聚合",
12
+ status="已显示状态",
13
+ )
14
+
15
+
16
+ COMMAND = SlashCommand(
17
+ "/status",
18
+ ("/context",),
19
+ "软件名与定位",
20
+ handler=dispatch,
21
+ )
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+ from enum import Enum, auto
6
+
7
+ from clak.runtime.session import ScriptSession
8
+
9
+
10
+ class SlashAction(Enum):
11
+ NONE = auto()
12
+ NEW_SESSION = auto()
13
+ SHOW_STATUS = auto()
14
+ RUN_SCRIPT = auto()
15
+ QUIT = auto()
16
+ UNKNOWN = auto()
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class SlashMatch:
21
+ name: str
22
+ description: str
23
+
24
+
25
+ @dataclass
26
+ class SlashDispatchResult:
27
+ action: SlashAction
28
+ message: str | None = None
29
+ status: str | None = None
30
+ command: str | None = None
31
+
32
+
33
+ class SlashTokenKind(Enum):
34
+ NOT_SLASH = auto()
35
+ INCOMPLETE = auto()
36
+ AVAILABLE = auto()
37
+ UNKNOWN = auto()
38
+
39
+
40
+ SlashHandler = Callable[[], SlashDispatchResult]
41
+ ScriptRunner = Callable[[ScriptSession], None]
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class SlashCommand:
46
+ """纯 slash 命令:只分发,不跑脚本。"""
47
+
48
+ name: str
49
+ aliases: tuple[str, ...]
50
+ description: str
51
+ handler: SlashHandler
52
+
53
+ def all_names(self) -> tuple[str, ...]:
54
+ return (self.name, *self.aliases)
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class ScriptEntry:
59
+ """脚本入口:slash 名 + 脚本 runner。"""
60
+
61
+ name: str
62
+ aliases: tuple[str, ...]
63
+ description: str
64
+ script_id: str
65
+ script_run: ScriptRunner
66
+
67
+ def all_names(self) -> tuple[str, ...]:
68
+ return (self.name, *self.aliases)
69
+
70
+
71
+ SlashEntry = SlashCommand | ScriptEntry
@@ -0,0 +1,22 @@
1
+ from clak import __version__
2
+ from clak.commands.types import (
3
+ SlashAction,
4
+ SlashCommand,
5
+ SlashDispatchResult,
6
+ )
7
+
8
+
9
+ def dispatch() -> SlashDispatchResult:
10
+ return SlashDispatchResult(
11
+ action=SlashAction.SHOW_STATUS,
12
+ message=f"clak {__version__}",
13
+ status="已显示版本",
14
+ )
15
+
16
+
17
+ COMMAND = SlashCommand(
18
+ "/version",
19
+ (),
20
+ "显示工具版本",
21
+ handler=dispatch,
22
+ )
File without changes