Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions .github/workflows/preview.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
name: preview
name: Preview Docs

on:
pull_request:
branches:
- master

permissions:
contents: write
pull-requests: write
Expand All @@ -19,28 +21,42 @@ jobs:
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
python-version: "3.x"
- run: echo "cache_id=$(date --utc '+%V')" >> "$GITHUB_ENV"
- uses: actions/cache@v4
with:
key: mkdocs-material-${{ env.cache_id }}
path: .cache
restore-keys: |
mkdocs-material-
- run: pip install .[docs]
- run: |
git fetch origin
mike deploy dev -p --allow-empty
echo "Preview URL: https://chattool.wzhecnu.cn/dev/" >> $GITHUB_STEP_SUMMARY
- run: python -m pip install --upgrade pip
- run: python -m pip install '.[docs]'
- name: Deploy preview docs
run: |
git fetch origin gh-pages --depth=1 || true
mike deploy dev --push --update-aliases --allow-empty
site_url=$(python - <<'PY'
from pathlib import Path

for line in Path("mkdocs.yml").read_text(encoding="utf-8").splitlines():
if line.startswith("site_url:"):
print(line.split(":", 1)[1].strip().rstrip("/"))
break
else:
raise SystemExit("site_url not found")
PY
)
echo "CHATTOOL_PREVIEW_URL=${site_url}/dev/" >> "$GITHUB_ENV"
echo "Preview URL: ${site_url}/dev/" >> "$GITHUB_STEP_SUMMARY"
- name: Comment PR with Preview Link
uses: actions/github-script@v6
env:
CHATTOOL_PREVIEW_URL: ${{ env.CHATTOOL_PREVIEW_URL }}
with:
script: |
const { payload } = context;
const prNumber = payload.number;
const baseUrl = 'https://chattool.wzhecnu.cn';
const previewLink = `${baseUrl}/dev/`;
const previewLink = process.env.CHATTOOL_PREVIEW_URL;
const comments = await github.rest.issues.listComments({
issue_number: prNumber,
owner: context.repo.owner,
Expand All @@ -52,6 +68,6 @@ jobs:
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Preview available at: ${previewLink}`
body: `Preview available at: ${previewLink}`,
});
}
}
6 changes: 3 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ name: Publish Package
on:
push:
tags:
- 'v*'
- "v*"

permissions:
contents: read

jobs:
build-and-publish:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -44,7 +44,7 @@ jobs:
echo "PACKAGE_VERSION=$package_version" >> "$GITHUB_ENV"
- name: Verify release commit is on master
run: |
git fetch origin master
git fetch --no-tags origin master:refs/remotes/origin/master
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/master; then
echo "Release tag commit $GITHUB_SHA is not contained in origin/master."
exit 1
Expand Down
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ All notable changes to this project will be documented in this file.

本项目按日期记录更新;正式发版信息也记录在本文件,不再维护待发布分组。

## 2026-08-12
- 准备 `7.1.4` patch 版本:为 ChatTool parent 顶层 CLI 增加真实 Click/LazyGroup 注册面生成的 `chattool --tree`,并补齐顶层 `chattool --version`。
- 文档与测试同步锁定当前 parent command surface:`browser`、`cc`、`client`、`crs`、`docker`、`explore`、`mcp`、`nginx`、`serve`、`skill`、`tplogin`;已迁出的 `dns`、`gh`、`image` 等不重新进入 parent CLI。
- 发布与文档 workflow gate 同步:发布 workflow 只允许 tag 触发、使用 GitHub OIDC/Trusted Publishing、无 legacy PyPI/Twine password secret,并用 `git fetch --no-tags origin master:refs/remotes/origin/master` 校验 tag commit 在默认分支;Preview Docs 从 `mkdocs.yml site_url` 读 URL,并先同步 `gh-pages`。
- MkDocs Material 启用 `pymdownx.emoji` 的 Material `twemoji`/`to_svg` renderer,避免 source/generated/live docs 漏出未渲染 Material icon shorthand。
- 发布前置已回读:刷新 PyPI recent-auth 后,`chatpypi publisher detail chattool -e RexWzh` 显示 active Trusted Publisher `cubenlp/ChatTool` + `publish.yml` + `(Any)`,`pending_count=0`;可按正常 tag-driven OIDC 发布 gate 继续。

## 2026-07-30
- 准备 `7.1.3` patch 版本:发布 ChatZulip parent 分离结果,使 PyPI 用户获得移除后的 `chattool` surface 与新的 `chattool[zulip]` / `chattool[arch]` / `chattool[tools]` 依赖边界。
- PyPI 发布工作流从长期 `PYPI_USERNAME` / `PYPI_PASSWORD` secrets 迁移到 `cubenlp/ChatTool` active Trusted Publisher 与 GitHub Actions OIDC;tag workflow 会先跑稳定测试、build 和 twine check,再执行 tokenless publish。
- PyPI 发布工作流从长期 PyPI username/password secrets 迁移到 `cubenlp/ChatTool` active Trusted Publisher 与 GitHub Actions OIDC;tag workflow 会先跑稳定测试、build 和 twine check,再执行 tokenless publish。
- ChatZulip parent 分离:`chattool zulip`、`src/chattool/tools/zulip/`、ChatTool 内 `ZulipConfig` re-export、Zulip MCP catalog 与 Zulip 专属测试/文档已移除;Zulip 能力由独立 `ChatZulip>=0.1.0,<0.2.0` / `chatzulip` 负责。
- 新增 `chattool[zulip]` optional extra,并把 `ChatZulip>=0.1.0,<0.2.0` 纳入 `chattool[arch]` / `chattool[tools]` 聚合安装;ChatTool base install 不再重复维护 Zulip runtime。

Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,28 @@ chatpypi probe mychat

如只想生成代码与测试骨架,可以用 `--without-mkdocs` 跳过 mkdocs/docs,用 `--without-workflows` 跳过 `.github/workflows/`。

## CLI 树

`chattool --tree` 从真实 Click/LazyGroup 注册面生成,用于锁定当前 parent command surface。

```text
chattool # Unified entry point for ChatTool services and scripts.
├── --help # Show help for the current command.
├── --version # Show package version.
├── --tree # Print the registered CLI tree.
├── browser # Browser automation helpers.
├── cc # cc-connect helper commands.
├── client # Remote client tools.
├── crs # Claude Relay Service helper commands.
├── docker # Docker utility commands.
├── explore # Research and data exploration helpers.
├── mcp # MCP server inspection and launch helpers.
├── nginx # Nginx configuration generation helpers.
├── serve # Local service helpers.
├── skill # Install and manage ChatTool skills.
└── tplogin # TP-Link router helpers.
```

## 开源协议

MIT License
22 changes: 22 additions & 0 deletions README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,28 @@ python scripts/migrate_chattool_envs_to_chatarch.py --dry-run
python scripts/migrate_chattool_envs_to_chatarch.py
```

## CLI Tree

`chattool --tree` is generated from the real Click/LazyGroup registry to lock the current parent command surface.

```text
chattool # Unified entry point for ChatTool services and scripts.
├── --help # Show help for the current command.
├── --version # Show package version.
├── --tree # Print the registered CLI tree.
├── browser # Browser automation helpers.
├── cc # cc-connect helper commands.
├── client # Remote client tools.
├── crs # Claude Relay Service helper commands.
├── docker # Docker utility commands.
├── explore # Research and data exploration helpers.
├── mcp # MCP server inspection and launch helpers.
├── nginx # Nginx configuration generation helpers.
├── serve # Local service helpers.
├── skill # Install and manage ChatTool skills.
└── tplogin # TP-Link router helpers.
```

## PyPI Package Scaffolding

```bash
Expand Down
27 changes: 27 additions & 0 deletions docs/cli-tree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# CLI 树

`chattool --tree` 从真实 Click/LazyGroup 注册面生成,用于锁定当前 parent command surface。此页面只记录 ChatTool parent 当前仍拥有的顶层命令;已迁出的能力(如 `dns`、`gh`、`image` 等)不在这里重新暴露。

```text
chattool # Unified entry point for ChatTool services and scripts.
├── --help # Show help for the current command.
├── --version # Show package version.
├── --tree # Print the registered CLI tree.
├── browser # Browser automation helpers.
├── cc # cc-connect helper commands.
├── client # Remote client tools.
├── crs # Claude Relay Service helper commands.
├── docker # Docker utility commands.
├── explore # Research and data exploration helpers.
├── mcp # MCP server inspection and launch helpers.
├── nginx # Nginx configuration generation helpers.
├── serve # Local service helpers.
├── skill # Install and manage ChatTool skills.
└── tplogin # TP-Link router helpers.
```

## 验证

- 源码入口:`chattool.client.main.cli`
- 回归测试:`tests/mock-cli-tests/client/test_chattool_version_tree_basic.py`
- 发布版本:`chattool==7.1.4`
5 changes: 4 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ markdown_extensions:
- pymdownx.caret
- pymdownx.critic
- pymdownx.details
- pymdownx.emoji
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- pymdownx.inlinehilite
- pymdownx.magiclink
- pymdownx.mark
Expand Down Expand Up @@ -105,6 +107,7 @@ extra:
nav:
- 主页:
- 快速开始: index.md
- CLI 树: cli-tree.md

- 模型能力:
- 客户端指南: client.md
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ dev = [
"chattool[arch,docs,setup,tests,tools]",
]
[project.urls]
Homepage = "https://github.com/cubenlp/chattool"
Repository = "https://github.com/cubenlp/chattool"
Homepage = "https://github.com/CubeNLP/ChatTool"
Repository = "https://github.com/CubeNLP/ChatTool"
Documentation = "https://chattool.wzhecnu.cn"

[project.scripts]
chattool = "chattool.client:main_cli"
Expand Down
2 changes: 1 addition & 1 deletion src/chattool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

__author__ = """Rex Wang"""
__email__ = "1073853456@qq.com"
__version__ = "7.1.3"
__version__ = "7.1.4"

from dotenv import load_dotenv

Expand Down
106 changes: 102 additions & 4 deletions src/chattool/client/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import click

from chattool import __version__


class LazyGroup(click.Group):
def __init__(
Expand Down Expand Up @@ -47,6 +49,13 @@ def format_commands(
formatter.write_dl(rows)


_TREE_PARAM_PURPOSES = {
"help": "Show help for the current command.",
"version": "Show package version.",
"tree": "Print the registered CLI tree.",
}


def _load_attr(module_path: str, attr: str) -> click.Command:
module = importlib.import_module(module_path)
return getattr(module, attr)
Expand All @@ -73,10 +82,84 @@ def _build_client_group() -> click.Command:
)


@click.group(cls=LazyGroup)
def cli():
"""ChatTool CLI - Unified entry point for ChatTool services and scripts."""
pass
def _command_help(command: click.Command) -> str:
if command.help:
return command.help
if command.short_help:
return command.short_help
if command.callback and command.callback.__doc__:
return command.callback.__doc__.strip()
return ""


def _param_signature(param: click.Parameter) -> str:
opts = getattr(param, "opts", None) or []
secondary = getattr(param, "secondary_opts", None) or []
names = [*opts, *secondary]
if names:
return "/".join(names)
return param.human_readable_name.upper().replace("_", "-")


def _param_purpose(param: click.Parameter) -> str:
if param.name in _TREE_PARAM_PURPOSES:
return _TREE_PARAM_PURPOSES[param.name]
if isinstance(param, click.Option) and param.help:
return param.help.rstrip(".") + "."
return "Option registered by Click."


def _iter_command_rows(command: click.Command) -> list[tuple[str, str]]:
rows = [("--help", _TREE_PARAM_PURPOSES["help"])]
for param in command.params:
if getattr(param, "hidden", False):
continue
rows.append((_param_signature(param), _param_purpose(param)))
if isinstance(command, LazyGroup):
ctx = click.Context(command)
for name in command.list_commands(ctx):
if name in command.commands:
child = command.commands[name]
if child is None or child.hidden:
continue
help_text = command._lazy_command_help.get(name)
if help_text is None and name in command.commands:
help_text = command.commands[name].get_short_help_str()
rows.append((name, help_text or ""))
elif isinstance(command, click.Group):
for name, child in sorted(command.commands.items()):
if child is None or child.hidden:
continue
rows.append((name, child.get_short_help_str()))
return rows


def render_command_tree(command: click.Command, name: str = "chattool") -> str:
lines = [f"{name} # {_command_help(command)}"]
rows = _iter_command_rows(command)
for index, (label, purpose) in enumerate(rows):
connector = "└──" if index == len(rows) - 1 else "├──"
lines.append(f"{connector} {label} # {purpose}")
return "\n".join(lines)


@click.group(cls=LazyGroup, invoke_without_command=True)
@click.version_option(__version__, prog_name="chattool")
@click.option(
"--tree",
"show_tree",
is_flag=True,
help="Print the registered CLI tree.",
)
@click.pass_context
def cli(ctx: click.Context, show_tree: bool) -> None:
"""Unified entry point for ChatTool services and scripts."""
if show_tree:
click.echo(render_command_tree(ctx.command, "chattool"))
ctx.exit(0)
if ctx.invoked_subcommand is None:
click.echo(ctx.get_help())
ctx.exit(0)


cli._lazy_commands.update(
Expand All @@ -94,6 +177,21 @@ def cli():
"crs": lambda: _load_attr("chattool.tools.crs.cli", "cli"),
}
)
cli._lazy_command_help.update(
{
"browser": "Browser automation helpers.",
"cc": "cc-connect helper commands.",
"client": "Remote client tools.",
"crs": "Claude Relay Service helper commands.",
"docker": "Docker utility commands.",
"explore": "Research and data exploration helpers.",
"mcp": "MCP server inspection and launch helpers.",
"nginx": "Nginx configuration generation helpers.",
"serve": "Local service helpers.",
"skill": "Install and manage ChatTool skills.",
"tplogin": "TP-Link router helpers.",
}
)


def main():
Expand Down
Loading
Loading