Watch context fill; warn early, and make every compaction keep the darkroom campaign's HQ packet (state.json, fronts, agents, worktrees, schedules, user…

<img src="docs/assets/banner.svg" alt="darkroom — Your Lightroom presets, on your own machine" width="100%">
<b>在自己電腦上,用你買的 Lightroom preset 修照片。</b><br> 挑 preset → 調強度 → 微調滑桿 → 即時預覽 → 匯出。不用訂閱,不上傳雲端。
退了 Lightroom 訂閱之後,手上買過的幾百個 XMP preset 就沒地方用了。darkroom 直接讀這些 .xmp,把裡面的 Lightroom 設定(曝光、對比、亮部/陰影、HSL、色彩分級、曲線、漸層遮罩……)用 GPU 在本機重算出來,拖滑桿就能即時看到結果。
讓人省心是這個工具唯一的目標:你只需要專注在擅長的照片編輯,其他的——檔案、路徑、格式、設定——交給 darkroom。 原則很簡單:preset 裡寫的設定一律由程式照算;AI 只做程式做不到的事(看懂畫面、找出位置、重畫內容)。照片原檔和買來的 preset 原檔,darkroom 永遠只讀不寫。
<img src="docs/screenshots/editor-v2.png" alt="darkroom 編輯畫面:左邊 preset 庫、中間預覽與強度、右邊滑桿" width="90%">
<img src="docs/assets/flow.svg" alt="Preset → Strength → Adjust → Preview → Export" width="90%">
| 功能 | 說明 |
|---|---|
| 套 preset | 解析 Lightroom PV2012 系列 XMP(ProcessVersion 6.7/10.0/11.0/15.4),強度 0~200%;Adobe 沒公開的部分(清晰度、紋理、去朦朧、亮部陰影)用公開演算法近似 |
| 即時預覽 | GPU 渲染,1.5MP 全套運算約 15~20 ms;拖滑桿時只算最新一次 |
| 微調滑桿 | 在 preset 之上逐項加減;復原/重做;按住看原圖 |
| 讀檔 | JPEG、PNG、TIFF(含 16-bit)、HEIC/HEIF(iPhone,10-bit、Display P3 轉 sRGB);依 EXIF 自動轉正 |
| A/B 對照 | 編輯前/後拖分隔線對照,隨時「還原成原圖」,也能取回上一份編輯 |
| 裁切與旋轉 | 拖框、鎖比例(原始、1:1、4:5、2:3、16:9、自由…)、拉直、轉 90°、鏡像;縮圖、預覽、匯出都套同一個範圍 |
| 匯出 | JPEG(品質或檔案大小上限)、PNG、TIFF(8/16-bit)、WebP;只縮不放的尺寸;中繼資料全留/只留版權/全移除,可單獨拿掉 GPS;輸出銳利化;匯出預設;嵌入 sRGB,永不覆蓋;24MP 約 0.4 秒/張 |
| preset 庫 | 群組樹、搜尋(含 AI 寫的中英文風格標籤)、最愛、改名、搬移、匯入 .xmp、把目前的修改存成自己的 preset、匯出成 Lightroom 讀得到的 .xmp——整理只改索引,原檔不動 |
| 照片庫 | 資料夾縮圖格、多選;每張照片的修改自動保存(以照片內容對應,搬移改名都不會掉);複製/貼上修改;匯出所選 |
| 設定 | 設定頁(語言 zh-TW/en-US、資料夾、AI、ComfyUI)、改了立即生效;設定可匯出/匯入 |
| 給 AI 代理用 | CLI(--json 固定格式)與 MCP server(38 個 darkroom_* 工具,例如取回上一份編輯 darkroom_edit_restore/CLI edit restore),和 App 共用同一套操作與錯誤訊息 |
偵測不到的功能(CUDA、HEIC、WebP、語意搜尋、ComfyUI…)會自動關掉並說明原因。還沒做的:RAW、AI 修圖建議、AI 遮罩、去雜物/美顏,見 開發文件的路線圖。
安裝檔(建議):到 GitHub Releases 下載 Windows 的 .msi/-setup.exe,或 Linux 的 .deb/.AppImage。第一次開啟時會說明要下載 Python 與 PyTorch(有 NVIDIA 顯示卡約 3 GB),你按同意才下載,之後就直接進編輯畫面。同一頁還有命令列執行檔 darkroom(給 AI 代理或腳本用)。
從原始碼:需要 Python 3.13、Node.js 22+、NVIDIA GPU(CUDA)。或者把 docs/agent-install.md 交給你的 Claude Code/Codex/Cursor,它會一步一步裝好、遇到要你決定的地方先問你。
兩種方式的完整步驟、GPU/CUDA 說明與疑難排解都在 安裝說明。
5 分鐘上手:第一次啟動 → 指定 preset 資料夾 → 修一張 → 匯出,以及 CLI 與 MCP 各一個例子。
其他文件:設定、架構、開發與發版、給 AI 代理的使用手冊、用詞定義。
darkroom 會被你自己和 AI 代理操作,所以這幾條是寫成測試守住的,不是口頭承諾:
safe_write):只建新檔、限定在指定資料夾、拒絕寫進 preset 資料夾、拒絕替換或刪除照片與 .xmp。Sec-Fetch-Site、Content-Type,讀照片路徑的請求要帶專用標頭,HTTP 也不收任意寫入路徑。op://…)或讀環境變數,設定檔裡放金鑰本身會被拒絕。MIT。darkroom 不附任何 preset;你買來的 preset 依它們原本的授權使用。
hooks/register.ts 66 lines1import type { Register } from 'claude-code'
2
3// Temporary mod for the darkroom campaign (2026-10-10). The main session runs a long
4// /common:strategic-advance campaign with many background agents; a compaction that drops the
5// HQ packet (which fronts are active, which agents and schedules are live, what the user decided)
6// costs a full reconstruction. This mod watches the context fill and steers every compaction.
7const WARN_PERCENT = 70
8const URGENT_PERCENT = 85
9
10const KEEP = [
11 'This is a long darkroom campaign run with /common:strategic-advance. The summary MUST keep, verbatim where possible:',
12 '1. Where truth lives: D:/Code/darkroom/.strategic-advance/darkroom-alpha/state.json (rebuild the HQ packet from it after compaction, never from chat memory), the ledger run-ledger.jsonl next to it, the map .claude/wayfinder/darkroom/map.md, the contracts in .claude/contract/, CONTEXT.md, AGENTS.md, and the memory folder C:/Users/powde/.claude/projects/D--Code-darkroom/memory/.',
13 '2. Every background agent still running: its id, what it was asked to do, its model, and whether it works on main or in a worktree (with the worktree path and branch).',
14 '3. Every scheduled job (cron id, what it fires, when it ends) and every one-shot schedule still pending.',
15 '4. The active fronts and their state (sealed / in progress / deferred / needs the user), and the next move.',
16 '5. Every decision the user made in this session, in their words, with the date: scope additions, product core (讓人省心), model policy, privacy rules, what must never be written or published.',
17 '6. Open findings not yet fixed, with file:line, and anything the user is waiting on from the agent or the agent from the user.',
18 'Drop raw tool output, file dumps, and resolved back-and-forth.',
19].join('\n')
20
21let percent: number | undefined
22let warnedAt = 0
23
24const statusText = () => (percent === undefined ? undefined : `ctx ${percent}%`)
25
26export const register: Register = on => {
27 on('session.start', async ($, e, next) => {
28 const r = await next(e)
29 await $.command.register({
30 name: 'hq-compact',
31 description: 'Compact now, keeping the darkroom campaign HQ packet (fronts, agents, schedules, user decisions)',
32 })
33 try { percent = (await $.session.usage()).context.percent } catch { /* no reading yet */ }
34 $.ui.status(statusText())
35 return r
36 })
37
38 on('session.measure', ($, e, next) => {
39 percent = e.context.percent
40 $.ui.status(statusText())
41 if (percent !== undefined) {
42 const level = percent >= URGENT_PERCENT ? URGENT_PERCENT : percent >= WARN_PERCENT ? WARN_PERCENT : 0
43 if (level > warnedAt) {
44 warnedAt = level
45 $.ui.toast(level === URGENT_PERCENT
46 ? `compact-helper:context ${percent}%,建議現在就 /hq-compact`
47 : `compact-helper:context ${percent}%,找個空檔 /hq-compact`)
48 }
49 if (percent < WARN_PERCENT) warnedAt = 0
50 }
51 return next(e)
52 })
53
54 on('command.run', { command: 'hq-compact' }, async $ => {
55 await $.session.compact({ instructions: KEEP })
56 return { text: '已壓縮(保留戰役 HQ 封包)。' }
57 })
58
59 // Every compaction of the main conversation (manual, threshold, or ahead of time) keeps the packet.
60 on('session.compact', ($, e, next) => {
61 if (e.agentId) return next(e)
62 const instructions = e.instructions ? `${e.instructions}\n\n${KEEP}` : KEEP
63 return next({ ...e, instructions })
64 }).catch(($, e, next) => next(e))
65}
66