alanbox 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/0boxer/AGENTS.md +3 -2
  2. package/0boxer/src/commands/AGENTS.md +2 -1
  3. package/0boxer/src/commands/install.js +47 -0
  4. package/1swarmer/AGENTS.md +5 -2
  5. package/1swarmer/src/AGENTS.md +4 -3
  6. package/1swarmer/src/args.js +7 -0
  7. package/1swarmer/src/cli.js +26 -0
  8. package/1swarmer/src/commands/AGENTS.md +3 -0
  9. package/1swarmer/src/commands/review-file.js +997 -0
  10. package/1swarmer/src/runner/AGENTS.md +1 -0
  11. package/1swarmer/src/runner/codex-runner.js +23 -3
  12. package/2designer/README.md +3 -0
  13. package/2designer/dist/{cdp-engine-JK2XVDHK.js → cdp-engine-4AIWSWXO.js} +2 -2
  14. package/2designer/dist/{cdp-engine-A5WTMTVF.js → cdp-engine-SG4K2BCX.js} +2 -2
  15. package/2designer/dist/{chunk-NQ3ASZUE.js → chunk-7X7PTLZH.js} +2 -2
  16. package/2designer/dist/{chunk-JVF26NXD.js → chunk-DPOWNFOH.js} +2 -2
  17. package/2designer/dist/{chunk-SKEIVBOU.js → chunk-ISUUIOO7.js} +1 -1
  18. package/2designer/dist/chunk-ISUUIOO7.js.map +1 -0
  19. package/2designer/dist/cli.js +494 -244
  20. package/2designer/dist/cli.js.map +1 -1
  21. package/2designer/dist/index.d.ts +7 -18
  22. package/2designer/dist/index.js +5 -198
  23. package/2designer/dist/index.js.map +1 -1
  24. package/2designer/dist/{playwright-engine-YBRDIUHF.js → playwright-engine-YXBY3KEN.js} +2 -2
  25. package/2designer/dist/{playwright-engine-3YKJOUNU.js → playwright-engine-YXGDTSZ5.js} +2 -2
  26. package/2designer/dist/tint-UD4CJ7S2.js +7 -0
  27. package/2designer/dist/{tint-I3FTT23O.js → tint-YN63MLVN.js} +1 -1
  28. package/2designer/dist/tint-YN63MLVN.js.map +1 -0
  29. package/4reporter/README.md +24 -0
  30. package/4reporter/dist/cli.js +464 -0
  31. package/4reporter/dist/cli.js.map +1 -0
  32. package/4reporter/dist/index.d.ts +108 -0
  33. package/4reporter/dist/index.js +445 -0
  34. package/4reporter/dist/index.js.map +1 -0
  35. package/4reporter/package.json +39 -0
  36. package/README.md +13 -5
  37. package/bin/reporter.js +11 -0
  38. package/cli.js +31 -6
  39. package/mcp/README.md +7 -1
  40. package/mcp/config.toml +4 -0
  41. package/package.json +8 -4
  42. package/skills/AGENTS.md +3 -3
  43. package/skills/aitool/SKILL.md +1 -1
  44. package/skills/desginer/SKILL.md +65 -45
  45. package/skills/swarmer/SKILL.md +37 -0
  46. package/2designer/LICENSE +0 -21
  47. package/2designer/dist/chunk-SKEIVBOU.js.map +0 -1
  48. package/2designer/dist/tint-I3FTT23O.js.map +0 -1
  49. package/2designer/dist/tint-RUSSUAWA.js +0 -7
  50. /package/2designer/dist/{cdp-engine-JK2XVDHK.js.map → cdp-engine-4AIWSWXO.js.map} +0 -0
  51. /package/2designer/dist/{cdp-engine-A5WTMTVF.js.map → cdp-engine-SG4K2BCX.js.map} +0 -0
  52. /package/2designer/dist/{chunk-NQ3ASZUE.js.map → chunk-7X7PTLZH.js.map} +0 -0
  53. /package/2designer/dist/{chunk-JVF26NXD.js.map → chunk-DPOWNFOH.js.map} +0 -0
  54. /package/2designer/dist/{playwright-engine-YBRDIUHF.js.map → playwright-engine-YXBY3KEN.js.map} +0 -0
  55. /package/2designer/dist/{playwright-engine-3YKJOUNU.js.map → playwright-engine-YXGDTSZ5.js.map} +0 -0
  56. /package/2designer/dist/{tint-RUSSUAWA.js.map → tint-UD4CJ7S2.js.map} +0 -0
package/cli.js CHANGED
@@ -10,8 +10,9 @@ const path = require('path');
10
10
  const { spawn } = require('child_process');
11
11
 
12
12
  const BOXER_COMMANDS = new Set(['install', 'init', 'update']);
13
- const SWARMER_COMMANDS = new Set(['swarm', 'doctor']);
13
+ const SWARMER_COMMANDS = new Set(['swarm', 'doctor', 'review-file']);
14
14
  const DESIGNER_COMMANDS = new Set(['designer']);
15
+ const REPORTER_COMMANDS = new Set(['reporter']);
15
16
 
16
17
  async function main(argv = process.argv.slice(2)) {
17
18
  const command = argv[0] && !argv[0].startsWith('-') ? argv[0] : '';
@@ -51,11 +52,16 @@ async function main(argv = process.argv.slice(2)) {
51
52
  return;
52
53
  }
53
54
 
55
+ if (REPORTER_COMMANDS.has(command)) {
56
+ await runReporter(argv.slice(1));
57
+ return;
58
+ }
59
+
54
60
  throw new Error(`unknown alanbox command: ${command}`);
55
61
  }
56
62
 
57
63
  async function runSwarmer(argv = process.argv.slice(2)) {
58
- if (argv[0] === 'info' || argv[0] === 'doctor') {
64
+ if (argv[0] === 'info' || argv[0] === 'doctor' || argv[0] === 'review-file') {
59
65
  await runSwarmerFlow(argv);
60
66
  return;
61
67
  }
@@ -83,10 +89,21 @@ async function runDesigner(argv = process.argv.slice(2)) {
83
89
  );
84
90
  }
85
91
 
86
- await runNodeCli(cliPath, argv);
92
+ await runNodeCli('designer', cliPath, argv);
93
+ }
94
+
95
+ async function runReporter(argv = process.argv.slice(2)) {
96
+ const cliPath = path.join(__dirname, '4reporter', 'dist', 'cli.js');
97
+ if (!existsSync(cliPath)) {
98
+ throw new Error(
99
+ 'reporter dist/cli.js not found. Build it from 4reporter before running this command.',
100
+ );
101
+ }
102
+
103
+ await runNodeCli('reporter', cliPath, argv);
87
104
  }
88
105
 
89
- function runNodeCli(cliPath, argv) {
106
+ function runNodeCli(label, cliPath, argv) {
90
107
  return new Promise((resolve, reject) => {
91
108
  const child = spawn(process.execPath, [cliPath, ...argv], {
92
109
  stdio: 'inherit',
@@ -96,12 +113,12 @@ function runNodeCli(cliPath, argv) {
96
113
  child.on('error', reject);
97
114
  child.on('exit', (code, signal) => {
98
115
  if (signal) {
99
- reject(new Error(`designer exited with signal ${signal}`));
116
+ reject(new Error(`${label} exited with signal ${signal}`));
100
117
  return;
101
118
  }
102
119
 
103
120
  if (code && code !== 0) {
104
- const error = new Error(`designer exited with code ${code}`);
121
+ const error = new Error(`${label} exited with code ${code}`);
105
122
  error.exitCode = code;
106
123
  reject(error);
107
124
  return;
@@ -121,24 +138,31 @@ Usage:
121
138
  alanbox install --target codex|claude|both
122
139
  swarmer info
123
140
  swarmer doctor
141
+ swarmer review-file C:\\path\\to\\file.ts
124
142
  alanbox swarmer [options] --worker "codex:<role>:<prompt>"
125
143
  alanbox swarm [options] --worker "codex:<role>:<prompt>"
144
+ alanbox review-file C:\\path\\to\\file.ts
126
145
  swarmer [options] --worker "codex:<role>:<prompt>"
127
146
  alanbox designer <command> [options]
128
147
  designer <command> [options]
148
+ alanbox reporter weekly --path C:\\repo --user "Your Git Name"
149
+ reporter weekly --path C:\\repo --user "Your Git Name"
129
150
 
130
151
  Command groups:
131
152
  install, init, update 0boxer package/resource commands
132
153
  swarmer, swarm, doctor 1swarmer worker orchestration and provider diagnostics
133
154
  swarmer info 1swarmer worker execution config check
134
155
  designer 2designer runtime UI measurement CLI
156
+ reporter 4reporter Git weekly report CLI
135
157
 
136
158
  Examples:
137
159
  swarmer info
138
160
  swarmer doctor
161
+ swarmer review-file C:\\repo\\src\\index.ts
139
162
  swarmer --namespace run-name --worker "codex:reviewer:检查当前改动"
140
163
  alanbox designer measure --url http://localhost:3000 --selector ".dialog"
141
164
  alanbox designer screenshot --url http://localhost:3000 --output page.png
165
+ reporter weekly --path C:\\repo --user "Your Git Name" --output weekly.md
142
166
  `);
143
167
  }
144
168
 
@@ -150,4 +174,5 @@ module.exports = {
150
174
  runSwarmerFlow,
151
175
  runSwarmer,
152
176
  runDesigner,
177
+ runReporter,
153
178
  };
package/mcp/README.md CHANGED
@@ -2,4 +2,10 @@
2
2
 
3
3
  This directory is reserved for optional alanbox MCP resources.
4
4
 
5
- The installer copies this directory to Codex and Claude resource homes when present. No MCP server is implemented here yet; keep this placeholder so packaged installs still create the target `mcp/alanbox` directory.
5
+ The installer copies this directory to Codex and Claude resource homes when present. No alanbox-owned MCP server is implemented here yet; keep this placeholder so packaged installs still create the target `mcp/alanbox` directory.
6
+
7
+ For Codex installs, `alanbox install/init/update --target codex` also ensures the user's Codex MCP config has the bundled `mcp_servers.playwright` entry from `config.toml`. Existing Playwright MCP entries are left untouched.
8
+
9
+ ### Important files
10
+
11
+ - `config.toml` — source MCP config fragment copied into Codex config only when `[mcp_servers.playwright]` is missing.
@@ -0,0 +1,4 @@
1
+ [mcp_servers.playwright]
2
+ args = ["-y", "@playwright/mcp@0.0.76"]
3
+ command = "npx"
4
+ startup_timeout_sec = 120
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "alanbox",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Run isolated child Codex or Claude workers with serial, parallel, and auto swarm orchestration.",
5
5
  "main": "cli.js",
6
6
  "bin": {
7
7
  "alanbox": "bin/alanbox.js",
8
8
  "swarmer": "bin/swarmer.js",
9
- "designer": "bin/designer.js"
9
+ "designer": "bin/designer.js",
10
+ "reporter": "bin/reporter.js"
10
11
  },
11
12
  "files": [
12
13
  "cli.js",
@@ -16,6 +17,8 @@
16
17
  "1swarmer/**",
17
18
  "2designer/package.json",
18
19
  "2designer/dist/**",
20
+ "4reporter/package.json",
21
+ "4reporter/dist/**",
19
22
  "skills/**",
20
23
  "plugin/**",
21
24
  "hooks/**",
@@ -28,8 +31,9 @@
28
31
  "swarm": "node bin/swarmer.js",
29
32
  "doctor": "node bin/alanbox.js doctor",
30
33
  "designer": "node bin/designer.js",
31
- "test": "node bin/alanbox.js --help && node bin/alanbox.js swarmer info && node bin/swarmer.js info && node bin/swarmer.js doctor --help && node bin/designer.js --help",
32
- "validate": "node bin/swarmer.js info && node bin/designer.js --help"
34
+ "reporter": "node bin/reporter.js",
35
+ "test": "node bin/alanbox.js --help && node bin/alanbox.js swarmer info && node bin/swarmer.js info && node bin/swarmer.js doctor --help && node bin/designer.js --help && node bin/reporter.js --help",
36
+ "validate": "node bin/swarmer.js info && node bin/designer.js --help && node bin/reporter.js --help"
33
37
  },
34
38
  "keywords": [
35
39
  "codex",
package/skills/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ## aibox skills
2
2
 
3
- `C:\Users\lenovo\Desktop\my-project\101my-aitool\aibox\skills` 存放随 `alanbox` 分发的 agent skills,按 `0boxer` 的安装命令作为统一安装源。当前用 `swarmer` 承载 `1swarmer` 的 `swarmer`/`swarm`、`info` 用法,用 `aitool` 作为 alanbox 总入口说明,并保留用户明确要求接入的 `2designer` UI 高精度还原 workflow;不要在这里接入 `3apiflower` 的 getapi,除非用户重新明确要求。
3
+ `C:\Users\lenovo\Desktop\my-project\101my-aitool\aibox\skills` 存放随 `alanbox` 分发的 agent skills,按 `0boxer` 的安装命令作为统一安装源。当前用 `swarmer` 承载 `1swarmer` 的 `swarmer`/`swarm`、`info` 用法,用 `aitool` 作为 alanbox 总入口说明,并保留用户明确要求接入的 `2designer` UI 高精度还原 workflow;不要在这里接入 `3apiflower` 的 `alan-utils` / `st`,除非用户重新明确要求。
4
4
 
5
5
  **Important:** skills 只写调用 workflow,不实现业务逻辑。worker 执行示例优先使用裸 `swarmer ...`;初始化和资源更新使用 `alanbox init` / `alanbox update`;本地源码调试才使用 `node bin/alanbox.js ...`。
6
6
 
@@ -10,12 +10,12 @@
10
10
 
11
11
  - `aitool/SKILL.md` — `alanbox` 总入口 skill;用于在 `swarmer`、`designer` 和安装/更新命令之间选择正确入口。
12
12
  - `swarmer/SKILL.md` — 合并后的 worker 编排、配置检查与 provider 诊断 skill;同时覆盖 `codex:`、`claude:` worker、串行/并行/auto workflow、`info` 和 `doctor`。
13
- - `desginer/SKILL.md` — `2designer` 的 UI 高精度还原 workflow;目录名当前按现状保留,内容通过 `designer` 的 `measure` / `screenshot` / `overlay` 命令做设计稿网站与运行页的像素级对比。
13
+ - `desginer/SKILL.md` — `2designer` 的 UI 高精度还原 workflow;目录名当前按现状保留,内容通过 `designer` 的 `measure` / `screenshot` / `overlay` / `changelist` 命令做设计稿网站与运行页的像素级对比,并要求用 `all/message.md` 固定截图坐标、用 `regions\word\<id>` 调用 `codex:` 或 `claude:` 子 agent 并发生成组件树 TODO。
14
14
 
15
15
  ### Implementation notes
16
16
 
17
17
  - 新增或修改 CLI 参数时,同步检查对应 `SKILL.md` 示例。
18
18
  - `install` 会把本目录中含 `SKILL.md` 的子目录安装到 Codex 的 `~/.codex/skills`,以及 Claude 的 `~/.claude/skill`。
19
- - `desginer` 调用的是 `designer` CLI;更新 `2designer` 的命令名、参数或产物路径约定时,同步更新本目录同名 skill。
19
+ - `desginer` 调用的是 `designer` CLI;更新 `2designer` 的命令名、参数或产物路径约定时,同步更新本目录同名 skill。UI 对齐流程的长期产物约定包括 `all\message.md`、每轮 `changelist.json`、`regions\word|graphic\<id>` 和合并后的组件树 `todo.json`。
20
20
  - `aibox-swarmer`、`sub-codex-swarmer`、`sub-codex-doctor` 已合并进 `swarmer`;安装/更新逻辑会清理这些旧 skill 名,避免用户环境里出现重复入口。
21
21
  - 不要把一次性本机 runId、账号、token、私有服务地址写进 skill。
@@ -13,7 +13,7 @@ description: 当需要使用 alanbox 这个本地 AI 工具包,并需要在 sw
13
13
  - provider 健康检查:使用 `alanbox doctor` 或 `swarmer doctor`,该命令归 `1swarmer`。
14
14
  - UI 高精度还原、运行页测量、截图、overlay 对比:使用 `designer` CLI;如果任务是按设计稿精修页面,优先使用当前安装的 UI 还原 skill。
15
15
  - 安装或更新 Codex/Claude 资源:使用 `alanbox init`、`alanbox update` 或 `alanbox install --target codex|claude|both`。
16
- - `3apiflower` API flow 当前不作为 `alanbox` npm 包公开命令;除非用户明确要求,不要把它接进 skills 或默认入口。
16
+ - `3apiflower` API flow 当前作为独立本地 `alan-utils` / `st` 工作区维护,不作为 `alanbox` npm 包公开命令;除非用户明确要求,不要把它接进 skills 或默认入口。
17
17
 
18
18
  ## 常用命令
19
19
 
@@ -1,29 +1,27 @@
1
1
  ---
2
- name: ui-pixel-restore
3
- description: "当需要按 Axure、原型站或线上设计稿网站对现有运行页面做 UI 高精度还原时使用。要求用户提供运行页 URL、设计稿 URL、两边对应的 JS/CSS selector 路径,并通过 Chrome CDP + designer 的 measure/screenshot/overlay 循环做组件级像素重叠、TODO 修复和 1px 级验收。适用于不要凭截图臆测、必须用精确节点重叠和 getBoundingClientRect/getComputedStyle 数据追差异的任务。"
2
+ name: desginer
3
+ description: "设计稿到运行页面做 UI 高精度。"
4
4
  ---
5
-
6
5
  # UI 高精度还原:运行页 ↔ 设计稿网站
7
6
 
8
- 目标是把一个已存在的页面或组件精确还原到 Axure/原型/设计稿网站的对应节点。你是判断和修复者;`designer` 只负责采集数据和生成叠图。
7
+ 目标是把一个运行页面精确还原到设计稿。`designer` 是运行页面精确还原到设计稿的工作流
9
8
 
10
- ## 输入契约
9
+ ## 工作流输入要求
11
10
 
12
11
  开始改代码前,必须拿到这些信息;缺失时先向用户要,不要用整页或随机节点替代:
13
12
 
14
- - 运行页 URL:要修改、要还原的页面。
15
- - 运行页 selector/JS 路径:要截图、叠图、测量的精确组件节点。
16
- - 设计稿 URL:Axure/原型站/设计稿网页。
17
- - 设计稿 selector/JS 路径:与运行页对应的精确设计节点;如果在 iframe 里,同时记录 frame selector,例如 `#mainFrame`。
18
- - CDP 地址:默认按用户提供值使用,例如 `127.0.0.1:8000`。
13
+ - 运行页 URL:要还原的运行页面。
14
+ - 运行页 selector/JS 路径
15
+ - 设计稿 URL:设计稿网页。
16
+ - 设计稿 selector/JS 路径
17
+ - CDP 地址:默认 `127.0.0.1:8000`。
19
18
  - 产物根目录:固定使用 `C:\Users\lenovo\Desktop\all-project\.tmp\<当天日期>-<hash>\`,每轮放在递增的 `round<N>\` 子目录,例如 `round1\`、`round2\`、`round3\`。
20
19
 
21
20
  优先使用用户提供的精确 JS 路径。`body > div:nth-child(...)` 这类路径只适合当前会话验证,不能当长期稳定 selector 写进代码。
22
21
 
23
- ## Chrome/CDP
24
-
25
- 需要连接用户当前登录态时,用独立调试端口启动 Chrome;端口、用户目录和初始 URL 按任务替换:
22
+ ## 工作流初始化
26
23
 
24
+ 用下方命令启动 Chrome页面;注意要一个页面两个tab,防止页面状态的丢失:
27
25
  ```powershell
28
26
  & "C:\Program Files\Google\Chrome\Application\chrome.exe" `
29
27
  --remote-debugging-address=127.0.0.1 `
@@ -37,13 +35,18 @@ description: "当需要按 Axure、原型站或线上设计稿网站对现有运
37
35
 
38
36
  遇到登录、权限、证书、白屏、网络拦截或需要人工操作的问题时,不要给最终失败结论。明确告诉用户在这个 Chrome 窗口处理,等待用户完成后继续同一轮测量和叠图。
39
37
 
40
- ## 主循环
38
+ 初始化阶段必须创建 `C:\Users\lenovo\Desktop\all-project\.tmp\<yyyy-mm-dd>-<hash>\all\message.md`,把本次要截图和测量的锚点写进去。这个文件是后续所有轮次、Codex/Claude 子 agent 和 TODO 合并的共享契约,避免因为设计稿和运行页截图坐标不一致浪费轮次。至少记录:URL、selector、CDP 地址、初始截图坐标等信息。
41
39
 
40
+
41
+ ## 主循环
42
42
  先建立本次任务产物目录。日期用当天日期,hash 用运行页 URL、设计稿 URL 和两边 selector 生成短 hash;无法稳定生成时用 6-8 位随机短 hash。示例:
43
43
 
44
44
  ```text
45
45
  C:\Users\lenovo\Desktop\all-project\.tmp\2026-06-17-a1b2c3d4\
46
+ all\
47
+ message.md
46
48
  round1\
49
+ \regions\
47
50
  design.png
48
51
  runtime.png
49
52
  overlay.png
@@ -64,52 +67,63 @@ C:\Users\lenovo\Desktop\all-project\.tmp\2026-06-17-a1b2c3d4\
64
67
  按这个顺序循环,直到验收通过:
65
68
 
66
69
  1. **建立锚点。** 明确运行页 selector 对应设计稿 selector。组件级任务必须拿组件根节点对组件根节点,不要乱拿整页叠图。
67
- 2. **生成本轮三图。** 在同一个 `round<N>\` 目录内一次性完成设计稿截图、运行页截图和重叠图;`<N>` 是当前循环轮次,第一轮用 `round1\`,修改后第二轮用 `round2\`,不得覆盖旧轮次。三张图缺任意一张,本轮无效,先补齐再分析。
70
+ 2. **生成本轮三图。** 在同一个 `round<N>\` 目录内一次性完成设计稿截图、运行页截图和重叠图;`<N>` 是当前循环轮次,第一轮用 `round1\`,修改后第二轮用 `round2\`,不得覆盖旧轮次。三张图缺任意一张,本轮无效,先补齐再分析。截图前先读取 `all\message.md`,确认本轮仍使用同一组组件根节点和坐标;如果运行页因交互状态导致 rect 改变,先更新 `all\message.md` 并说明原因。
68
71
  ```bash
69
72
  designer screenshot --url "<designUrl>" --selector "<designSelector>" --output "C:\Users\lenovo\Desktop\all-project\.tmp\<yyyy-mm-dd>-<hash>\round<N>\design.png" --cdp 127.0.0.1:8000
70
73
  designer screenshot --url "<runtimeUrl>" --selector "<runtimeSelector>" --output "C:\Users\lenovo\Desktop\all-project\.tmp\<yyyy-mm-dd>-<hash>\round<N>\runtime.png" --cdp 127.0.0.1:8000
71
74
  designer overlay --design "C:\Users\lenovo\Desktop\all-project\.tmp\<yyyy-mm-dd>-<hash>\round<N>\design.png" --url "<runtimeUrl>" --selector "<runtimeSelector>" --output "C:\Users\lenovo\Desktop\all-project\.tmp\<yyyy-mm-dd>-<hash>\round<N>\overlay.png" --cdp 127.0.0.1:8000
72
75
  ```
73
76
  如果设计节点在 Axure iframe 里,而 `screenshot` 暂不支持 `--frame`,用 Playwright/CDP 在 iframe document 内定位并截图该节点;产物仍命名为 `design.png`,后续继续用同一轮 `overlay` 命令。始终传 `--selector` 和 `--output`,让 `overlay` 以 headless 方式生成组件级像素合成图。设计图会以品红色覆盖到运行截图上;品红色明显错位处就是布局、尺寸、颜色或层级问题。
74
- 3. **视觉差异分析。** 同时看本轮的 `design.png`、`runtime.png`、`overlay.png`:判断布局、颜色、层级、字号、边框、圆角、间距和文案。不要只看单张截图。
75
- 4. **TODO 清单。** 把差异拆成可测项目,例如“标题 x 偏 8px”“表单项高度少 4px”“关联·评分标准(1:N)宽度不对”“某独立 path 节点造成真实间距”。
76
- 5. **measure 定量。** TODO 涉及的运行页节点和设计稿节点分别测量:
77
+ 3. **视觉差异分区。** 同时看本轮的 `design.png`、`runtime.png`、`overlay.png`:判断布局、颜色、层级、字号、边框、圆角、间距和文案。不要只看单张截图。每轮必须生成 `changelist.json`、全量对比图和区域目录:
78
+ ```bash
79
+ designer changelist --design "C:\Users\lenovo\Desktop\all-project\.tmp\<yyyy-mm-dd>-<hash>\round<N>\design.png" --runtime "C:\Users\lenovo\Desktop\all-project\.tmp\<yyyy-mm-dd>-<hash>\round<N>\runtime.png" --output "C:\Users\lenovo\Desktop\all-project\.tmp\<yyyy-mm-dd>-<hash>\round<N>\changelist.json" --annotated "C:\Users\lenovo\Desktop\all-project\.tmp\<yyyy-mm-dd>-<hash>\round<N>\changes.png" --regions-dir "C:\Users\lenovo\Desktop\all-project\.tmp\<yyyy-mm-dd>-<hash>\round<N>\regions"
80
+ ```
81
+ `changelist` 只输出变更区域 JSON 和设计稿/运行图左右并排的差异高亮对比图,用来快速定位需要进一步 `measure` 的节点;默认双模式下还会生成 `changes-word-sentence.png` 与 `changes-graphic-large.png`,并在 `regions\word\<id>`、`regions\graphic\<id>` 下为每个区域导出裁剪后的 `design.png`、`runtime.png`、`compare.png` 和 `region.json`。不要把它当成 UI 是否通过的唯一判断。
82
+ 4. (如果主布局未基本完成的跳过)**暴力并发生成 TODO。** 优先按 `regions\word\<id>` 拆分差异;如果 word 区域有 25 个,就启动 25 个独立 Codex/Claude 子 agent 并行分析,cdoex 每个子 agent 只负责一个区域目录,如果
83
+
84
+ 每个 Codex/Claude 子 agent 只产出 JSON 片段,不直接改代码。片段写到当前轮临时文件,例如 `round<N>\todo-parts\word-<id>.json`;主 agent 负责合并。单个区域 JSON 片段必须按组件层级组织,类似 React 组件树,不要写平铺句子;具体格式见后文“子 agent JSON 片段格式”。
85
+
86
+ 合并所有片段为 `round<N>\todo.json`。合并时按业务组件层级归并同名组件和子组件,去掉重复区域,只保留有证据的修改项;相同问题出现在多个区域时合并为一个组件 TODO,并在 `evidenceRegions` 中列出所有区。
87
+ 5. **分析修改定量。** 对合并后的 `todo.json` 做分析,测。先去重 `measureNeeded`,再对 TODO 涉及的运行页节点和设计稿节点分别测量:
77
88
  ```bash
78
89
  designer measure --url "<runtimeUrl>" --selector "<runtimeSelector>" --depth 0 --cdp 127.0.0.1:8000
79
90
  designer measure --url "<designUrl>" --selector "<designSelector>" --depth 0 --cdp 127.0.0.1:8000
80
91
  designer measure --url "<designUrl>" --frame "#mainFrame" --selector "#u0" --depth 0 --cdp 127.0.0.1:8000
81
92
  ```
82
- `measure` 返回 `bbox` 与 `computedStyle`。必须用完整数据:bbox、font、color、background-color、border、padding、margin、display、visibility 缺失时重新测,不要用半截数据做判断。
93
+ `measure` 返回 `bbox` 与 `computedStyle`。必须用完整数据:bbox、font、color、background-color、border、padding、margin、display、visibility 缺失时重新测,不要用半截数据做判断。测量结果写回 `todo.json` 对应组件节点,形成“证据区域 → 组件层级 → 定量字段 → 修改建议”的闭环。
94
+
83
95
  6. **修改实现。** 按项目 AGENTS.md、公共组件规范和现有代码风格改。先修影响最大的位移和尺寸,再修字体、颜色、边框、圆角和细节。
84
96
  7. **重复验证。** 每次修改后重新执行“生成本轮三图”、`measure` 和 TODO 更新。不要在“感觉差不多”时停止。
85
97
 
86
- ## Axure/原型测量规则
87
-
88
- - Axure 里有些节点是独立 path 节点,没有布局容器。计算间距时必须测量相邻组件的 `rect`,用坐标差值反推这些节点的实际占位。
89
- - 设计稿 iframe 节点按这种逻辑测:
90
- ```js
91
- const iframe = document.getElementById("mainFrame");
92
- const doc = iframe.contentDocument;
93
- const win = iframe.contentWindow;
94
- const node = doc.querySelector("#uNNN");
95
- const cs = win.getComputedStyle(node);
96
- return {
97
- rect: node.getBoundingClientRect(),
98
- font: cs.font,
99
- color: cs.color,
100
- bg: cs.backgroundColor,
101
- border: cs.border,
102
- padding: cs.padding,
103
- margin: cs.margin,
104
- };
105
- ```
106
- - 对比标题、表单项、按钮、状态栏这类节点时,不只量外层;必要时继续量文本叶子节点、图标节点和相邻节点。
98
+ ## 子 agent JSON 片段格式
99
+
100
+ 每个 Codex/Claude 子 agent 只产出 JSON 片段,不直接改代码。片段写到当前轮临时文件,例如 `round<N>\todo-parts\word-<id>.json`;主 agent 负责合并。单个区域 JSON 片段必须按组件层级组织,类似 React 组件树,不要写平铺句子:
101
+
102
+ ```json
103
+ {
104
+ "sourceRegion": "regions/word/1",
105
+ "components": {
106
+ "开发页面的xx1组件": {
107
+ "selectorGuess": "",
108
+ "evidence": ["region.json", "compare.png"],
109
+ "todo": ["要怎么修改","不需要修改,设计稿合理误差"],
110
+ "children": {
111
+ "xx1的子组件xx2": {
112
+ "selectorGuess": "",
113
+ "todo": ["要怎么修改"],
114
+ "measureNeeded": [
115
+ { "runtimeSelector": "", "designSelector": "", "reason": "需要定量确认 x/width/font" }
116
+ ]
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ ```
107
123
 
108
- ## 实践约束
124
+ ## 测量规则
109
125
 
110
- - **padding 放在卡片外层,不放在内容槽。** 卡片内同时有 `XTitle` + 内容时,padding 只加在内容 div 上,会让标题贴到卡片左边沿。标准形状:外层 `rounded bg-white px-* pb-*` → `XTitle` → 内容 div 只保留布局类(`min-h-0 flex-1`)。
111
- - **独立节点。** Axure 中部分节点是独立 path 节点,无布局容器。计算间距时必须测量相邻组件的 rect,用坐标差值反推这些节点的实际占位。
112
- - **组件自带 chrome。** 以“卡片”形态交付的组件已自带边框、圆角、标题槽、状态栏等装饰件。外层再套 `<div className="rounded bg-white">`、或手写 `{rows.length} 条`,都会重复 chrome 并与组件内部状态脱节。各组件可配置的槽位与开关:`XDataGrid`(`title`、`StatusBarExtra`、`noBorderRadius`、`noRecordCountInStatusBar`、`noSelectRowCountInStatusBar`、`hideStatusBar`、`hideHead`);`XDraggableModal`(`title`、`titleIcon`、`hideDefaultTitleIcon`、`hiddenTitle`、`layoutMode`、`noFrame`);`XSelector`(`statusBarExtra`)。在组件外层做组合前,先检查它暴露的槽;`.windsurf/skills/xinherc/` 与 `./Spec/组件库规范/api-docs/<Name>.md` 是真理来源。
126
+ 默认用 `measure` 测量节点的 `getBoundingClientRect()` `getComputedStyle()`,并对比两边的 bbox、font、color、background-color、border、padding、margin、display、visibility。如果measure无法测量时用playwright MCP 兜底。
113
127
 
114
128
  ## 验收条件
115
129
 
@@ -117,6 +131,12 @@ C:\Users\lenovo\Desktop\all-project\.tmp\2026-06-17-a1b2c3d4\
117
131
 
118
132
  - 可控关键节点的 `bbox` 位置和尺寸与设计稿差异达到 1px 以内,并且 `overlay.png` 没有明显品红错位。
119
133
  - 剩余差异来自公共组件库不可修改的内部 chrome、渲染机制或浏览器字体差异,并且已经用 `measure` 证明差异来源。
120
- - 剩余差异符合项目 AGENTS.md、公共组件规范或上面的实践约束,且说明具体原因和影响范围。
134
+ - 剩余差异符合项目 AGENTS.md、公共组件规范或上面的实践约束,设计稿的不完善和设计稿不可以避免的不对齐,且说明具体原因和影响范围。
121
135
 
122
136
  最终汇报要包含:使用的运行页/设计稿 selector、最后一轮 `design/runtime/overlay` 文件路径、已修 TODO、未修差异及其验收理由。
137
+
138
+ ## 实践约束
139
+
140
+ - **padding 放在卡片外层,不放在内容槽。** 卡片内同时有 `XTitle` + 内容时,padding 只加在内容 div 上,会让标题贴到卡片左边沿。标准形状:外层 `rounded bg-white px-* pb-*` → `XTitle` → 内容 div 只保留布局类(`min-h-0 flex-1`)。
141
+ - **独立节点。** Axure 中部分节点是独立 path 节点,无布局容器。计算间距时必须测量相邻组件的 rect,用坐标差值反推这些节点的实际占位。
142
+ - **组件自带 chrome。** 以“卡片”形态交付的组件已自带边框、圆角、标题槽、状态栏等装饰件。
@@ -13,6 +13,7 @@ description: 当 Codex 或 Claude 需要通过 alanbox 的 swarmer/swarm 调用
13
13
  swarmer info
14
14
  alanbox doctor
15
15
  swarmer --namespace run-name --worker "codex:reviewer:检查当前改动"
16
+ swarmer review-file "C:\path\to\file-or-dir"
16
17
  ```
17
18
 
18
19
  `swarmer` 是对外稳定入口,内部走 `swarm` 执行链路。包级命令也可以写成:
@@ -20,6 +21,7 @@ swarmer --namespace run-name --worker "codex:reviewer:检查当前改动"
20
21
  ```powershell
21
22
  alanbox swarmer info
22
23
  alanbox swarm --namespace run-name --worker "codex:reviewer:检查当前改动"
24
+ alanbox review-file "C:\path\to\file-or-dir"
23
25
  ```
24
26
 
25
27
  ## Worker 规格
@@ -80,6 +82,40 @@ swarmer --auto "检查这个项目的主要风险" -a "codex:reviewer:检查代
80
82
  swarmer --resume <runId> --namespace <namespace> -y
81
83
  ```
82
84
 
85
+ ## 双 CLI 文件 review
86
+
87
+ 当需要 Codex CLI 和 Claude CLI 同时审查一个文件或目录,并把报告写到一个 Markdown 文件时,使用专用命令:
88
+
89
+ ```powershell
90
+ swarmer review-file "C:\repo\src\index.ts"
91
+ ```
92
+
93
+ 默认行为:
94
+
95
+ - 第一阶段并行调用 `codex` 和 `claude` 初审同一目标,并各自生成 `*.codex.initial.md` / `*.claude.initial.md` sidecar。
96
+ - 第二阶段从 sidecar 初审 md 读取 `[codex√] 检查出的内容`、`[claude√] 检查出的内容`,再合并到主报告。
97
+ - 后续复核直接追加链条标记:认同写成 `[codex√claude√]`,不认同写成 `[codex√claude❌]`,出现 `❌` 后继续让下一方复核,例如 `[codex√claude❌codex√]`。
98
+ - 共识链最后必须为 `√`;超过最大轮次仍未达成时,会追加强制最终 `√` 并保留前面 `❌` 的原因供人工复查。
99
+ - 报告文件默认为 `C:\Users\lenovo\Desktop\all-project\.tmp\swarm\<yyyy-mm-dd-HHmmss>-<hash>.md`。
100
+
101
+ 长任务或某个 provider 超时时,可以分阶段恢复:
102
+
103
+ ```powershell
104
+ swarmer review-file "C:\repo\src" --phase initial --providers codex,claude
105
+ swarmer review-file "C:\repo\src" --phase cross-check --report-file "C:\Users\lenovo\Desktop\all-project\.tmp\swarm\2026-06-18-161626-xxxxxxxx.md"
106
+ ```
107
+
108
+ 可用参数:
109
+
110
+ - `--providers codex,claude` 指定参与执行的 provider。
111
+ - `--review-kind defects|improvements` 指定审查类型;未传时会从 `--context` 中的“改进/优化/性能/可维护”等词自动推断。
112
+ - `--report-file <path>` 复用或指定单个 Markdown 报告文件,常用于只补跑 cross-check。
113
+ - `--report-dir <path>` 指定目录,CLI 会在里面生成一个新的时间哈希 Markdown 文件。
114
+ - `--max-consensus-rounds <n>` 限制 `❌` 条目继续复核的最大轮数;默认 4,之后会按协议追加最终 `√`。
115
+ - `--claude-permission-mode <mode>` 覆盖 Claude 权限模式;默认是 `bypassPermissions`,用于避免非交互读取文件时卡住。
116
+ - `--no-claude-safe-mode` 关闭默认传给 Claude 的 `--safe-mode`;默认开启 safe mode,以减少本机 hooks/plugins 对自动化 review 的干扰。
117
+ - `--claude-effort <level>` 覆盖 Claude effort;默认是 `low`,用于让自动化 review 可及时返回。
118
+
83
119
  ## 诊断
84
120
 
85
121
  修改配置、排查 provider、账户主目录、沙箱、命令路径或子 worker 健康状态前,先看已解析配置:
@@ -107,3 +143,4 @@ alanbox doctor --account codex-19b
107
143
  ```
108
144
 
109
145
  `%USERPROFILE%\.multirunagent` 是兼容存储路径,不要因为命令名改成 `swarmer` 就改写这个路径。
146
+ `review-file` 是例外:它按用户要求把单个 Markdown review 报告写入 `C:\Users\lenovo\Desktop\all-project\.tmp\swarm` 或 `--report-root` / `--report-file` / `--report-dir` 指定位置。
package/2designer/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Zhiyu Fang
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/overlay/tint.ts"],"sourcesContent":["import sharp from 'sharp'\r\n\r\nexport type TintMode = 'magenta' | 'ghost' | 'difference'\r\n\r\n/**\r\n * Process a design image for overlay compositing.\r\n *\r\n * Modes:\r\n * - 'magenta': Tint non-white pixels magenta (best for white/light backgrounds)\r\n * - 'ghost': Reduce opacity uniformly (works on any background color)\r\n * - 'difference': No preprocessing — caller should use 'difference' blend mode\r\n *\r\n * Returns a PNG buffer with alpha channel.\r\n */\r\nexport async function tintDesignImage(\r\n imagePath: string,\r\n mode: TintMode = 'auto' as any,\r\n): Promise<Buffer> {\r\n const { data, info } = await sharp(imagePath)\r\n .ensureAlpha()\r\n .raw()\r\n .toBuffer({ resolveWithObject: true })\r\n\r\n const pixels = new Uint8Array(data.buffer)\r\n\r\n // Auto-detect: check if background is white/light\r\n const resolvedMode = mode === ('auto' as any) ? detectMode(pixels) : mode\r\n\r\n if (resolvedMode === 'magenta') {\r\n return tintMagenta(pixels, info)\r\n }\r\n\r\n if (resolvedMode === 'difference') {\r\n // Return as-is for difference blend\r\n return sharp(imagePath).ensureAlpha().png().toBuffer()\r\n }\r\n\r\n // ghost mode: uniform opacity reduction\r\n return tintGhost(pixels, info, 0.4)\r\n}\r\n\r\nfunction detectMode(pixels: Uint8Array): TintMode {\r\n // Sample corners + edges to detect background color\r\n // If most sampled pixels are bright (>220), use magenta mode\r\n // Otherwise use ghost mode\r\n let brightCount = 0\r\n const sampleSize = Math.min(pixels.length / 4, 1000)\r\n const step = Math.floor(pixels.length / 4 / sampleSize)\r\n\r\n for (let i = 0; i < sampleSize; i++) {\r\n const idx = i * step * 4\r\n const brightness = (pixels[idx] + pixels[idx + 1] + pixels[idx + 2]) / 3\r\n if (brightness > 220) brightCount++\r\n }\r\n\r\n return (brightCount / sampleSize) > 0.5 ? 'magenta' : 'ghost'\r\n}\r\n\r\nconst LIGHT_THRESHOLD = 230\r\nconst MAGENTA: [number, number, number] = [220, 40, 160]\r\n\r\nfunction tintMagenta(\r\n pixels: Uint8Array,\r\n info: { width: number; height: number },\r\n): Promise<Buffer> {\r\n for (let i = 0; i < pixels.length; i += 4) {\r\n const r = pixels[i], g = pixels[i + 1], b = pixels[i + 2]\r\n const brightness = (r + g + b) / 3\r\n\r\n if (brightness > LIGHT_THRESHOLD) {\r\n pixels[i + 3] = 0\r\n } else {\r\n const darkness = 1 - brightness / 255\r\n pixels[i] = MAGENTA[0]\r\n pixels[i + 1] = MAGENTA[1]\r\n pixels[i + 2] = MAGENTA[2]\r\n pixels[i + 3] = Math.round(darkness * 200)\r\n }\r\n }\r\n\r\n return sharp(Buffer.from(pixels.buffer), {\r\n raw: { width: info.width, height: info.height, channels: 4 },\r\n })\r\n .png()\r\n .toBuffer()\r\n}\r\n\r\nfunction tintGhost(\r\n pixels: Uint8Array,\r\n info: { width: number; height: number },\r\n opacity: number,\r\n): Promise<Buffer> {\r\n for (let i = 0; i < pixels.length; i += 4) {\r\n pixels[i + 3] = Math.round(pixels[i + 3] * opacity)\r\n }\r\n\r\n return sharp(Buffer.from(pixels.buffer), {\r\n raw: { width: info.width, height: info.height, channels: 4 },\r\n })\r\n .png()\r\n .toBuffer()\r\n}\r\n"],"mappings":";AAAA,OAAO,WAAW;AAclB,eAAsB,gBACpB,WACA,OAAiB,QACA;AACjB,QAAM,EAAE,MAAM,KAAK,IAAI,MAAM,MAAM,SAAS,EACzC,YAAY,EACZ,IAAI,EACJ,SAAS,EAAE,mBAAmB,KAAK,CAAC;AAEvC,QAAM,SAAS,IAAI,WAAW,KAAK,MAAM;AAGzC,QAAM,eAAe,SAAU,SAAiB,WAAW,MAAM,IAAI;AAErE,MAAI,iBAAiB,WAAW;AAC9B,WAAO,YAAY,QAAQ,IAAI;AAAA,EACjC;AAEA,MAAI,iBAAiB,cAAc;AAEjC,WAAO,MAAM,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS;AAAA,EACvD;AAGA,SAAO,UAAU,QAAQ,MAAM,GAAG;AACpC;AAEA,SAAS,WAAW,QAA8B;AAIhD,MAAI,cAAc;AAClB,QAAM,aAAa,KAAK,IAAI,OAAO,SAAS,GAAG,GAAI;AACnD,QAAM,OAAO,KAAK,MAAM,OAAO,SAAS,IAAI,UAAU;AAEtD,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,UAAM,MAAM,IAAI,OAAO;AACvB,UAAM,cAAc,OAAO,GAAG,IAAI,OAAO,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,KAAK;AACvE,QAAI,aAAa,IAAK;AAAA,EACxB;AAEA,SAAQ,cAAc,aAAc,MAAM,YAAY;AACxD;AAEA,IAAM,kBAAkB;AACxB,IAAM,UAAoC,CAAC,KAAK,IAAI,GAAG;AAEvD,SAAS,YACP,QACA,MACiB;AACjB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AACzC,UAAM,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC;AACxD,UAAM,cAAc,IAAI,IAAI,KAAK;AAEjC,QAAI,aAAa,iBAAiB;AAChC,aAAO,IAAI,CAAC,IAAI;AAAA,IAClB,OAAO;AACL,YAAM,WAAW,IAAI,aAAa;AAClC,aAAO,CAAC,IAAI,QAAQ,CAAC;AACrB,aAAO,IAAI,CAAC,IAAI,QAAQ,CAAC;AACzB,aAAO,IAAI,CAAC,IAAI,QAAQ,CAAC;AACzB,aAAO,IAAI,CAAC,IAAI,KAAK,MAAM,WAAW,GAAG;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO,MAAM,OAAO,KAAK,OAAO,MAAM,GAAG;AAAA,IACvC,KAAK,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,UAAU,EAAE;AAAA,EAC7D,CAAC,EACE,IAAI,EACJ,SAAS;AACd;AAEA,SAAS,UACP,QACA,MACA,SACiB;AACjB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AACzC,WAAO,IAAI,CAAC,IAAI,KAAK,MAAM,OAAO,IAAI,CAAC,IAAI,OAAO;AAAA,EACpD;AAEA,SAAO,MAAM,OAAO,KAAK,OAAO,MAAM,GAAG;AAAA,IACvC,KAAK,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,UAAU,EAAE;AAAA,EAC7D,CAAC,EACE,IAAI,EACJ,SAAS;AACd;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/overlay/tint.ts"],"sourcesContent":["import sharp from 'sharp'\r\n\r\nexport type TintMode = 'magenta' | 'ghost' | 'difference'\r\n\r\n/**\r\n * Process a design image for overlay compositing.\r\n *\r\n * Modes:\r\n * - 'magenta': Tint non-white pixels magenta (best for white/light backgrounds)\r\n * - 'ghost': Reduce opacity uniformly (works on any background color)\r\n * - 'difference': No preprocessing — caller should use 'difference' blend mode\r\n *\r\n * Returns a PNG buffer with alpha channel.\r\n */\r\nexport async function tintDesignImage(\r\n imagePath: string,\r\n mode: TintMode = 'auto' as any,\r\n): Promise<Buffer> {\r\n const { data, info } = await sharp(imagePath)\r\n .ensureAlpha()\r\n .raw()\r\n .toBuffer({ resolveWithObject: true })\r\n\r\n const pixels = new Uint8Array(data.buffer)\r\n\r\n // Auto-detect: check if background is white/light\r\n const resolvedMode = mode === ('auto' as any) ? detectMode(pixels) : mode\r\n\r\n if (resolvedMode === 'magenta') {\r\n return tintMagenta(pixels, info)\r\n }\r\n\r\n if (resolvedMode === 'difference') {\r\n // Return as-is for difference blend\r\n return sharp(imagePath).ensureAlpha().png().toBuffer()\r\n }\r\n\r\n // ghost mode: uniform opacity reduction\r\n return tintGhost(pixels, info, 0.4)\r\n}\r\n\r\nfunction detectMode(pixels: Uint8Array): TintMode {\r\n // Sample corners + edges to detect background color\r\n // If most sampled pixels are bright (>220), use magenta mode\r\n // Otherwise use ghost mode\r\n let brightCount = 0\r\n const sampleSize = Math.min(pixels.length / 4, 1000)\r\n const step = Math.floor(pixels.length / 4 / sampleSize)\r\n\r\n for (let i = 0; i < sampleSize; i++) {\r\n const idx = i * step * 4\r\n const brightness = (pixels[idx] + pixels[idx + 1] + pixels[idx + 2]) / 3\r\n if (brightness > 220) brightCount++\r\n }\r\n\r\n return (brightCount / sampleSize) > 0.5 ? 'magenta' : 'ghost'\r\n}\r\n\r\nconst LIGHT_THRESHOLD = 230\r\nconst MAGENTA: [number, number, number] = [220, 40, 160]\r\n\r\nfunction tintMagenta(\r\n pixels: Uint8Array,\r\n info: { width: number; height: number },\r\n): Promise<Buffer> {\r\n for (let i = 0; i < pixels.length; i += 4) {\r\n const r = pixels[i], g = pixels[i + 1], b = pixels[i + 2]\r\n const brightness = (r + g + b) / 3\r\n\r\n if (brightness > LIGHT_THRESHOLD) {\r\n pixels[i + 3] = 0\r\n } else {\r\n const darkness = 1 - brightness / 255\r\n pixels[i] = MAGENTA[0]\r\n pixels[i + 1] = MAGENTA[1]\r\n pixels[i + 2] = MAGENTA[2]\r\n pixels[i + 3] = Math.round(darkness * 200)\r\n }\r\n }\r\n\r\n return sharp(Buffer.from(pixels.buffer), {\r\n raw: { width: info.width, height: info.height, channels: 4 },\r\n })\r\n .png()\r\n .toBuffer()\r\n}\r\n\r\nfunction tintGhost(\r\n pixels: Uint8Array,\r\n info: { width: number; height: number },\r\n opacity: number,\r\n): Promise<Buffer> {\r\n for (let i = 0; i < pixels.length; i += 4) {\r\n pixels[i + 3] = Math.round(pixels[i + 3] * opacity)\r\n }\r\n\r\n return sharp(Buffer.from(pixels.buffer), {\r\n raw: { width: info.width, height: info.height, channels: 4 },\r\n })\r\n .png()\r\n .toBuffer()\r\n}\r\n"],"mappings":";;;;AAAA,OAAO,WAAW;AAclB,eAAsB,gBACpB,WACA,OAAiB,QACA;AACjB,QAAM,EAAE,MAAM,KAAK,IAAI,MAAM,MAAM,SAAS,EACzC,YAAY,EACZ,IAAI,EACJ,SAAS,EAAE,mBAAmB,KAAK,CAAC;AAEvC,QAAM,SAAS,IAAI,WAAW,KAAK,MAAM;AAGzC,QAAM,eAAe,SAAU,SAAiB,WAAW,MAAM,IAAI;AAErE,MAAI,iBAAiB,WAAW;AAC9B,WAAO,YAAY,QAAQ,IAAI;AAAA,EACjC;AAEA,MAAI,iBAAiB,cAAc;AAEjC,WAAO,MAAM,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS;AAAA,EACvD;AAGA,SAAO,UAAU,QAAQ,MAAM,GAAG;AACpC;AAEA,SAAS,WAAW,QAA8B;AAIhD,MAAI,cAAc;AAClB,QAAM,aAAa,KAAK,IAAI,OAAO,SAAS,GAAG,GAAI;AACnD,QAAM,OAAO,KAAK,MAAM,OAAO,SAAS,IAAI,UAAU;AAEtD,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,UAAM,MAAM,IAAI,OAAO;AACvB,UAAM,cAAc,OAAO,GAAG,IAAI,OAAO,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,KAAK;AACvE,QAAI,aAAa,IAAK;AAAA,EACxB;AAEA,SAAQ,cAAc,aAAc,MAAM,YAAY;AACxD;AAEA,IAAM,kBAAkB;AACxB,IAAM,UAAoC,CAAC,KAAK,IAAI,GAAG;AAEvD,SAAS,YACP,QACA,MACiB;AACjB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AACzC,UAAM,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC;AACxD,UAAM,cAAc,IAAI,IAAI,KAAK;AAEjC,QAAI,aAAa,iBAAiB;AAChC,aAAO,IAAI,CAAC,IAAI;AAAA,IAClB,OAAO;AACL,YAAM,WAAW,IAAI,aAAa;AAClC,aAAO,CAAC,IAAI,QAAQ,CAAC;AACrB,aAAO,IAAI,CAAC,IAAI,QAAQ,CAAC;AACzB,aAAO,IAAI,CAAC,IAAI,QAAQ,CAAC;AACzB,aAAO,IAAI,CAAC,IAAI,KAAK,MAAM,WAAW,GAAG;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO,MAAM,OAAO,KAAK,OAAO,MAAM,GAAG;AAAA,IACvC,KAAK,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,UAAU,EAAE;AAAA,EAC7D,CAAC,EACE,IAAI,EACJ,SAAS;AACd;AAEA,SAAS,UACP,QACA,MACA,SACiB;AACjB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,GAAG;AACzC,WAAO,IAAI,CAAC,IAAI,KAAK,MAAM,OAAO,IAAI,CAAC,IAAI,OAAO;AAAA,EACpD;AAEA,SAAO,MAAM,OAAO,KAAK,OAAO,MAAM,GAAG;AAAA,IACvC,KAAK,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,UAAU,EAAE;AAAA,EAC7D,CAAC,EACE,IAAI,EACJ,SAAS;AACd;","names":[]}
@@ -1,7 +0,0 @@
1
- import {
2
- tintDesignImage
3
- } from "./chunk-SKEIVBOU.js";
4
- export {
5
- tintDesignImage
6
- };
7
- //# sourceMappingURL=tint-RUSSUAWA.js.map