DeepTutor CLI 的命令树一次看完:命令组到底有几个,取决于你怎么数

2026-08-10

接手一个陌生的 CLI 项目,第一件想干的事通常是「先看看它有哪些命令」。多数人的做法是跑一下 --help,或者 grep 一遍 @app.command(。DeepTutor 这个项目会让这两条路给出不一样的答案——而且不是差一两个,是差出一整个量级的解释空间。

本文把 deeptutor_cli/ 这一层的命令树按行号读一遍,重点讲清「怎么数」这件事,因为它直接决定你后面找代码时会不会找错文件。

先交代口径:以下所有行号、文件名与数字都对应我们采集的仓库快照 HEAD 456f9c2,采集日 2026-08-10,deeptutor/__version__.py 里的版本号是 1.5.11。我们只读源码文本,没有安装、部署或运行过这个项目。你 clone 之后行号可能已经漂了,但文件名和标识符是稳的,照着找即可。

一、入口在哪:两条等价的启动路径

控制台入口点写在 pyproject.toml:78deeptutor = "deeptutor_cli.main:main"。也就是说 deeptutor 这个可执行名指向的是 deeptutor_cli/main.py 里的 main()

另一条路是 python -m deeptutor_cli,由 deeptutor_cli/__main__.py 支撑,全文只有 5 行,做的事就是 import 并调用同一个 main()deeptutor_cli/__main__.py:1-5)。两条路殊途同归,读源码时不必分别追。

顶层 Typer 应用的定义在 main.py:29-34,四个要素:

  • name="deeptutor"
  • help 文案是 DeepTutor CLI – agent-first interface for capabilities, tools, and knowledge.
  • no_args_is_help=True——不带参数直接打印帮助
  • add_completion=False——没有 shell 补全安装命令

最后一条值得单独记一笔。Typer 默认会带一组补全安装命令,这个项目在 main.py:34add_completion=False 显式关掉了。这一行的语义是不注册补全安装命令;至于你的终端里补全是否可用,还取决于你自己的 shell 配置,我们没有验证。

还有一处副作用要知道:main.py:26-27模块导入时就执行了 set_mode(RunMode.CLI)configure_logging()。不是在 main() 里,是 import 阶段。也就是说只要有人 import deeptutor_cli.main,运行模式与日志配置就已经被设定了。

二、命令组挂载表

命令组的挂载集中在 main.py:48-59,help 文案在 main.py:36-46。按源码顺序列全:

挂载名help 文案
partnerManage partners (IM-connected companions).
chatInteractive chat REPL.
kbManage knowledge bases.
skillManage skills and install from hubs (ClawHub, …).
skills同上(main.py:52 注释标明是 skill 的别名)
memoryView and manage lightweight memory.
pluginList plugins.
configInspect configuration.
sessionManage shared sessions.
notebookManage notebooks and imported markdown records.
providerManage provider OAuth login.
bookManage interactive Books (BookEngine).

这张表读起来有个坑:skills 不是一个新的组main.py:52 把同一个 Typer 对象又挂了一次,名字换成复数。所以 deeptutor skill listdeeptutor skills list 走的是同一份代码。这意味着,如果你按「挂载名」数,得到 12;按「不同的命令组实体」数,只有 11。

顺带说一句 help 文案与实现对不上的地方:provider 组的 help 写的是 Manage provider OAuth login.main.py:45),但这个组下 logingithub-copilot 走的是校验既有认证的一次请求,不是 OAuth 流程(provider_cmd.py:79-99,该命令自身的 help 用词是 validate existing Copilot authprovider_cmd.py:20)。两处措辞不一致,以代码为准。说完就停,我们不推断为什么会这样。

三、四个顶层命令

除了命令组,还有 4 个直接挂在顶层的命令:

  • run <capability> <message>main.py:75)——docstring 是 Run any capability in a single turn (agent-first entry point).main.py:97)。capability 参数的 help 里举了几个名字:chat, deep_solve, deep_question, deep_research, visualize, math_animator, mastery_pathmain.py:77-83)。选项一串:--session--tool/-t--kb--notebook-ref--history-ref--language/-l(默认 "en")、--config--config-json--format/-f(默认 "rich",help 写 Output format: rich | json.)(main.py:85-95)。
  • startmain.py:117)——docstring Launch backend + frontend together. Source installs default to production.,选项 --home(Path)与 --dev(默认 False,help 写 Use the Next.js development server for frontend work.)。
  • servemain.py:132)——--host 默认 "0.0.0.0"--port 默认 None(为 None 时取 get_backend_port()),--reload 默认 False。
  • initinit_cmd.py:542)——注意它不在 main.py 里,是 init_cmd.py 注册到顶层 app 的。签名是 --cli(默认 False,help Initialize for CLI-only use.)与 --home(默认 None),docstring Create or update data/user/settings for this workspace.

--host 那个默认值请留意:0.0.0.0 意味着默认监听所有网络接口,不是只听 127.0.0.1。这是源码里的默认配置,不是对你实际网络环境的判断,怎么处置取决于你自己的部署环境——把它放到公网可达的机器上跑之前,先想清楚这一点。防火墙、反向代理一类的收口做法属于通用运维经验,不是该项目文档的内容。

serve 里还有一处 Windows 专属分支值得单独记:main.py:148-152sys.platform == "win32" 时切换到 WindowsProactorEventLoopPolicy,注释说明的理由是 uvicorn 默认的 SelectorEventLoop 不支持 asyncio.create_subprocess_exec。这句注释顺带透露了一个事实:这个后端要起子进程。另一处相关的是 uvicorn 缺失时的处理——打印 API server dependencies not installed.raise typer.Exit(code=1)main.py:156-161),装的是纯 CLI 依赖的话会走到这一支。

四、反直觉的那一处:四个数字,都对

回到开头那个问题。「DeepTutor CLI 有多少个命令」这个问题,在这个仓库里至少有四个都能自圆其说的答案:

43 ——全仓 deeptutor_cli/*.py@app.command( 装饰器的 grep 计数,分布是 skill 8、kb 7、notebook 6、session 5、partner 4、book 3、main 3、memory 2、plugin 2、config_cmd 1、provider_cmd 1、init_cmd 1。

12 ——顶层挂载的命令组名数量(含 skills 别名)。

11 ——去掉别名之后不同的命令组数量。

4 ——顶层直挂的命令数(run / start / serve / init)。

这四个数字互相之间不能换算,原因有三处,每一处都能在源码里定位:

其一,chat 在 43 里的贡献是 0。 它不是用 @app.command 注册的,而是 @app.callback(invoke_without_command=True)chat.py:43-60)。这个写法的语义是:deeptutor chat 不带子命令时直接执行回调本身,也就是直接进 REPL。所以 grep @app.command( 这条路会把这个项目最显眼的交互入口整个漏掉。这是本文最想让你记住的一条——用装饰器 grep 数命令,在这个仓库会数漏

其二,43 里的 skill 那 8 条,有两条可达路径。 因为同一个 Typer 对象挂了两次(main.py:51-52)。装饰器只写了一遍,命令行上却是 skillskills 两个前缀都通。

其三,文件行数与命令数没有关系。 最典型的是 skill_login.py:134 行,@app.command( 的计数是 0。它一个命令都不注册,只提供 loopback OAuth 的纯函数与流程(hub_origin_from_baseoauth_start_urlrun_loginLoginResult__all__skill_login.py:134),真正的 skill login 命令写在 skill.py 里,运行时才 from .skill_login import ...skill.py:186)。反过来,common.py 有 912 行、init_wizard.py 有 996 行,两个都是 0 命令的支撑模块。整个 deeptutor_cli/ 是 20 个文件(19 个 .py + 1 个 README.md)、合计 5421 行(wc -l),其中命令注册只占很小一部分。

所以拿到这个仓库要找某个命令的实现时,正确的路径不是搜命令名,而是先在 main.py:48-59 找到组名对应的 register_* 来自哪个模块,再进那个文件。skill login 找不到就是这么来的——它的名字在 skill.py,逻辑在 skill_login.py

五、README 没有记的那几组

deeptutor_cli/README.md 有 274 行,但它记录的命令面比代码窄。逐条对照(左边是代码位置,右边是 README 状态):

  • skill / skills 组共 8 个子命令(skill.py:75/108/171/228/242/365/523/543,注册见 main.py:51-52)——README 的「资源管理命令」一节只列了 kb / session / notebook / memory / plugin / config / provider(README.md:168-229),没有 skill 章节
  • book 组 3 个子命令(book.py:17/37/49)——README 未记录。这里照实标一下:book.py:1-5 的模块 docstring 写的是当前只暴露维护类命令,创作与阅读仍走 API + web 前端。这一句与本文第三节没展开 book 子命令的原因正好吻合——list / health / refresh-fingerprints 三条都是维护性质的。
  • partner 组 4 个子命令(partner.py:17/47/62/76)——README 未记录。
  • 顶层 startmain.py:117-129)——README 只写了 runchatserve 三个入口章节,未记录 start

这几处只是陈述差异:代码里有、README 里没有,以我们实读的仓库状态为准。我们不推断原因,也不由此评价文档质量。实际影响是具体的——如果你只读 README 来判断这个 CLI 能做什么,会漏掉四块

另外有两处 README 与代码的口径差,落在具体命令的选项与步骤上:init_cmd.py:3-4 的模块 docstring 自述是「four-step wizard (ports → LLM → embedding → review)」,而代码里 total_steps = 4 if cli_only else 5init_cmd.py:429-431),多出来的是 Search 那一步(init_cmd.py:501-505);chat 的选项表在 README.md:127-134 只列了 5 个,代码里还有 --notebook-ref--history-ref--config--config-jsonchat.py:50-56)。这两处各自另有专篇细讲,本文只标位置。

六、两处需要如实说明的边界

第一,这个 CLI 会在你本机起进程、开端口、往本机写文件。前面提到的 serve 子进程注释是一处;skill login 会在本机起一个 loopback 服务器,绑 ("127.0.0.1", 0) 拿随机端口,只接受 /callback 路径,state 不匹配返回 400 state mismatch,默认超时 300.0 秒(skill_login.py:111:83-96:69);skill install <ref> 会按 <hub>:<slug>[@version] 从远端 hub 取内容装进本机工作区,模块 docstring 写明流程是「hub security verdict → safe extraction → frontmatter adaptation → .hub-lock.json provenance」,并且有 --allow-unverified 这个显式绕过校验的开关(skill.py:1-16:108-169)。这些都是本机侧的实际动作,不是纯读操作,是否使用请结合自身环境评估。

第二,run 的 help 里举的那些 capability 名字(deep_solvemastery_path 等)在源码里就是一串字符串示例。这个项目是教育向的,涉及掌握度、复习节奏一类的东西都是项目自己的实现选择,不是经过验证的教学结论。本文只写这些名字出现在 main.py:77-83 的 help 文案里,不评价它们在学习效果上的任何表现,也不承诺任何结果。

七、你可以照着核的五步

  1. 打开 deeptutor_cli/main.py:48-59,把 add_typer 的调用逐行数一遍,确认 skill 出现了两次、第二次名字是 skills
  2. deeptutor_cli/ 目录下跑 grep -c "@app.command(" *.py,把结果加起来,确认是 43,并确认 chat.py 的计数是 0。
  3. 打开 chat.py:43-60,确认那里是 @app.callback(invoke_without_command=True) 而不是 @app.command——这就是上一步为什么会漏掉 chat
  4. 打开 deeptutor_cli/README.md,在全文搜 skillbookpartnerstart 四个词,对照第五节的清单看它们各自有没有章节。
  5. 打开 main.py:132-152,确认 --host 的默认值是 "0.0.0.0",以及 win32 那个事件循环分支的注释理由。

上面每一条都不需要安装这个项目,clone 下来读文本就能完成。

最后交代边界:本文只读了 deeptutor_cli/ 这一层的注册结构与各命令的签名,没有逐行读完 5421 行;common.py 里那个约 380 行的事件流渲染状态机(common.py:316-692)、init_wizard.py 中间段的向导交互实现,我们都没有逐行核实。各命令在真实终端下的实际行为与退出码,我们一概没有验证——本文只陈述源码文本写了什么。文中出现的所有默认值都是源码里的默认配置,不构成对运行结果的任何保证。


本文依据 DeepTutor 官方仓库(github.com/HKUDS/DeepTutor)的 README、AGENTS.mdpyproject.tomldeeptutor/ 下的源码整理,核对日 2026-08-10,对应仓库快照 456f9c2(版本 1.5.11)。 本文内容为仓库源码与文档口径,我们没有安装、部署或运行过该项目,也没有调用过其中任何一个模型 API, 因此不涉及生成质量、响应速度与教学效果的任何描述。 参数与默认值随版本变动,请以仓库最新代码与 --help 的实际输出为准。 安全相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。