kishormorol/nightaudit

GitHub: kishormorol/nightaudit

一款利用闲置 AI CLI 订阅在夜间自动执行只读代码审查并生成每日摘要的自动化工具。

Stars: 25 | Forks: 1

# nightaudit **审计并不会改变账本。** 让你闲置的 Claude Code 或 Codex 订阅发挥作用——在你忙碌时对你的项目进行只读审查,每天早晨提供一份摘要。 ![该仓库的一段真实 code_review:两个 HIGH 发现,随后是早晨的摘要](https://static.pigsec.cn/wp-content/uploads/repos/cas/70/70a7ac4ae00a5c4ba61e86933a5365031d916aafdf37ee7d6a222615dc4712ed.svg) [![PyPI](https://img.shields.io/pypi/v/nightshift-cli)](https://pypi.org/project/nightshift-cli/) [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![python](https://img.shields.io/badge/python-3.10%2B-blue)](pyproject.toml)
## 这是什么 你已经在为 Claude Code 或 ChatGPT 付费。那个订阅在一天中的大部分时间,且毫无疑问在整个晚上,都处于闲置状态。nightaudit 利用这些闲置时间审查你指定的项目,并将结果留在一份 Markdown 文件中,让你在喝咖啡时阅读。 两种 CLI 都可以——Claude Code 或 Codex,取决于你拥有哪一个。如果两者都有,它会同时使用它们,且各自有独立的预算。 它绝不编辑你的代码。它没有 daemon 也没有 server——由 cron 调用它,它自行决定是否运行,然后继续休眠。它所知道的一切都存在于两个你可以随时删除的目录中。 这就是该工具的全部。本页面的其余部分是关于如何使用它。 ## 开始之前 - **Python 3.10+** - **至少一个 AI CLI**,已安装并已登录——可以是 [Claude Code](https://claude.com/claude-code) 或 [Codex](https://developers.openai.com/codex)。运行 `claude --version` 或 `codex --version`;如果该命令有效,nightaudit 就能找到它。如果两者都有,它会同时使用它们,且各自有独立的预算。 - **cron**——macOS 和 Linux 上的标准配置。Windows 可通过 WSL 使用。 你不需要 API key。nightaudit 驱动着你手动使用的相同 CLI,使用的是你已有的订阅。 ## 安装 ``` pipx install nightshift-cli nightaudit --version ``` 这不是拼写错误。除了安装行之外,该工具在任何地方都叫 `nightaudit`:在改名之前,它以 `nightshift-cli` 的名称发布,而 PyPI 无法在不抛弃所有使用旧名称用户的情况下重命名项目。因此,包保留了旧名称,而命令获得了新名称。你今天只需输入一次 `nightshift-cli`,以后再也不用了。 (`uv tool install nightshift-cli` 和 `pip install nightshift-cli` 也可以; pipx 和 uv 只是让它不干扰你的其他环境。无论哪种方式,命令都是 `nightaudit`。) **从 0.3.0 版本升级(当时它被称为 nightshift)?** `pipx upgrade nightshift-cli` 然后继续使用。你的配置、预算历史和队列会在它们原本所在的 `~/.nightshift` 中被读取,旧的 `nightshift` 命令仍然有效,因此你现有的 crontab 会继续运行——它会打印一条通知并完成任务。但这并非永久有效:在方便时运行 `nightaudit init`,它会就地重写旧的 cron 块。该别名将在 1.0 版本中移除。要随时移动状态,可以使用 `mv ~/.nightshift ~/.nightaudit`——它会在下次运行时被自动识别。 ## 设置 运行一次 `nightaudit init`。它会查找你的 AI CLI,询问要审查哪些项目以及何时审查,写入配置文件,并提议安装驱动一切的 cron 行: ![nightaudit init 引导你完成检测、项目、调度和 cron 设置](https://static.pigsec.cn/wp-content/uploads/repos/cas/71/7104c05fde4da2a8ad7b1dff615480e128166b4b65ae55aaa2e961c8f4664805.svg) 项目路径是它唯一需要你提供的内容。其他所有内容都有可以通过按回车键(Enter)接受的默认值: | 它询问的内容 | 含义 | 默认值 | | --- | --- | --- | | **project path** | 要审查的 repo。你可以根据需要输入任意多个;空行表示结束。 | — | | **name** | 在摘要中如何称呼它。 | 目录的名称 | | **tasks** | 要对它运行哪些审查。 | `code_review, security_audit, deps_audit` | | **windows** | 允许运行的时间段,本地时间。 | `00:00-06:00` | | **idle minutes** | 你必须首先离开 Claude Code 多长时间。 | `60` | | **digest dir** | 早晨摘要存放的位置。 | `~/nightaudit-reports` | 它写入的所有内容都会进入 `~/.nightaudit/config.yaml`。你可以随时手动编辑它——它是纯 YAML,并且 `nightaudit status` 会对它进行验证。 ## 查看运行情况 不要等到今晚。现在就强制运行一次: ``` nightaudit run --now ``` `--now` 会跳过窗口和闲置检查,让你立即获得审查结果。在终端中,nightaudit 会实时流式传输——你会看到与你亲自运行 `claude` 时相同的读取和推理过程: ![一次真实的 nightaudit 运行:读取、推理,然后是七个按严重程度排序的发现](https://static.pigsec.cn/wp-content/uploads/repos/cas/76/762f4df896ea893006ad863fe63111a9666de1a2d1ef51abec98afc7e5848a0a.svg) 那是 nightaudit 针对其自身仓库的一次真实运行,而那些都是真实的 bug。其中两个在同一天晚上变成了提交:一个可能导致永远挂起并持有调度器锁的运行 (`claude_code.py:366`),以及一个可能被错误所有者释放的锁 (`lock.py:121`)。 发现按 🔴 HIGH、🟠 MED、🟡 LOW 排序,每个发现都引用了 `file:line`,以便你可以直接跳转到那里。 ## 然后忘了它 如果你让 `init` 安装了 cron 行,你就已经完成了。Cron 每小时调用一次 `nightaudit run`;该命令会自行决定是否采取行动,并在答案为“否”时静默退出: ``` 0 * * * * ~/.local/bin/nightaudit run >> /tmp/nightaudit-cron.log 2>&1 30 7 * * * ~/.local/bin/nightaudit digest >> /tmp/nightaudit-cron.log 2>&1 ``` 每小时运行的行是受限的;07:30 的行用于渲染昨天的发现。 `init` 会写入二进制文件的绝对路径,因为 cron 运行时的 `PATH` 极有可能不包含你的路径,并且会将两个流重定向到日志中,因为 cron 的输出无处可去。 只有当**所有四个门**都打开时,才会发生运行: 1. **Window**——时钟位于你 `schedule.windows` 的其中一个时间段内。 2. **Idle**——你已经有一段时间(`idle_minutes`)没有触碰 Claude Code 了。nightaudit 会监视 `~/.claude/projects` 并在你工作时避开你。 3. **Budget**——你今天和本周还有剩余的运行次数。 4. **Lock**——没有其他运行正在进行中。 任何一个门说“不”都是正常的,而不是错误。它会打印一行并以 exit 0 退出: ``` $ nightaudit run nothing to do — outside configured windows (00:00-06:00) ``` 每次运行都会从一个持久化的轮询队列中弹出一个 `(project, task)` 对, 因此每个项目都会轮到,而一个嘈杂的项目不会让其他项目挨饿。 要随时检查它的状态,请问: ![nightaudit status:预算条、下一个窗口、接下来要做什么、最近的运行](https://static.pigsec.cn/wp-content/uploads/repos/cas/62/6277391e07b3c094b323e25e3a476c1310f09016f4caef9ca1b6daefa1ac168b.svg) 并且要查看由 cron 启动的运行——包括已经在进行中的运行—— `nightaudit watch` 会实时跟随,并首先重放上次完成的运行。 ## 阅读摘要 每次运行都会追加到 `~/nightaudit-reports/YYYY-MM-DD/`。每天一次, `nightaudit digest` 会将它们渲染成一个文件,`DIGEST-YYYY-MM-DD.md`: ``` # Nightaudit · 晨间摘要 Wed Jul 15, 2026 · generated 17:57 local · 1 project · 1 run ## 剩余预算 - `claude_code` ▓░░░░░ 1/6 today · 1/30 week ## 重点 - 🔴 Replace the buffered `subprocess.run(..., timeout=...)` with the same `Popen` + `os.killpg` treatment the streaming path uses … — _nightaudit · code_review_ · `nightaudit/adapters/claude_code.py:267` - 🟠 Have `release()` re-read the lockfile and unlink only when the recorded pid is still our own … — _nightaudit · code_review_ · `nightaudit/lock.py:121` ## 运行日志 | project | task | provider | status | dur | time | | --- | --- | --- | --- | --- | --- | | nightaudit | code_review | claude_code | ok | 2m18s | 15:23 | ``` 最高严重程度的排在前面,按项目分组,二十秒内即可读完。 **[这里是该摘要的完整内容](docs/sample-digest.md)**——是真实的,而不是 模拟的。 跳过和失败的运行会保留在日志中。一次没有发生的运行也是信息,静默丢弃它会让你对该工具失去信任。 想提前获取,或者获取特定某一天的? ``` nightaudit digest # today, written to the digest dir nightaudit digest --date 2026-07-14 # a past day nightaudit digest --stdout # print it instead of writing it ``` ## AI 触碰 0 个文件 nightaudit 的全部价值取决于能否安全地无人值守运行,因此 在这里只读不仅仅是文档中的承诺——它是在实际执行工具的层面上被强制执行的。 本节是关于 AI 的,标题是故意这么说的。如果你配置了 [checks](#checks),那些是你自己的命令,它们以你的身份运行——请参阅 下文。 Claude Code 适配器使用其自身的权限标志调用 CLI: ``` claude --print "" --output-format json --allowed-tools Read Grep Glob NotebookRead --disallowed-tools Bash Edit MultiEdit Write NotebookEdit WebFetch WebSearch Task ``` 因此,有三件事是真实的: - **白名单是整个工具的预算。** Claude Code 无法调用不在列表中的工具。 没有 Edit,没有 Write,没有 shell。 - **黑名单是双重保险。** 它的存在是为了防止未来的 CLI 版本向默认值添加新的具备写入能力的工具,从而悄无声息地扩大 nightaudit 的能力范围。 - **没有后备方案。** 如果标志被拒绝,运行就会失败并被记录为失败。 nightaudit 从不重试不受限制的调用。 提示词也强化了这一点,但提示词不是安全边界,也不被视为安全边界。 标志才是。 nightaudit 也绝不触碰你的 git 状态:没有提交,没有分支,没有 推送。顺其自然的话,它只会读取,并且只在一个地方写入——摘要目录。 ## 检查 一个 task 是给一个无法写入的 AI 的提示词。一个 **check** 是你自己的命令,nightaudit 会运行它: ``` projects: - name: gradagent tasks: [code_review] checks: - name: tests run: pytest -q - name: lint run: ruff check . timeout_s: 30 ``` 每一个都会在审查之前在项目目录中运行,并连同其退出码和输出的末尾部分进入摘要: ``` ### gradagent #### 检查 - ✗ `tests` — `pytest -q` · exit 1 ``` 3 个失败,128 个通过 ``` - ✓ `lint` — `ruff check .` · exit 0 ``` **检查是在沙箱之外的,这正是它的意义所在。** AI 被它无法辩驳的标志限制为只读。你的检查是你的命令,以你的权限运行:`pytest` 会写入 `.pytest_cache/` 是因为你让它这么做的。nightaudit 不对它进行沙箱化,也不假装这么做。 在添加之前,有两件事值得了解: - **没有 shell。** 命令被拆分为参数并直接运行,因此 `&&`、管道、`*` 和 `$(...)` 不会被解释——`run: rm -rf $HOME` 会将四个字符 `$HOME` 传递给 `rm`。如果你想要一个管道,可以将检查指向一个脚本。 - **`config.yaml` 现在可以执行命令了。** 在检查功能出现之前,它是惰性数据;任何能写入它的东西现在都可以从 cron 以你的身份运行命令。这是让配置文件运行测试的固有特性。`~/.nightaudit/prompts/` 从未获得这种特性——提示词仅仅是交给模型的文本。 失败、超时或指定了未安装程序的检查会被报告,仅此而已。它绝不会导致审查失败——审查才是你正在等待的东西。 ## 不要让它烧光你的配额 nightaudit 在你已付费的订阅上运行,这意味着它成为问题的最快方式就是烧光你的配额。因此,它会进行统计。 ``` providers: claude_code: enabled: true budget: max_runs_per_day: 6 max_runs_per_week: 30 ``` - **每一次尝试都算数**——包括失败和超时。它们消耗了你的配额,因此它们会消耗预算。只计算成功会让一个损坏的项目在一个死循环中耗尽账户。 - **两个上限都有约束力。** 低于每日上限但达到了每周上限?它会停止。 - **`--now` 会跳过窗口和闲置检查,绝不跳过预算检查。** - **达到上限时,它会停止并说明情况**,一次,作为摘要中的一个 `skipped` 行。 从低开始。每天六次运行已经有很多审查了。 ## 配置 存在于 `~/.nightaudit/config.yaml`。这里是它的全貌——这是所有的配置项: ``` providers: claude_code: enabled: true budget: { max_runs_per_day: 6, max_runs_per_week: 30 } codex: enabled: true budget: { max_runs_per_day: 6, max_runs_per_week: 30 } # Optional. Only needed when the CLI isn't on PATH under its own name — # e.g. the Codex bundled inside ChatGPT.app: binary: /Applications/ChatGPT.app/Contents/Resources/codex projects: - name: gradagent path: ~/projects/gradagent tasks: [code_review, deps_audit, docs_drift] - name: nightaudit path: ~/projects/nightaudit tasks: [code_review] # Optional. Pin this project to one provider. Without it, whichever enabled # provider is idle and under budget takes the project. provider: codex # Optional. Your own commands, run before the review. Unlike a task, these # are NOT sandboxed — see "Checks". checks: - name: tests run: pytest -q timeout_s: 120 schedule: windows: ["09:00-18:00", "00:00-06:00"] # local time; may cross midnight idle_minutes: 60 digest: dir: ~/nightaudit-reports run: timeout_s: 600 ``` 编辑后运行 `nightaudit status`——它会验证文件,并在有任何问题时准确告诉你哪里出错了。 设置 `NIGHTAUDIT_HOME` 以将整个状态目录移动到其他地方。 ## 任务 一个 task 就是一个提示词模板。nightaudit 附带五个: | task | 它寻找什么 | | --- | --- | | `code_review` | Bug、竞态条件和正确性问题 | | `security_audit` | 注入、授权缺失、不安全的默认值 | | `deps_audit` | 未固定的、过时的或有风险的依赖项 | | `docs_drift` | 不再与代码匹配的文档 | | `dead_links` | 指向不存在的事物的链接和图片路径 | 为每个项目分配适合它的任务——一个 Terraform repo 可能需要 `security_audit` 和 `deps_audit`,而不是 `dead_links`。 **编写你自己的:** 将任何 `.md` 文件放入 `~/.nightaudit/prompts/`,它的文件名就会成为一个有效的任务名称。使用内置的名称可以覆盖该模板。 模板必须告诉模型在每个发现前面加上 `HIGH`、`MED` 或 `LOW` 的前缀,并引用 `file:line`。解析是很宽容的——未标记的发现会被保留并归档为 `LOW`,而不是被丢弃。 ## 命令 | 命令 | 作用 | | --- | --- | | `nightaudit init` | 检测 CLI,注册项目,写入配置,提议安装 cron | | `nightaudit run [--now]` | 一次受限的运行。`--now` 跳过窗口+闲置检查 | | `nightaudit watch [-n N]` | 实时跟随运行,包括由 cron 启动的运行 | | `nightaudit digest [--date]` | 渲染 `DIGEST-YYYY-MM-DD.md` | | `nightaudit status` | 预算条、最近的运行、下一个窗口、provider 健康状况 | 每个命令都支持 `help`。 ## 故障排除 **`nothing to do — outside configured windows (00:00-06:00)`** 设计如此——它不在窗口期内。可以使用 `nightaudit run --now` 强制运行,或者扩大 `schedule.windows`。 **`nothing to do — claude_code used 12m ago (needs 60m idle)`** 你一直在使用 Claude Code,所以 nightaudit 正在避开你。可以使用 `--now`,或者降低 `idle_minutes`(`0` 表示禁用检查)。 **`nothing to do — claude_code: daily budget spent (6/6 today)`** 今天的配额用完了。如果想要更多,可以调高 `max_runs_per_day`。 **``No usable AI CLI found. Install Claude Code or Codex and re-run `nightaudit init`.``** 你的 `PATH` 中没有任何 CLI。在相同的 shell 中检查 `claude --version` / `codex --version`。 **Cron 从不运行它。** Cron 使用最小化的 `PATH`,这就是为什么 `init` 会将二进制文件的绝对路径写入你的 crontab。检查 `/tmp/nightaudit-cron.log`——cron 运行的所有内容都记录在那里。在 macOS 上,cron 可能还需要“完全磁盘访问权限”才能读取你的项目。 **运行卡住了。** 运行会在 `run.timeout_s`(默认为 600 秒)时被终止,并记录为 `timeout`。不需要手动清理任何东西。 ## 卸载 nightaudit 在其他任何地方都不保留状态,因此移除它只需三行: ``` crontab -e # delete the block (see below) rm -rf ~/.nightaudit # config, ledger, queue, event logs pipx uninstall nightshift-cli ``` 如果你最初是在 0.4.0 之前安装的,你的状态可能仍然在 `~/.nightshift` 中——nightaudit 会在那里读取它,而不是强迫你移动它。`nightaudit status` 会打印它实际使用的目录,那才是你要删除的目录。 `init` 将它的 crontab 行围在两个标记之间——删除它们以及它们之间的所有内容: ``` # nightaudit(托管 — 通过 `nightaudit init` 编辑) ... # 结束 nightaudit ``` `~/nightaudit-reports` 中的摘要属于你——删不删由你决定。 ## Providers 调度器、账本、队列和摘要都与 provider 无关。启用你拥有的任何 CLI;每个都有独立的预算。 | provider | 状态 | 如何强制只读 | | --- | --- | --- | | `claude_code` | 可用 | CLI 权限标志——一个只读类工具的白名单,加上每个可变工具的黑名单 | | `codex` | 可用 | Codex 自身的 OS 沙箱——macOS 上的 Seatbelt,Linux 上的 Landlock + seccomp | | `copilot` | 桩代码 | — 见下文 | 一个硬性要求,也是最后一列存在的全部原因:**只读保证必须由 CLI 自身的权限系统强制执行**,而不是通过善意地请求模型来实现。做不到这一点的适配器不会被合并,因为“AI 触碰 0 个文件”是整个产品的核心。 **Copilot:需要帮助,但被上游阻碍。** 它以有文档记录的桩代码形式提供 (`nightaudit/adapters/copilot.py`)。障碍不在于工作量——而在于 Copilot CLI 没有能够达到该标准的强制执行原语。它的文件级拒绝不能跨工具应用,所以 `shell(cat x)` 会绕过被拒绝的 `read(x)`,而且[一个未解决的问题](https://github.com/github/copilot-cli/issues/2722) 报告称 `--deny-tool="read(...)"` 会*无视模式*阻止*所有*读取。 一个工具遵守而另一个工具忽略的拒绝并不是权限系统。如果上游发生了改变,这个适配器只需一下午的工作量——`codex.py` 和 `claude_code.py` 都是参考模板。 ## 开发 ``` python -m venv .venv && .venv/bin/pip install -e ".[dev]" .venv/bin/pytest ``` 测试套件不消耗任何配额:调度器、预算、队列和摘要都是针对 `FakeAdapter` 进行测试的,而 Claude Code 适配器是用模拟的 `subprocess` 测试的。没有任何测试会通过 shell 调用真实的 AI CLI。 本页面上的图片是根据真实捕获的输出生成的——如果你更改了 CLI 打印的内容,请参阅 [docs/RECORDING.md](docs/RECORDING.md)。 ## 许可证 MIT
标签:AI编程助手, Python, 代码审查, 开发效率, 无后门, 网络可观测性, 逆向工具