☰
前端转 AI 100 天 Day 20:CLI 打包与发布——让助手变成真正的命令行工具
2026/10/14 8:23:49 网站建设 项目流程

前端转 AI 第 20 天:CLI 打包与发布 - 让助手变成真正的命令行工具

昨日回顾

Day 18-19 把测试写完了,核心模块覆盖 90%+。

但有个尴尬的问题——我的助手现在还得这样跑:

cd~/day19/ai-assistant-v6 python-msrc.ai_assistant.main

这根本不像一个"工具"。真正的命令行工具应该是:

ai-assistant

在任何目录下都能敲。

今天把它打包成真正的 CLI 工具,还能发布到 PyPI 让别人pip install。

Day 20 目标:把项目打包成可安装的 CLI 工具,理解 Python 打包生态。


一、为什么前端对"打包"很亲切

前端其实天天在打包:

  • npm run build把源码打成dist/
  • package.json的bin字段声明 CLI
  • npm publish发布到 npm registry
  • npx直接运行已发布的工具

Python 的打包逻辑几乎一模一样:

前端Python
package.jsonpyproject.toml
package.json的bin[project.scripts]
npm install -gpip install
npm linkpip install -e .
npm publishtwine upload/flit publish
npm registryPyPI
npx xxxpipx run xxx
node_modules/site-packages/
dist/dist/*.whl

理解了 npm,Python 的打包就是"换个名字"。


二、Python 打包的几种方式

Python 的打包生态比 npm 乱多了——有好几套工具。

工具现状推荐度
setuptools老牌、万能、啰嗦⭐⭐⭐ 兼容性好
poetry一体化、依赖 + 打包⭐⭐⭐⭐ 生态好
flit极简、只做打包⭐⭐⭐ 简单项目
hatch现代、官方推荐⭐⭐⭐⭐ 新项目推荐
pdm现代、PEP 582⭐⭐⭐
uv极快(Rust)、全能⭐⭐⭐⭐⭐2024 后新宠

这么多工具怎么选?

好消息:不管用哪个工具,pyproject.toml是共同标准。写一份配置,所有工具都能读。

我的建议:

  • 学习阶段:用setuptools(Python 自带,最稳)
  • 新项目:用hatch或uv
  • 团队协作:用poetry(依赖锁定好)

今天用setuptools,因为它最简单、最不容易出坑,学会了其他工具 10 分钟能上手。


三、理解pyproject.toml

先看完整结构(我们从 Day 6 起就在用):

[build-system] requires = ["setuptools>=68", "wheel"] build-backend = "setuptools.build_meta" [project] name = "ai-assistant" version = "0.1.0" description = "命令行 AI 助手" readme = "README.md" requires-python = ">=3.11" authors = [ { name = "你的名字", email = "you@example.com" } ] license = { text = "MIT" } keywords = ["ai", "agent", "cli"] dependencies = [ "pydantic>=2.0.0", "python-dotenv>=1.0.0", "rich>=13.0.0", ] [project.optional-dependencies] dev = ["pytest>=8.0.0", "pytest-cov>=5.0.0", "pytest-asyncio>=0.24.0"] [project.scripts] ai-assistant = "ai_assistant.main:main" [project.urls] Homepage = "https://github.com/yourname/ai-assistant" Repository = "https://github.com/yourname/ai-assistant"

三块核心配置

1.[build-system]:用哪个打包工具

[build-system] requires = ["setuptools>=68", "wheel"] build-backend = "setuptools.build_meta"

这段不用改,模板化。意思是"用 setuptools 打包,需要 68 版本以上"。

2.[project]:项目元信息

字段必填说明
name✅包名(PyPI 上唯一)
version✅版本号
description一句话描述
readmeREADME 文件
requires-pythonPython 版本要求
dependencies依赖列表(等于requirements.txt)
authors作者
license开源协议

关键点:dependencies写在这里,等于requirements.txt。装包时自动装依赖。

3.[project.scripts]:CLI 入口(最重要!)

[project.scripts] ai-assistant = "ai_assistant.main:main"

这行的含义:

  • 左边ai-assistant:命令行敲的命令
  • 右边ai_assistant.main:main:调用ai_assistant包的main.py里的main()函数

等价于前端:

// package.json{"bin":{"ai-assistant":"./bin/cli.js"}}

一模一样的心智。


四、改造项目结构

1. 复制 Day 19 的项目

cp-rday19/ai-assistant-v6 day20/ai-assistant-v7cdday20/ai-assistant-v7

2. 检查目录结构

标准 Python 包结构长这样:

ai-assistant-v7/ ├── pyproject.toml ├── README.md ├── .gitignore ├── .env.example ├── src/ │ └── ai_assistant/ │ ├── __init__.py │ ├── __main__.py # ← 新增 │ ├── main.py │ ├── config.py │ ├── models.py │ └── ... └── tests/ ├── conftest.py ├── test_storage.py └── test_memory.py

关键点:src/布局。

为什么用src/布局?

  • 避免"意外 import":如果包直接在根目录,测试时可能 import 到源码而不是安装的版本
  • 强制显式安装:必须pip install -e .才能 import,更接近用户实际使用

3. 创建__main__.py

__main__.py让包可以这样跑:

python-mai_assistant

内容:

"""支持 `python -m ai_assistant`"""from.mainimportmainif__name__=="__main__":main()

同时,[project.scripts]里的main()是给ai-assistant命令用的。两者都可以。

4. 修改main.py

确保有一个可被调用的main()函数:

"""程序入口"""importargparseimportasyncioimportsysfrom.configimportconfigfrom.loggerimportsetup_logging,get_loggerfrom.sessionimportAIAssistantdefparse_args():parser=argparse.ArgumentParser(prog="ai-assistant",description="命令行 AI 助手(本地)",formatter_class=argparse.ArgumentDefaultsHelpFormatter,)parser.add_argument("--version",action="version",version="%(prog)s 0.1.0",)parser.add_argument("--no-stream",action="store_true",help="关闭流式输出",)parser.add_argument("--log-level",default=config.LOG_LEVEL,choices=["DEBUG","INFO","WARNING","ERROR"],help="日志级别",)returnparser.parse_args()asyncdefasync_main(args):app=AIAssistant(use_stream=notargs.no_stream)try:awaitapp.run()finally:app.close()defmain():"""CLI 入口函数"""args=parse_args()config.ensure_dirs()setup_logging(args.log_level)logger=get_logger(__name__)try:asyncio.run(async_main(args))exceptKeyboardInterrupt:print("\n已退出")sys.exit(0)exceptExceptionase:logger.exception("应用崩溃")print(f"应用崩溃:{e}",file=sys.stderr)sys.exit(1)if__name__=="__main__":main()

这里加了--version参数——CLI 工具的标配。

5. 补充pyproject.toml

[build-system] requires = ["setuptools>=68", "wheel"] build-backend = "setuptools.build_meta" [project] name = "ai-assistant-cli-demo" # PyPI 上必须唯一,加个后缀 version = "0.1.0" description = "命令行 AI 助手 - 前端转 AI 学习项目" readme = "README.md" requires-python = ">=3.11" authors = [ { name = "前端转 AI" } ] license = { text = "MIT" } keywords = ["ai", "agent", "cli", "llm"] dependencies = [ "pydantic>=2.0.0", "python-dotenv>=1.0.0", "rich>=13.0.0", ] [project.optional-dependencies] dev = [ "pytest>=8.0.0", "pytest-cov>=5.0.0", "pytest-asyncio>=0.24.0", "build>=1.0.0", ] [project.scripts] ai-assistant = "ai_assistant.main:main" [project.urls] Homepage = "https://github.com/yourname/ai-assistant" Repository = "https://github.com/yourname/ai-assistant" # setuptools 需要的:告诉它去哪找包 [tool.setuptools.packages.find] where = ["src"] # pytest 配置 [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto" addopts = "-v --tb=short"

注意最后两段——这是 setuptools 的专属配置。

[tool.setuptools.packages.find]告诉 setuptools 去src/下找包。不写的话,setuptools 会在根目录找,找不到ai_assistant。

6. 加一个 README

README.md:

# ai-assistant 命令行 AI 助手(学习项目)。 ## 安装 ```bash pip install -e .

使用

# 直接运行ai-assistant# 或者python-mai_assistant

开发

pipinstall-e".[dev]"pytest

许可

MIT

--- ## 五、本地安装(可编辑模式) ### 1. `pip install -e .` ```bash cd day20/ai-assistant-v7 pip install -e .

-e= editable = 开发模式。

它做了什么:

  • 不会复制文件,而是在site-packages里放一个"指向当前目录"的软链接
  • 改代码立刻生效,不用重新安装
  • 同时注册ai-assistant命令

输出:

Obtaining file:///Users/you/day20/ai-assistant-v7 Installing build dependencies ... done Checking if build backend supports build_editable ... done ... Successfully built ai-assistant-cli-demo Successfully installed ai-assistant-cli-demo-0.1.0

2. 测试新命令

在任何目录下:

cd~ ai-assistant

应该会启动助手!

╭───────────────────────────╮ │ │ │ AI 助手 v2(带记忆) │ │ │ ╰───────────────────────────╯ ℹ 会话 ID: a3f9b2e1 你 ›

同时也能:

ai-assistant--version# ai-assistant 0.1.0ai-assistant--help# usage: ai-assistant [-h] [--version] [--no-stream] ...python-mai_assistant--version# ai-assistant 0.1.0

是不是超有成就感?你的项目从一个"文件夹"变成了真正的"工具"。

3. 卸载

pip uninstall ai-assistant-cli-demo

-e安装的也能卸载干净。


六、构建分发包

"安装到本地"和"发布给别人"是两回事。

1. 安装build

pipinstallbuild

build是 Python 官方的构建工具,比老式的python setup.py sdist简单得多。

2. 构建

python-mbuild

输出:

* Creating venv isolated environment... * Installing packages in isolated environment... (setuptools>=68, wheel) * Getting build dependencies for sdist... * Building sdist... * Building wheel from sdist * Creating venv isolated environment... * Installing packages in isolated environment... (setuptools>=68, wheel) * Getting build dependencies for wheel... * Building wheel... Successfully built ai_assistant_cli_demo-0.1.0.tar.gz and ai_assistant_cli_demo-0.1.0-py3-none-any.whl

dist/目录下多出两个文件:

dist/ ├── ai_assistant_cli_demo-0.1.0.tar.gz # 源码包 (sdist) └── ai_assistant_cli_demo-0.1.0-py3-none-any.whl # 二进制包 (wheel)

两种包的区别:

类型后缀内容用途
sdist.tar.gz源码需要编译的场景
wheel.whl预构建优先用这个,装得快

前端类比:

  • sdist≈ 源码包(含package.json,装的时候要 build)
  • wheel≈ 打包好的dist/(直接就能用)

3. 从 wheel 安装

pipinstalldist/ai_assistant_cli_demo-0.1.0-py3-none-any.whl

和从 PyPI 装是一样的效果。可以把这个.whl发给同事,同事pip install xxx.whl就能用。


七、发布到 PyPI(了解)

发布 = 上传到 PyPI(Python 官方的包仓库,相当于 npm registry)。

1. 注册账号

  • 测试环境:https://test.pypi.org/
  • 生产环境:https://pypi.org/

先在测试环境玩,别一上来就往生产发。

2. 生成 API Token

登录 → Account settings → API tokens → Add API token

得到一个pypi-xxx...开头的 token。

3. 用twine上传

pipinstalltwine# 上传到测试环境twine upload--repositorytestpypi dist/*# 上传到生产twine upload dist/*

会提示输入用户名和密码:

  • 用户名:__token__
  • 密码:粘上pypi-xxx...

4. 验证

# 从测试环境安装pipinstall--index-url https://test.pypi.org/simple/ ai-assistant-cli-demo

或者同事直接:

pipinstallai-assistant-cli-demo

你就发布了一个 Python 包!

⚠️PyPI 的包名是全局唯一的,且不能删除(只能 yank)。测试时一定加后缀,比如xxx-demo/xxx-test。

5. 自动发布:GitHub Actions

手动上传太麻烦。生产项目一般配 CI:

# .github/workflows/publish.ymlname:Publish to PyPIon:release:types:[published]jobs:publish:runs-on:ubuntu-latestpermissions:id-token:write# 用 Trusted Publisher,不用 tokensteps:-uses:actions/checkout@v4-uses:actions/setup-python@v5with:python-version:"3.11"-run:pip install build-run:python-m build-uses:pypa/gh-action-pypi-publish@release/v1

流程:

  1. 在 GitHub 上创建 release
  2. Actions 自动构建
  3. 自动上传 PyPI

这叫 Trusted Publisher(OIDC 认证)——不用手动存 token,更安全。

这部分今天只要知道有这回事,用的时候再查。


八、版本管理

版本号不是随便起的,Python 遵循语义化版本(SemVer):

主版本.次版本.修订号 ↑ ↑ ↑ Major Minor Patch
变化示例含义
Patch0.1.0 → 0.1.1bug 修复
Minor0.1.0 → 0.2.0加功能,向后兼容
Major0.1.0 → 1.0.0破坏性变更

版本号写在pyproject.toml里:

[project] version = "0.1.0"

但是——手动改太容易忘。更好的做法:

1. 用__version__变量

src/ai_assistant/__init__.py:

__version__="0.1.0"

pyproject.toml里引用:

[project] dynamic = ["version"] [tool.setuptools.dynamic] version = { attr = "ai_assistant.__version__" }

这样版本号只在源码里改一次,打包时自动读取。

2. 用setuptools_scm:从 git tag 自动生成

最强方案——版本号从 git tag 来:

[build-system] requires = ["setuptools>=68", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [project] dynamic = ["version"] [tool.setuptools_scm]

然后打 tag 就是发版本:

gittag v0.1.1gitpush--tags

打包时版本号自动是0.1.1。

前端类比:类似npm version patch,但更自动化。

建议:

  • 学习项目:手动写在__init__.py
  • 正式项目:用setuptools_scm+ git tag

九、用pipx隔离安装 CLI 工具

问题:pip install ai-assistant会装到当前环境,如果你在项目 A 的 venv 里,就污染了。

解决:pipx——专门装 CLI 工具的工具。

# 安装 pipxbrewinstallpipx# Mac# 或pipinstall--userpipx# 用 pipx 装工具pipxinstallai-assistant-cli-demo# 现在 anywhere 都能用ai-assistant

pipx会为每个工具创建独立虚拟环境,互不干扰。

前端类比:pipx≈npx(全局安装的工具),但更隔离。

推荐:所有 CLI 工具都用pipx装,不用pip install。


十、实战:把 Day 19 项目完整打包

1. 完整项目结构

ai-assistant-v7/ ├── pyproject.toml ✅ ├── README.md ✅ ├── .gitignore ├── .env.example ├── LICENSE ← 新增 ├── src/ │ └── ai_assistant/ │ ├── __init__.py (加 __version__) │ ├── __main__.py ✅ 新增 │ ├── main.py (加 --version) │ ├── config.py │ ├── models.py │ ├── orm_models.py │ ├── db_orm.py │ ├── storage_orm.py │ ├── llm.py │ ├── memory.py │ ├── commands.py │ ├── ui.py │ ├── logger.py │ └── schema.sql ├── tests/ │ ├── conftest.py │ ├── test_storage.py │ └── test_memory.py ├── scripts/ │ └── migrate_json_to_db.py └── dist/ (构建产物,不提交)

2.__init__.py

"""AI 助手 - 命令行工具"""__version__="0.1.0"

3.LICENSE(MIT 示例)

MIT License Copyright (c) 2026 前端转 AI Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

4..gitignore加构建产物

# 已有 __pycache__/ *.py[cod] .venv/ venv/ .env data/ logs/ .vscode/ .DS_Store # 新增 build/ dist/ *.egg-info/ .pytest_cache/ .coverage htmlcov/

5. 完整构建流程

# 1. 清理旧的rm-rfbuild dist *.egg-info# 2. 跑测试pytest# 3. 构建python-mbuild# 4. 检查产物lsdist/# ai_assistant_cli_demo-0.1.0-py3-none-any.whl# ai_assistant_cli_demo-0.1.0.tar.gz# 5. 在一个干净的环境里测试安装python-mvenv /tmp/test-envsource/tmp/test-env/bin/activate pipinstalldist/ai_assistant_cli_demo-0.1.0-py3-none-any.whl ai-assistant--versiondeactivaterm-rf/tmp/test-env

第 5 步是关键!不要在自己开发环境测,要在干净环境里测——能发现"忘了声明某个依赖"的问题。

6. 加一个 Makefile(可选但推荐)

Makefile:

.PHONY: install dev test cov build clean publish install: pip install -e . dev: pip install -e ".[dev]" test: pytest cov: pytest --cov=src/ai_assistant --cov-report=html build: clean python -m build clean: rm -rf build dist *.egg-info .pytest_cache .coverage htmlcov find . -type d -name __pycache__ -exec rm -rf {} + publish: build twine upload dist/*

用法:

makedev# 装开发依赖maketest# 跑测试makebuild# 打包makepublish# 发布

前端类比:相当于package.json的scripts,但 Python 用 Makefile 更普遍。


十一、前端视角:Python 打包 vs npm

用一张表彻底对照:

环节npmPython (setuptools)
项目配置package.jsonpyproject.toml
声明 CLI"bin": {...}[project.scripts]
入口文件#!/usr/bin/env nodedef main(): ...
本地链接npm linkpip install -e .
依赖安装npm installpip install -e .
依赖锁package-lock.jsonpoetry.lock/uv.lock
构建npm run buildpython -m build
构建产物dist/(JS 文件)dist/*.whl+*.tar.gz
发布npm publishtwine upload/ CI
仓库npm registryPyPI
全局安装npm i -gpipx install
免安装运行npxpipx run/uvx
版本来源package.jsonpyproject.toml/ git tag

最爽的一点:Python 有-e模式,改代码立刻生效。npm 的link也能做到,但没这么顺滑。

最烦的一点:Python 的构建工具太多。npm 就一套,Python 有一堆。选一套用,别纠结。


十二、今日踩坑

  1. 忘了[tool.setuptools.packages.find]→ setuptools 找不到src/下的包,打出来的 wheel 是空的
  2. [project.scripts]路径写错→ 比如写成src.ai_assistant.main:main,会报ModuleNotFoundError
  3. PyPI 包名冲突→ 想叫ai-assistant结果被占了,加前缀或后缀
  4. 忘了把dist//build/加到.gitignore→ 提交一堆构建产物
  5. pip install -e .后忘了pip uninstall→ 环境里一堆开发包,冲突
  6. README.md里的相对链接在 PyPI 上失效→ PyPI 渲染的是打包时那一刻的内容,别放动态链接
  7. license = { text = "MIT" }写成license = "MIT"→ 新版 PEP 639 支持简写,但旧版会报错,兼容起见用老写法
  8. 没在干净环境测→ 自己机器上装了各种库,跑得好好的,用户装完直接ModuleNotFoundError
  9. python -m build报错 “No module named ‘build’”→ 忘了pip install build
  10. pip install .装了旧版本→ pip 有缓存,加--no-cache-dir或先pip uninstall

十三、阶段小结:Day 16-20 你有了什么

从数据到测试到打包,四天打完了一套"工程化地基":

Day内容产出
16SQLiteDatabase封装类
17SQLAlchemy ORMSessionStorageORM
18pytest 基础tests/目录 + 19 个测试
19pytest 进阶mock / 集成 / 性能测试
20打包一个能pip install的 CLI 工具

你的项目现在有的能力:

  • ✅ 数据持久化(SQLite + ORM)
  • ✅ 测试保障(90%+ 覆盖率)
  • ✅ 命令行工具(ai-assistant命令)
  • ✅ 分发包(.whl能给同事)
  • ✅ 自动发布(GitHub Actions 可配)

这已经是一个"能拿出来说"的项目了。简历上可以写:

命令行 AI 助手:基于 Python 3.11,集成 SQLAlchemy + Pydantic + pytest,支持会话持久化、记忆管理、CLI 打包发布,测试覆盖率 90%+。

但它还差一块——它现在只能"本地跑",没有 API。


十四、今日总结

今天核心收获三件事:

  1. [project.scripts]是 Python 的 CLI 入口,等价于 npm 的bin
  2. pip install -e .是开发模式,改代码立刻生效,本地开发必备
  3. python -m build是标准构建工具,产出 wheel + sdist,上传 PyPI 就能发布

一个感受:

前端的"打包发布"是显式的——你有dist/,有npm publish,每次改动都清楚"这一步是在打包"。
Python 的"打包发布"是隐式的——你的项目一直就是一个"包",pyproject.toml定义了一切,pip install -e .只是"让它知道自己是个包"。

从今天起,你写的 Python 项目不再是"脚本集合",而是"可安装、可分发、可发布"的软件。
这是从"写代码"到"做产品"的关键一步。

Day 20 完成度:✅

明日预告(Day 21)

进入 FastAPI,正式进入后端世界。

  • FastAPI 是什么,为什么比 Flask 更适合 AI 应用
  • 写第一个接口:GET /和POST /chat
  • Pydantic 模型自动校验(Day 11 的成果)
  • 自动生成 API 文档(Swagger UI)
  • 把ai-assistant从 CLI 变成 HTTP 服务

准备好,从"命令行"到"服务端"的跨越。

最后

如果这篇对你有用,点个赞让我知道有人在看。

评论区互动:你之前打包发布过 npm 包吗?感觉 Python 的流程和前端比,哪个更舒服?

有问题评论区见,我们第 21 天继续。

100 天,第 21 天见。


附录:系列目录

已完成 ✅

阶段天数内容
基础01-06Python 语法、数据结构、函数、OOP、文件、依赖管理
网络07-08HTTP 请求、异步 asyncio
工程09-12正则、装饰器、Pydantic、日志调试
项目13-14命令行 AI 助手 v1 / v2
复盘15阶段总结
数据16-17SQLite 数据库、SQLAlchemy ORM
测试18-19pytest 入门 + 进阶
工具20CLI 打包与发布 ✅

进行中 🎯

阶段天数内容
后端21-30FastAPI + 完整服务端
大模型31-45LLM API + Prompt 工程
RAG46-60向量数据库 + 知识库
Agent61-80LangChain / 工具调用
实战81-100完整 Agent 项目上线

100 天后见,一起从切图仔变成 Agent 开发者。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询