← Back to home@imcaibaicai

outbox

Reliably push a local folder to GitHub: per-command proxy injection, self-healing gh auth, pre-push secret gate, and SHA read-back verification. Zero dependencies.

Stars
0
Language
JavaScript
Created
Oct 11, 2026
Updated
Oct 11, 2026

Introduction

outbox

把本地目录可靠地送上 GitHub。一句话,不翻车。

English abstract · MIT

outbox 把「推送」从一个每次都要现场拼命令的临时动作,变成一条确定性的流水线:

代理自动探测 → gh 登录态自愈 → 推送前安全闸门 → 建仓 / 提交 / 推送 → 远端 SHA 回读校验

一份内核,三种用法:命令行、任何 Agent(读 SKILL.md)、DeepSeek Harness 插件(dsh-github-push)。 三种外壳共用同一份代码,不存在「插件和 CLI 行为不一致」的问题。


它到底解决了什么

推送失败几乎从来不是因为 git push 写错了,而是因为周围那些没人管的事。下面是真实踩过的坑,以及 outbox 的处理方式:

真实故障为什么会发生outbox 怎么处理
gh 突然报「未登录」,可 git push 明明好好的gh 把登录态存在自己的 hosts.yml 里,该文件会因版本升级/配置迁移被清空;但 token 一直躺在系统凭据管理器里检测到未登录时,从凭据管理器取出 token 自动灌回 gh,并重新校验
网络时通时不通、推送卡死环境变量里的代理并不是每条调用链都带着;代理关掉后变量还指着死端口每次先探测端口,通则按次注入代理、不通则只清掉指向该端口的陈旧变量后直连
推着推着要输密码,挂在半路git 交互式询问子进程一律置 GIT_TERMINAL_PROMPT=0,宁可清清楚楚失败,也不静默挂起
密钥被推上公开仓库没人会在推送前逐行看 diff推送前扫描即将提交的索引,命中高危内容直接阻断
命令返回 0,但推的是旧东西退出码不等于结果推送后回读远端分支,要求远端 SHA 严格等于本地 HEAD 才算成功
仓库里写死了本机用户目录的绝对路径无意识泄露身份扫描绝对路径中的用户名、身份证号、手机号等,命中后禁止自动公开

安装

零依赖,只要 Node ≥ 18。

git clone https://github.com/<your-account>/outbox.git
cd outbox
node src/cli.mjs --help

用法

# 先看一眼会发生什么(只做预检,不提交不推送)
node src/cli.mjs --dir ./my-project --visibility private --dry-run

# 发布到私有仓库(仓库不存在会自动创建)
node src/cli.mjs --dir ./my-project --visibility private --description "我的项目"

# 明确要开源时才用 public
node src/cli.mjs --dir ./my-project --visibility public

常用选项:

选项说明
--dir <路径>要发布的目录(默认当前目录)
--name <仓库名>远端仓库名(默认由目录名规整而来)
--visibility <值>public / private / ask(默认 ask)
--proxy <值>auto / off / http://host:port(默认 auto,探测 7890)
--proxy-port <端口>代理探测端口,默认 7890
--dry-run只做预检
--json结构化输出,便于脚本与 Agent 消费

退出码:0 成功 · 1 出错 · 2 预检阻断 · 3 需要你先做决定

安全闸门

扫描的对象是索引(git ls-files --cached),也就是真正会被推上去的东西——因此 .gitignore 覆盖的文件天然不会被误报。

命中结果分三级:

级别含义行为
block高置信度凭据:GitHub/OpenAI/AWS 令牌、私钥文件内容、.env/*.pem/id_rsa 等文件名、超过 95 MiB 的文件硬阻断,远端不会被创建
confirm疑似个人敏感信息:身份证号、手机号、学号字段、暴露本机用户名的绝对路径,以及你自己的自定义模式禁止自动公开,必须逐条确认
warn疑似明文口令/密钥赋值、超过 10 MiB 的大文件、已被 gitignore 覆盖却仍在版本控制中的残留只提示,不阻断

补充自定义模式:在 ~/.outbox/patterns.json 写

{ "confirm": [{ "id": "my-secret", "label": "我自己的敏感词", "pattern": "公司内部代号" }] }

放行清单

任何带测试夹具的仓库都会天然含有假密钥——测试扫描器就得放几个假 token 进去。没有出口的话,这种仓库会被自己的闸门拦住(outbox 第一次推送时,确实拦住了它自己)。

两种放行方式:

仓库级:在仓库根目录写 .outboxallow,每行一条:

# <路径 glob> [规则 id]
tests/selftest.mjs    github-token
tests/**/*.fixture.js

省略规则 id 表示放行该路径下的全部规则,但建议永远写明确则 id——放行范围越小越容易审计。这个文件本身会随仓库一起提交,所以「谁放行了什么」永远可查。

行内:在同一行写上 outbox:allow,该行的内容规则全部跳过。

被放行的命中不会凭空消失,而是记入结果的 allowlisted 字段,依旧可见。

设计取舍

为什么代理不写进 git 全局配置? 写全局最省事,但一旦代理软件关掉,本机所有 git 操作(包括别的仓库)都会连不上。outbox 只在本次命令上用 -c http.proxy=... 注入,不留后遗症。

为什么不用 where.exe 定位 git/gh? Windows 控制台工具按 OEM 代码页输出,而 Node 按 UTF-8 解码。只要可执行文件路径含中文(例如用户名是中文),解析结果就会变成一串替换字符,随后 spawn 报 ENOENT。更阴险的是 Git 常装在纯 ASCII 路径下所以一切正常——这个 bug 只在中文用户名的机器上爆发。正确做法是把裸名字交给 Windows 自己按 PATH 解析。

为什么推送成功的判据是 SHA 而不是退出码? git push 返回 0 只代表命令没报错,不代表远端真的有你要的那个提交。回读 git ls-remote 拿到远端分支 SHA、与本地 HEAD 严格比对,才是可信证据。

为什么安全闸门扫描索引而不是目录? 索引就是「将要被推送的内容」,天然排除了忽略项,不会对 .gitignore 里的东西误报。

作为 Agent Skill 使用

SKILL.md 是给任意 Agent(Claude Code / Codex / DSH / 其它)读的说明书。把它放进你的技能目录,或直接贴进系统提示,Agent 就知道什么时候该调 outbox、什么时候必须先问用户。

相关项目

  • dsh-plugin/ —— 同一份内核的 DSH 工具壳,注册模型工具 github_push,让「一句话推送」真正落地。源码就在本仓库里;它同时单独发布为一个 dsh-github-push 仓库,以便被 DSH 插件市场收录。两处的内核副本由 scripts/sync-plugin.mjs 机械同步,不存在第二份手写实现。
  • dsh-plugin-publish —— 面向 Agent 技能的发布流程协议(preflight → 建仓 → awesome 列表 PR → 市场提交)。它是「说明书」,outbox 是「代码内核」,两者互补。

English abstract

outbox reliably pushes a local folder to GitHub in a single command. It wraps the parts that actually break: auto-detecting a local proxy (default port 7890) and injecting it per command instead of globally, self-healing a gh login by recovering the token from the OS credential store, gating the push behind a secret/PII scan of the staged index, and verifying success by reading back the remote branch SHA instead of trusting an exit code. Zero dependencies, Node ≥ 18. One core, three shells: CLI, agent skill, and a DeepSeek Harness plugin.

License

MIT