Metadata-Version: 2.5
Name: fcmd
Version: 0.3.8
Summary: 极速 Python 工具集应用：DAG 任务调度 + 组合 CLI
Author: fcmd
License: MIT
License-File: LICENSE
Keywords: cli,dag,scheduler,task,tools,workflow
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.13
Requires-Dist: pyyaml>=6.0.1
Provides-Extra: dev
Requires-Dist: pillow>=10; extra == 'dev'
Requires-Dist: prek>=0.4.5; extra == 'dev'
Requires-Dist: pymupdf>=1.23; extra == 'dev'
Requires-Dist: pypdf>=5.9.0; extra == 'dev'
Requires-Dist: pyrefly>=1.1.1; extra == 'dev'
Requires-Dist: pytesseract>=0.3.13; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
Requires-Dist: pytest-xdist>=3.6.1; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: resvg-py>=0.5; extra == 'dev'
Requires-Dist: ruff>=0.8.0; extra == 'dev'
Requires-Dist: tox-uv>=1.13.1; extra == 'dev'
Requires-Dist: tox>=4.25.0; extra == 'dev'
Provides-Extra: img
Requires-Dist: pillow>=10; extra == 'img'
Provides-Extra: lint
Requires-Dist: pyrefly>=1.1.1; extra == 'lint'
Requires-Dist: ruff>=0.8.0; extra == 'lint'
Provides-Extra: ocr
Requires-Dist: pillow>=10; extra == 'ocr'
Requires-Dist: pytesseract>=0.3.13; extra == 'ocr'
Provides-Extra: office
Requires-Dist: pillow>=10; extra == 'office'
Requires-Dist: pymupdf>=1.23; extra == 'office'
Requires-Dist: pypdf>=5.9.0; extra == 'office'
Requires-Dist: pytesseract>=0.3.13; extra == 'office'
Requires-Dist: resvg-py>=0.5; extra == 'office'
Provides-Extra: pdf
Requires-Dist: pymupdf>=1.23; extra == 'pdf'
Requires-Dist: pypdf>=5.9.0; extra == 'pdf'
Provides-Extra: svg
Requires-Dist: resvg-py>=0.5; extra == 'svg'
Provides-Extra: test
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'test'
Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
Requires-Dist: pytest-xdist>=3.6.1; extra == 'test'
Requires-Dist: pytest>=8.0.0; extra == 'test'
Description-Content-Type: text/markdown

# fcmd

极速 Python 工具集应用：DAG 任务调度 + 组合 CLI。

## 特性

- **极速冷启动**（< 100ms）：三层懒加载，核心模块零外部依赖
- **DAG 任务调度**：四种执行策略（sequential/thread/async/dependency）
- **组合 API 简洁**：`@fx.task` 装饰器 + 自动依赖推断 + `@fx.tool` CLI 工具
- **纯 CLI（自实现 Console）**：ASCII 表格、彩色输出、Win7/8 兼容
- **便捷脚本模式**：`fcmd pymake b` 一键调用
- **YAML 编排**：GitHub Actions 风格的 `jobs`/`needs`/`matrix`/`if` 任务图
- **零配置工具发现**：`fcmd/cli/` 下的模块自动注册为 CLI 子命令

## 安装

```bash
pip install fcmd          # 或 uv add fcmd
```

可选依赖（按需安装）：

| Extra | 安装命令 | 提供的工具/能力 |
|-------|---------|---------------|
| `img` | `pip install fcmd[img]` | `imagetool`（Pillow） |
| `pdf` | `pip install fcmd[pdf]` | `pdftool`（PyMuPDF + pypdf） |
| `ocr` | `pip install fcmd[ocr]` | `pdftool ocr`（pytesseract，需系统 tesseract） |
| `office` | `pip install fcmd[office]` | img + pdf + ocr 全家桶 |
| `lint` | `pip install fcmd[lint]` | 开发用：ruff + pyrefly |
| `test` | `pip install fcmd[test]` | 开发用：pytest + cov + xdist |
| `dev` | `pip install fcmd[dev]` | lint + test + office + prek + tox |

要求 Python ≥ 3.9。

## 快速上手

### Python API

```python
import fcmd as fx

@fx.task
def extract() -> list[int]: return [1, 2, 3]

@fx.task
def double(extract: list[int]) -> list[int]: return [x * 2 for x in extract]

graph = fx.graph(extract, double)  # double 自动依赖 extract
report = fx.run(graph)
print(report["double"])  # [2, 4, 6]
```

### CLI

```bash
fcmd                   # 列出所有工具
fcmd pymake b          # 构建
fcmd pymake tc         # 类型检查
fcmd info pymake       # 查看 pymake 子命令
fcmd graph pymake tc   # 输出 DAG（Mermaid）
fcmd env               # 查看运行环境
fcmd doctor            # 诊断环境问题
```

## 内建命令

`fcmd` 自带 7 个内建命令（不通过 `@fx.tool` 注册）：

| 命令 | 说明 |
|------|------|
| `fcmd graph <tool> [subcommand]` | 可视化工具子命令的 DAG（Mermaid/layers/describe） |
| `fcmd info [tool] [subcommand]` | 展示工具或子命令的元信息（不执行） |
| `fcmd completion --shell bash\|zsh\|fish` | 生成 shell 补全脚本 |
| `fcmd yaml <file> [job]` | 从 YAML 加载任务图并执行（支持 `--dry-run`/`--strategy`） |
| `fcmd env [--json]` | 展示运行环境（fcmd 版本、Python、平台、工具数、可选依赖） |
| `fcmd doctor` | 诊断环境问题（Python 版本、核心模块、工具模块、可选依赖、PATH 命令） |
| `fcmd profiler <script.py> [args] [-E html\|text] [-o FILE] [--no-browser]` | 分析含 `fx.run()` 的脚本，生成性能剖面报告（HTML/文本） |

shell 补全安装：

```bash
eval "$(fcmd completion --shell bash)"       # bash
eval "$(fcmd completion --shell zsh)"         # zsh
fcmd completion --shell fish | source         # fish
```

## 工具列表

59 个工具（其中 47 个为 Python 模块，`clr`/`autofmt`/`dockercmd`/`sshcopyid`/`reseticoncache`/`piptool` 纯 exec 与 compute 工具及 `setenv`/`writefile`/`filedate`/`filerename`/`filelevel`/`folderback`/`taskkill`/`which`/`sysinfo` action 原语工具由 DSL 声明），按用途分组：

### 项目构建与发布

| 工具 | 别名 | 说明 |
|------|------|------|
| `pymake` | `pm` | 项目构建/测试/检查/发布全流程（b/t/tc/cov/bump/all 等） |
| `autofmt` | - | 代码格式化与检查（封装 ruff format/check） |
| `bumpversion` | - | 版本号自动管理（patch/minor/major + git tag） |
| `packtool` | - | Python 打包（源码/依赖/wheel/嵌入式 Python/zip/清理） |
| `piptool` | `pipt` | pip 包管理（安装/卸载/重装/下载/升级/冻结，DSL 声明 + compute 通配符展开） |

### 文件与目录

| 工具 | 别名 | 说明 |
|------|------|------|
| `writefile` | - | 写入文本内容到文件 |
| `filedate` | - | 文件日期前缀处理（添加/清除） |
| `filelevel` | - | 文件等级标记重命名（PUB/NOR/INT/CON/CLA） |
| `filerename` | - | 文件批量重命名（前缀/后缀/序号/替换） |
| `filesearch` | - | 文件名/内容搜索（支持正则与通配符） |
| `folderback` | - | 文件夹备份（自动清理旧备份） |
| `folderzip` | - | 压缩当前目录下所有子文件夹 |
| `hashfile` | - | 文件哈希计算（md5/sha256/sha1，支持目录批量） |
| `pathtool` | - | 路径处理（拆分/拼接/规范化/相对路径） |
| `archivex` | - | 归档解压（zip/tar/gz/bz2，跨平台） |
| `zipencrypt` | - | ZIP 加密（AES/ZipCrypto，批量） |

### 文本与编码

| 工具 | 别名 | 说明 |
|------|------|------|
| `txttool` | - | 文本处理（大小写/去重/排序/统计/截断） |
| `asciitool` | - | ASCII 码表（char/code/table） |
| `codetool` | - | 编解码（Base64/URL/HTML/Hex/ROT13） |
| `casetool` | - | 命名风格转换（camel/snake/kebab/pascal） |
| `colortool` | - | 颜色换算（RGB/HEX/HSL/HSV） |
| `convtool` | - | 单位换算（长度/重量/温度/面积等） |
| `csvtool` | - | CSV 处理（pretty/extract/filter/sort） |
| `hashtool` | - | 字符串哈希（md5/sha1/sha256/sha512） |
| `idtool` | - | ID 生成（UUID/雪花/时间戳） |
| `jsontool` | - | JSON 处理（pretty/get/keys/validate） |
| `mathtool` | - | 数学计算（eval/sqrt/pow/factorial） |
| `padtool` | - | 文本对齐（left/right/center/justify） |
| `randtool` | - | 随机生成（数字/字符串/选择/洗牌） |
| `regextool` | - | 正则表达式（match/find/replace/split） |
| `stattool` | - | 统计工具（mean/median/variance/quantile） |
| `textdiff` | - | 文本比较（行级 diff，统一格式输出） |
| `timetool` | - | 时间工具（convert/format/timezone） |
| `calcdate` | - | 日期计算（add/workdays/diff/compare） |
| `urltool` | - | URL 解析（编码/解码/拆分/拼接） |
| `cryptool` | - | 文本对称加密（genkey/encrypt/decrypt，PBKDF2 + HMAC-SHA256 CTR + Encrypt-then-MAC） |

### 数据格式

| 工具 | 别名 | 说明 |
|------|------|------|
| `xmltool` | - | XML 处理（pretty/get/keys/validate） |
| `yamtool` | - | YAML 处理（pretty/get/keys/validate） |
| `inifile` | - | INI 处理（get/set/list/keys，基于 configparser） |
| `tomltool` | - | TOML 处理（get/keys/format/validate，3.11+ 用 tomllib，3.9-3.10 回退 tomli） |

### 多媒体（可选依赖）

| 工具 | 别名 | 说明 |
|------|------|------|
| `imagetool` | - | 图片处理（resize/crop/rotate/flip/convert/watermark/compress/info/exif/histogram/colors），依赖 `fcmd[img]` |
| `pdftool` | - | PDF 处理（merge/split/compress/encrypt/decrypt/extract/ocr 等 15 子命令），依赖 `fcmd[pdf]`，OCR 需 `fcmd[ocr]` |

### 系统与进程

| 工具 | 别名 | 说明 |
|------|------|------|
| `sysinfo` | - | 系统信息收集（Python/平台/内存/磁盘/CPU） |
| `portcheck` | - | 端口检查与扫描（socket 检测占用，lsof/netstat 查询占用进程，跨平台） |
| `taskkill` | - | 按名称终止进程（Windows taskkill / Unix pkill） |
| `which` | - | 查找可执行命令路径 |
| `clr` | - | 跨平台清屏 |
| `screenshot` | - | 跨平台截图（Windows PowerShell / macOS screencapture / Linux gnome-screenshot） |
| `reseticoncache` | - | 重置 Windows 图标缓存（非 Windows 打印提示） |
| `iptool` | - | IP 地址工具（本机 IP/公网 IP/子网计算） |
| `nettool` | - | HTTP 客户端（GET/POST/HEAD，状态码/响应头） |
| `urlcheck` | - | URL 可访问性与访问速度检测（HEAD/GET 探测 + 并发测速排序，供镜像源选优） |
| `websave` | - | 网页内容保存（主页及同源相关子页面，过滤广告/静态资源） |
| `lscalc` | - | LS-DYNA 计算（run/mpi/status） |
| `dockercmd` | - | Docker 操作（login/push/pull，镜像仓库通用） |

### 开发环境

| 工具 | 别名 | 说明 |
|------|------|------|
| `gittool` | - | Git 操作（add+commit/init/init-submodules/clean/push/pull） |
| `envdev` | - | 开发环境一键配置（分组入口 lang/app/mirror/check/all，镜像按服务（pip/conda/rustup/cargo/go/maven）独立自动选优 + 教育网镜像探测（动态拉取 MirrorZ 列表）+ 工具链，Linux 含系统依赖） |
| `setenv` | - | 设置当前进程环境变量（支持 `--default` 仅在未设置时写入） |
| `sshcopyid` | - | SSH 公钥部署到远程服务器（依赖 sshpass） |

查看任意工具的子命令与字段详情：

```bash
fcmd info <tool>                 # 列出工具所有子命令
fcmd info <tool> <subcommand>    # 展示 ToolSpec 完整字段
```

## 任务定义 API

### `@fx.task` —— Python 函数即任务

函数参数名即依赖名，fcmd 据此自动构建 DAG：

```python
import fcmd as fx

@fx.task
def fetch() -> str:
    return "raw data"

@fx.task
def parse(fetch: str) -> list[str]:
    return fetch.split()

@fx.task
def save(parse: list[str]) -> None:
    # parse 自动依赖 fetch，save 自动依赖 parse
    ...

report = fx.run(fx.graph(fetch, parse, save))
```

### `@fx.tool` —— 函数即 CLI 子命令

```python
import fcmd
from fcmd.models import run_command

@fcmd.tool("gittool", subcommand="a", help="git add + commit")
def add_commit(message: str, amend: bool = False) -> None:
    """添加并提交。"""
    cmd = ["git", "add", "-A"]
    run_command(cmd, check=True)
    cmd = ["git", "commit", "-m", message]
    if amend:
        cmd.append("--amend")
    run_command(cmd, check=True)
```

CLI 参数映射规则：

- 无默认值参数 → 位置参数（`message` → `message`）
- 有默认值参数 → `--name` 选项（`amend: bool = False` → `--amend`）
- `bool = False` → `--name`（store_true）
- `bool = True` → `--no-name`（store_false，保留原参数名）
- `X | None` / `Optional[X]` → 自动解包为 `X`（兼容 Python 3.9 PEP 604 字符串注解求值）

### `cmd` / `aggregate` 任务

无需 Python 函数，直接用命令或聚合依赖：

```python
@fcmd.tool("mytool", subcommand="build", cmd=["uv", "build"], help="构建")
def _build() -> None: ...  # 函数体可空，cmd 优先执行

@fcmd.tool("mytool", subcommand="all", needs=["build", "test"], strategy="dependency")
def _all() -> None: ...  # aggregate：仅声明依赖
```

## YAML 编排

GitHub Actions 风格的 `jobs`/`needs`/`matrix`/`if`：

```yaml
# deploy.yaml
strategy: thread              # 图级默认策略
defaults:
  retry: {max_attempts: 3}
  timeout: 300
  env: {CI: "true"}

jobs:
  setup:
    cmd: ["git", "clone"]

  build:
    needs: [setup]
    cmd: ["uv", "build"]
    timeout: 120

  deploy:
    needs: [build]
    run: "twine upload dist/*"
    env: {TWINE_TOKEN: "..."}
    continue-on-error: true

  notify:
    needs: [build]
    if: "failure()"           # 仅 build 失败时执行
    cmd: ["python", "-m", "notify"]

  test:
    matrix:                    # 笛卡尔积展开为 4 个任务
      py: ["3.9", "3.10"]
      os: ["linux", "windows"]
    cmd: ["pytest"]
```

执行：

```bash
fcmd yaml deploy.yaml                  # 执行全部 jobs
fcmd yaml deploy.yaml build            # 仅执行 build 及其依赖
fcmd yaml deploy.yaml --dry-run        # 打印执行计划不执行
fcmd yaml deploy.yaml --strategy thread  # 覆盖执行策略
```

或 Python API：

```python
from fcmd.orchestration.yaml_loader import load_yaml
from fcmd.engine.executors import run

graph = load_yaml("deploy.yaml")
report = run(graph, strategy="dependency")
```

## 命令定义 DSL

exec 型简单命令无需编写 Python 模块，用 TOML 声明即可注册为工具。两种形态：**单命令**（`[commands.<name>]`，如内置的 `clr`）与**多子命令**（`[commands.<tool>.<sub>]`，如内置的 `pymake`/`gittool`/`autofmt` 纯 exec 子命令）。两级配置：包内 `fcmd/commands/*.toml`（出厂命令，按工具拆分为多个 TOML 文件）与 `${FCMD_HOME:-~/.fcmd}/commands.toml`（用户自定义，同名可覆盖内置单命令；与 Python 模块工具重名时 Python 优先，多子命令形态逐子命令合并注册——同名子命令先注册者优先，用户仅可新增子命令）。

```toml
# ~/.fcmd/commands.toml
[commands.hello]
help = "向某人问好"
cmd = "echo 你好 {name} x{count}"
cwd = "/tmp"                     # 可选：工作目录（支持 {参数名} 插值）
timeout = 30                     # 可选：超时秒数
env = { LANG = "zh" }            # 可选：环境变量（值支持插值）
message = "同步完成: {name}"       # 可选：执行成功后打印的完成消息（支持插值，dry-run 不打印；目标被 when 守卫跳过时不打印）
fail_message = "同步失败"          # 可选：执行失败后打印的失败消息（支持插值，如修复提示）

[commands.hello.args.name]       # 无 default → positional 参数
help = "目标名字"

[commands.hello.args.count]
type = "int"                     # str(默认)/int/float/bool/path/choices/list
default = 1                      # 有 default → --count 选项（list 无 default → positional 多值）
help = "次数"

[commands.publish]               # {参数名:content} 文件内容插值：读取文件内容（展开 ~，utf-8，去首尾空白）替换进 cmd
help = "发布文件备注"
cmd = ["notes-cli", "publish", "--text", "{note:content}"]
[commands.publish.args.note]
type = "path"                    # content 插值仅支持 type=str/path 参数
default = "~/note.txt"

[commands.pkg.install]           # list 参数：独占占位符按元素展开（无 shell 每元素一个 token）
help = "批量安装"
cmd = ["pip", "install", "{packages}"]
[commands.pkg.install.args.packages]
type = "list"
help = "包名列表"

[commands.fmttool.lint]          # bool 参数 on：值为真时向 cmd 尾部追加固定 token
help = "检查并可选自动修复"
cmd = ["ruff", "check", "{target}"]
[commands.fmttool.lint.args.target]
type = "str"
default = "."
[commands.fmttool.lint.args.fix]
type = "bool"
default = false
on = ["--fix", "--unsafe-fixes"]

[commands.deploy.build]           # when 守卫：探针不满足 → SKIPPED（命令退出码 0）
help = "有代码变更才构建"
cmd = ["make", "build"]
when = {cmd = "git status --porcelain", expect = "nonempty"}  # 探针 cmd/path 二选一，支持 {参数名} 插值
allow_upstream_skip = true        # 上游被跳过时豁免执行（默认连坐跳过）

[commands.backup.files]           # 探针 expect：cmd 用 nonempty/empty/success/failure，path 用 exists/missing
help = "目录已备份则跳过"
cmd = ["robocopy", "{source}", "{dest}", "/mir"]
when = {path = "{dest}", expect = "missing"}  # path 探针支持插值与 ~ 展开

[commands.deploy.all]             # 聚合可声明 args：值经共享变量流入子任务插值
help = "构建并部署"
needs = ["build"]
[commands.deploy.all.args.tag]
type = "str"
default = "latest"

[commands.dockercmd.login]        # tty 透传：stdout/stderr 不捕获（交互式命令）
help = "登录 Docker 镜像仓库"
cmd = ["docker", "login", "--username", "{username}", "{registry}"]
tty = true                        # 密码提示可见可输入
message = "已登录镜像仓库: {registry} (用户: {username})"
[commands.dockercmd.login.args.username]
default = ""
default_env = ["USERNAME", "LOGNAME", "USER", "LNAME"]  # 值等于 default 时按链取第一个非空环境变量

[commands.setenv]                 # action 原语：进程内动作（fn 任务，非 subprocess）
help = "设置环境变量"
action = "setenv"                 # 内建动作名；参数 schema 来自动作实现签名（禁声明 args）
message = "环境变量 {name} 已设置"

[commands.pkg.u]                  # compute 计算原语：采集→计算→注入 exec 参数（输出管道）
help = "卸载包（支持通配符）"
cmd = ["pip", "uninstall", "-y", "{expanded}"]
[commands.pkg.u.args.packages]
type = "list"
[commands.pkg.u.compute]
action = "pip_expand"             # 计算动作：输入按实现签名取 CLI 变量，返回值注入 {expanded}
as = "expanded"                   # 空结果（空 list/空串）→ SKIPPED；同链同名只计算一次

[commands.sync]
help = "同步（跨平台示例）"
win.cmd = "robocopy src dst /mir"   # 字符串 → shell 执行
unix.cmd = ["rsync", "-a", "src/", "dst"]  # 数组 → 无 shell（注入面最小）
aliases = ["sy"]

# 多子命令形态：与 Python 模块同名的工具逐子命令合并注册
[commands.mytool.go]
help = "执行构建"
cmd = ["make", "build"]

[commands.mytool.all]
help = "聚合（needs 编排，无 cmd）"
needs = ["go"]
strategy = "thread"              # sequential/thread/async/dependency
```

```bash
fcmd hello world --count 3        # echo 你好 world x3
fcmd sy                           # 别名调用
fcmd mytool all                   # 聚合：先 go 后 all（thread 并行）
```

能力边界：单命令与多子命令 exec 形态（平台分支 / 参数（含 list 多值、bool on 固定 token 与 default_env 环境回退链） / 插值（含 `{参数名:content}` 文件内容插值） / cwd / env / timeout / when 守卫探针（支持参数插值与返回码判定）/ needs 聚合（含 allow_upstream_skip 豁免连坐）/ 聚合 args 共享插值 / tty 透传 / message 与 fail_message post-run 消息 / strategy / action 内建动作原语（进程内 fn 任务，参数 schema 来自动作实现签名）/ compute 计算原语（进程内计算动作返回值注入 exec 参数，输出管道形态））；matrix/if 条件编排属 YAML 编排的领地。

### DSL 逻辑边界

判断一段命令逻辑能否迁入 DSL：

| 类别 | 判定 | 例子 |
|------|------|------|
| 纯 exec（含 when 守卫与聚合编排） | 可迁 DSL | `gittool a/i`（守卫链 `_init`/`_add`/`_commit`）、`piptool d/f`、`dockercmd login`（tty 透传 + default_env 环境回退）、`sshcopyid`（when 探针 + env 插值传 SSHPASS + `{参数名:content}` 公钥内容）、`reseticoncache`（win shell 链 + unix 提示分支） |
| 输出管道（采集→计算→注入 exec 参数） | 可迁 DSL（compute 计算原语） | `piptool u/r`（`pip list` 采集 + 通配符展开/受保护包过滤注入 uninstall/install 参数；r 经 `_uninstall`/`_install` 隐藏链编排） |
| 输出管道（计算主体为进程内逻辑，输出解析仅子步骤） | 保留 Python | `portcheck`（socket 探测主体 + lsof/netstat 占用查询回显） |
| 动态遍历（运行时枚举文件系统） | 保留 Python | `gittool isub`、`envdev` 系列 |
| 进程内副作用（无法映射为子进程） | 副作用型动作可迁 DSL（action 原语） | `setenv`/`writefile`/`filedate`/`filerename`/`filelevel`/`folderback`/`taskkill`/`which`/`sysinfo`（已迁入 action 原语；taskkill 逐条目过程回显 + 绝对路径防递归，which 逐条查找回显，sysinfo 诊断信息打印，均无返回值消费）；`pathtool`/`filesearch`/`nettool`/`iptool` 产出计算结果（路径解析计算/搜索结果/HTTP 响应/IP 解析），非副作用型动作，保留 Python |
| 多步有状态流程 | 保留 Python | `bumpversion`、`packtool` |

## 执行策略

四种策略通过 `fx.run(graph, strategy=...)` 或 `fcmd yaml --strategy ...` 指定：

| 策略 | 说明 |
|------|------|
| `sequential` | 拓扑序串行执行，简单可预测 |
| `thread` | 同层任务用线程池并行（适合 I/O 密集） |
| `async` | 同层任务用 asyncio 并发（适合异步 I/O） |
| `dependency`（默认） | 仅按依赖关系调度，同层并行，兼顾效率与正确性 |

## 开发

```bash
git clone <repo-url>
cd fcmd
pip install -e fcmd[dev]      # 安装开发依赖（lint + test + office + prek + tox）

fcmd pymake tc                # 类型检查（pyrefly + ruff）
fcmd pymake t                 # 运行测试
fcmd pymake cov               # 测试 + 覆盖率
fcmd pymake chk               # 类型检查聚合（清理 + pyrefly + lint + fmt + test_fast）
```

工具链独立配置文件：`ruff.toml` / `pyrefly.toml` / `pytest.ini` / `.coveragerc` / `.bumpversion.toml` / `uv.toml` / `.pre-commit-config.yaml`。`pyproject.toml` 仅含项目元数据。

新增工具：在 `src/fcmd/cli/` 下新建模块（非 `_` 前缀、非 `main`），用 `@fcmd.tool` 装饰器注册即可，无需修改 `main.py`。可选在模块内定义 `__tool_aliases__: list[str]` 声明别名。

## 许可证

MIT
