{"_id":"@blurname/debrief","name":"@blurname/debrief","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@blurname/debrief","version":"0.1.0","description":"Debrief your Debian box: snapshot every installed package (plus loose non-dpkg binaries) and track what drifts","type":"module","bin":{"debrief":"dist/debrief.js"},"engines":{"node":">=18"},"repository":{"type":"git","url":"git+https://github.com/blurname/debrief.git"},"homepage":"https://github.com/blurname/debrief#readme","bugs":{"url":"https://github.com/blurname/debrief/issues"},"publishConfig":{"access":"public"},"scripts":{"start":"bun run bin/debrief.ts","test":"bun test","test:apt":"docker run --rm -v \"$PWD\":/app:ro oven/bun:debian bash /app/test/apt-e2e.sh","test:node":"bun run build && docker run --rm -e DEBRIEF_RUN='node /app/dist/debrief.js' -v \"$PWD\":/app:ro node:bookworm-slim bash /app/test/apt-e2e.sh","build":"bash scripts/build-js.sh","build:bin":"bash scripts/build-bin.sh","build:deb":"bash scripts/build-deb.sh","prepack":"bash scripts/build-js.sh","typecheck":"tsc --noEmit"},"keywords":["debian","apt","dpkg","package-manager","package.json"],"license":"MIT","devDependencies":{"@types/bun":"latest","typescript":"^5"},"_id":"@blurname/debrief@0.1.0","gitHead":"27336f794982fbb40c700940429143ab3e839275","_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-KKQBrQFaq2JK9Yt4Bahq+UM1CzSkYQTGJntvHTj5EEyUdVQ9kM4hjze66dwDFmikyZpuy+sq1irvjfI90zrK8Q==","shasum":"16463afa78b125f911657667d055c931a938500f","tarball":"https://registry.npmjs.org/@blurname/debrief/-/debrief-0.1.0.tgz","fileCount":4,"unpackedSize":50447,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHfbgzmFFw/+L3U5k2AfSEfmSN1urzQWvWIaMYUUSNwcAiB4Rxy2yE7h5nllJt4viVQ2DK5VXElfGC0z/I3IZrdEKA=="}]},"_npmUser":{"name":"blurname","email":"naughtybao@outlook.com"},"directories":{},"maintainers":[{"name":"blurname","email":"naughtybao@outlook.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/debrief_0.1.0_1783469291805_0.6472371370441266"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-08T00:08:11.696Z","0.1.0":"2026-07-08T00:08:12.031Z","modified":"2026-07-08T00:08:12.273Z"},"maintainers":[{"name":"blurname","email":"naughtybao@outlook.com"}],"description":"Debrief your Debian box: snapshot every installed package (plus loose non-dpkg binaries) and track what drifts","homepage":"https://github.com/blurname/debrief#readme","keywords":["debian","apt","dpkg","package-manager","package.json"],"repository":{"type":"git","url":"git+https://github.com/blurname/debrief.git"},"bugs":{"url":"https://github.com/blurname/debrief/issues"},"license":"MIT","readme":"# debrief\n\n***Debrief* 你的 Debian 机器——问它到底装了什么,并追踪它的变化。**\n\n（名字取自 `deb` + *debrief*「事后汇报」：把整台机器的安装情况汇报上来。）\n\n主线用法:**收集当前机器上装了什么,变成一份清单,然后追踪它的变化。** 软件你(或某个\nagent)照常用 apt 直接装,debrief 不负责装、负责\"看和管\"——`collect` 把系统快照成\n`debrief.json` + `debrief.lock`,`status` 告诉你系统相对快照又多了/少了/升级了什么。\n\n## 概念映射\n\n| npm | debrief |\n| --- | --- |\n| `package.json`(直接依赖) | `debrief.json` — 你**主动装**的包(`apt-mark showmanual`) |\n| `package-lock.json`(完整依赖树) | `debrief.lock` — **全部**已装包 + 确切版本(含依赖) |\n| `node_modules` | 系统 dpkg 状态(通过后端读写) |\n| npm registry | apt sources / Debian 镜像 |\n| `pip freeze` / `dpkg --get-selections` | `debrief collect` — 从系统反向生成清单 |\n| `git status` | `debrief status` — 系统当前状态 vs 快照 |\n| `npm install` | `debrief install` — 把清单里缺的装到另一台机器 |\n\n底层就是 Debian 原生的 `dpkg-query`(装了什么)+ `apt-mark showmanual`(哪些是你主动装的、\n而非被依赖带进来的)。后者恰好等价于\"直接依赖 vs 传递依赖\":主动装的进 manifest,依赖只进 lock。\n\n### 也能抓\"非 dpkg\"装的裸二进制\n\n有些软件根本不走 apt——`curl … | sh`、手动下载、brew/cargo 之类,直接把一个可执行文件丢到\nPATH 上(比如 [herdr](https://herdr.dev/docs/install/) 就是 `curl -fsSL https://herdr.dev/install.sh | sh`)。\n`collect` 会顺带扫 `/usr/local/bin`、`~/.local/bin` 等目录,对每个可执行文件用 `dpkg -S` 反查:\n**dpkg 不认领的,就是这类\"裸二进制\"**,记进 lock 的 `binaries`(含内容 `sha256`,所以自更新会被\n`status` 认出来;加 `--probe-versions` 还能跑 `--version` 取版本)。\n\n```\n$ debrief status\nnon-dpkg binaries (curl|sh / manual / brew / ...):\n+ herdr /usr/local/bin/herdr\n~ bun /usr/local/bin/bun (contents changed)\n```\n\n`--no-binaries` 关掉这个扫描;`--scan-dirs a:b:c` 指定要扫的目录。\n\n## 主线:收集 & 追踪\n\n```bash\ndebrief collect          # 把当前系统快照进 debrief.json(主动装的)+ debrief.lock(全部)\ndebrief collect --pin    # manifest 里记确切版本(而非默认的 \"*\")\ndebrief status           # 对比:系统相对上次 collect 多了/少了/升级了什么\ndebrief list             # 清单里的包及其状态、可升级情况\ndebrief outdated         # 有新版本可升级的包\n```\n\n典型场景:把 `debrief.json` + `debrief.lock` 提交进你的 dotfiles / 配置仓库。之后 agent 直接\n`apt install` 了什么新东西,`debrief status` 一眼就能看出来;确认无误就 `debrief collect` 重新快照。\n\n```\n$ debrief status\n+ ripgrep 14.1.0-1\n+ libpcre2-16-0 10.42-1 (dep)\n~ nginx 1.24.0-2 → 1.26.0-1\n- jq 1.6-2.1\n\n+2 added  ~1 changed  -1 removed (vs snapshot; run `debrief collect` to update it)\n```\n\n## 设计取向:友好封装(friendly wrapper)\n\n这是刻意选择的最保守语义:\n\n- **`install` 只增不改**:只安装 manifest 里*尚未安装*的包。已经装了的,即使版本不满足约束\n  (drift),也**只警告、绝不改动**。不做整机对齐(no reconcile),不会去卸载\"多余\"的包。\n- **`add` / `remove` 是显式动作**:`add` 会安装并写入 manifest;`remove` 会卸载并从 manifest 移除。\n- **lockfile 给你可复现性**:`install` 时若锁定的版本仍然可用且满足约束,就装*锁定的那个*\n  (输出里标 `↺`),而不是最新的——这样在另一台机器上 `install` 得到同样的结果。\n\n## 后端(可插拔)\n\n所有逻辑都只依赖一个 `Backend` 接口,因此同一套代码能在有没有 apt 的机器上跑:\n\n- **`apt`** — 真实 Debian,底层调用 `dpkg-query` / `apt-cache` / `apt-get`(改动系统的操作会在\n  非 root 时自动加 `sudo`)。\n- **`mock`** — 内存/文件模拟的假仓库,自带一批仿真包版本。在没有 dpkg 的机器(比如 NixOS)上\n  默认启用,用于开发和测试。\n\n选择顺序:`--backend` > `$DEBRIEF_BACKEND` > lockfile 里记录的后端 > 自动探测(有 `apt-get` 就用\n`apt`,否则 `mock`)。\n\n## 反向:把清单装到另一台机器(可选)\n\ncollect 出来的清单也能反过来用——在一台新机器上把清单里缺的装齐。这条线是刻意保守的\n\"友好封装\":\n\n- **`install` 只增不改**:只安装清单里*尚未安装*的包。已装的即使版本不满足约束(drift),\n  也**只警告、绝不改动**;绝不卸载\"多余\"的包。\n- **`add` / `remove` 是显式动作**:`add` 会安装并写入 manifest;`remove` 会卸载并移除。\n- **lockfile 给可复现性**:`install` 时若锁定版本仍可用且满足约束,就装*锁定的那个*(标 `↺`)。\n\n```bash\ndebrief install                    # 把 manifest 里缺的装上(优先按 lock 复现)\ndebrief add nginx@^1.24 git        # 显式安装并记录\ndebrief add htop=3.2.1-1           # 钉死一个确切版本\ndebrief remove git                 # 卸载 + 从 manifest 移除\ndebrief remove git --save-only     # 只从 manifest 移除,保留已安装\ndebrief init                       # 创建一个空的 debrief.json\ndebrief --dry-run <cmd>            # 预演,不改动任何东西\n```\n\n安装用 bun:`bun link` 后可直接用 `debrief`;或 `bun run bin/debrief.ts <command>` 直接跑。\n\n## 版本约束(npm 风格,作用在真实 Debian 版本号上)\n\n约束语法借用 npm,但比较用的是 dpkg 自己的版本算法(epoch、`~` 预发布排序等都正确处理):\n\n| 写法 | 含义 |\n| --- | --- |\n| `nginx` | 最新候选版;manifest 里写成 `^<装上的版本>` |\n| `nginx@^1.24` | `>=1.24` 且 `<2`(caret:允许非 major 升级) |\n| `nginx@~1.24` | `>=1.24` 且 `<1.25`(tilde:钉住 minor) |\n| `nginx@'>=1.22 <1.26'` | 空格分隔 = AND 组合区间 |\n| `nginx=1.24.0-1` | 精确的 Debian 版本 |\n\n## 开发\n\n```bash\nbun install\nbun test            # 单元 + 集成测试(全程用 mock 后端,任何机器都能跑)\nbun run typecheck\n```\n\n## 分发\n\n代码只用 `node:` 内置模块(通过 `src/sys.ts` 这层薄封装),所以**同一份代码在 Bun 和 Node\n(≥18)上都能跑**。默认分发方式:打成一个几十 KB 的 JS,**要求目标机有 `bun` 或 `node`**——比把整个\n运行时塞进二进制(~100MB)划算得多。\n\n```bash\nbun run build      # → dist/debrief.js(~40KB,单文件,含 node shebang)\nnode dist/debrief.js collect        # 用 node 跑\nbun  dist/debrief.js collect        # 用 bun 跑\n```\n\n- **npm**:`npm i -g debrief`(或 `bunx debrief`)—— `bin` 指向 `dist/debrief.js`,`prepack`\n  会自动构建。`npm pack` 出来的包只有 16KB / 3 个文件。\n- **`curl | sh`**:`curl -fsSL <host>/install.sh | sh` —— 拉下 `dist/debrief.js` 和一个 exec\n  node/bun 的启动器丢到 `~/.local/bin`(见 `scripts/install.sh`)。注意:这样装的 `debrief`\n  自己就是个\"裸二进制\",会被它自己的 `collect` 扫描逮到。\n\n已在真实 Debian 上验证:\n- 两种运行时结果一致 —— `bun run test:apt`(Bun)、`bun run test:node`(纯 Node)。\n- **`npm pack` → 在干净的 node 机器上 `npm i -g` → `debrief collect`/`add hello` 对着真 apt 跑通。**\n- `install.sh` 拉取 + 启动器 + 运行,链路验证过。\n\n> 真正 `npm publish` / 建 Release / 建 apt 仓库还没做(需要账号和授权)——上面验证的是\"发出去就能用\"。\n\n### 想要\"零运行时依赖\"?(体积换取)\n\n如果目标机连 node/bun 都不想装,可以退回自包含二进制 / `.deb`(每个 ~100MB,因为把运行时也打进去了):\n\n```bash\nbun run build:bin     # → dist/debrief-linux-{x64,arm64}(自包含,无需运行时)\nbun run build:deb     # → dist/debrief_<ver>_amd64.deb(需 dpkg-deb;NixOS 上在容器里打)\nsudo apt install ./dist/debrief_0.1.0_amd64.deb\n```\n\n`.deb` 有个额外的好处:装完 debrief 就是个正经 dpkg 包,会出现在它自己的 `collect` 里(吃自己的狗粮)。\n\n## 已知边界\n\n- 管理的是**系统级**的包(不是项目本地隔离);这是 apt 的本性。\n- `apt` 后端已在 **Debian 13 (trixie) 容器**里对着真实镜像端到端验证过(见下);开发主机\n  (NixOS)日常用 mock 后端顶替。\n- 依赖解析交给 apt 自己:manifest 只记你主动装的包,依赖只进 lock——正如 npm 只把直接依赖\n  写进 `package.json`。\n\n## 在真实 Debian 上验证\n\n单元测试全程用 mock 后端,任何机器都能跑。`apt` 后端要在真 Debian 上验证,用容器即可:\n\n```bash\ndocker run --rm -v \"$PWD\":/app:ro oven/bun:debian bash /app/test/apt-e2e.sh\n# 受限网络下加 --dns 1.1.1.1\n```\n\n已验证:`collect`/`status` 正确解析 `dpkg-query` + `apt-mark showmanual`(手动装的包进\nmanifest、依赖标 `auto:true` 只进 lock、带 `arch`);`status` 能发现绕过工具、直接用 apt 装/删\n的包;`add`/`remove` 走真实 `apt-get install/remove`。\n\nMIT\n","readmeFilename":"README.md","_rev":"1-2be435f948c41c3ecb956bed7e957ca3"}