scifig-agents 0.0.0-stage → 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lionelee
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.
package/NOTICE.md ADDED
@@ -0,0 +1,38 @@
1
+ # Third-party material
2
+
3
+ scifig-agents is an independent project. It is not affiliated with, endorsed by, or connected to
4
+ SciDraw AI (sci-draw.com), and it calls none of that service's interfaces.
5
+
6
+ ## Bundled data
7
+
8
+ **`skills/scifig/references/journal-figure-requirements.json`**
9
+ An unmodified copy of "Journal figure requirements: size, resolution, format and font
10
+ specifications by publisher" by SciDraw AI (2026), https://sci-draw.com/journal-figure-requirements,
11
+ licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Last verified by its
12
+ publisher on 2026-07-21.
13
+
14
+ **`skills/scifig/scripts/journals.py`**
15
+ Numeric presets hand-encoded from the data set above (a derived work under the same CC BY 4.0
16
+ terms for the encoded values). The underlying requirements belong to the respective publishers.
17
+
18
+ **`skills/scifig/references/ai-image-policies.md`**
19
+ A condensed table adapted from "AI-generated figure policies by journal and publisher" by
20
+ SciDraw AI (2026), https://sci-draw.com/ai-figure-policy, licensed under CC BY 4.0. Last verified
21
+ by its publisher on 2026-07-21. Rows were shortened; the policies belong to the respective publishers.
22
+
23
+ ## Colour palettes
24
+
25
+ - Okabe-Ito colour-universal-design palette: Masataka Okabe and Kei Ito, https://jfly.uni-koeln.de/color/
26
+ - "Bright" and "muted" qualitative schemes: Paul Tol, https://personal.sron.nl/~pault/
27
+
28
+ ## Methods
29
+
30
+ - Colour-vision-deficiency simulation matrices: G. M. Machado, M. M. Oliveira and L. A. F. Fernandes,
31
+ "A physiologically-based model for simulation of color vision deficiency", IEEE TVCG 15(6), 2009.
32
+ - Arrow meanings in `skills/scifig/references/figure-recipes.md` are those of the Systems Biology
33
+ Graphical Notation (SBGN) Process Description language, https://sbgn.github.io/specifications,
34
+ as summarised in "Scientific diagram notation reference: SBGN arcs, standards and conventions"
35
+ by SciDraw AI (2026), https://sci-draw.com/scientific-diagram-notation, CC BY 4.0, last verified
36
+ by its publisher on 2026-07-21.
37
+ - The graphical-abstract dimensions quoted in the same file were read from Elsevier's
38
+ graphical-abstract page on 2026-11-11.
package/README.md CHANGED
@@ -1,3 +1,139 @@
1
- # Temporary Holding Version
1
+ # scifig
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ English | [中文](README.zh-CN.md)
4
+
5
+ A skill pack that teaches AI coding agents to make publication-ready scientific figures. One command installs the `scifig` skill into Codex, Claude Code, Antigravity and the other agents it finds on your machine:
6
+
7
+ ```sh
8
+ npx scifig-agents setup
9
+ ```
10
+
11
+ Then ask your agent in plain words: "Draw a Transformer architecture figure for my group-meeting slides", "Make a graphical abstract", "Plot this CSV as a Nature single-column figure", "Export this figure as a 600 dpi TIFF", "Can this figure go to Cell?".
12
+
13
+ **Gallery:** <https://lionelee.github.io/scifig/>: figures Codex made with this skill, from a one-sentence request to the delivered file.
14
+
15
+ ## What it does
16
+
17
+ | Capability | How |
18
+ |---|---|
19
+ | Concept and structure figures: graphical abstracts, mechanisms, pathways, workflows, research roadmaps, frameworks, model and system architectures; from text, from a sketch, or by editing an image | The agent writes a detailed brief and hands it to **its own image-generation tool** (Codex `image_gen`, Antigravity `generate_image`). The skill supplies:<br>• how to write the brief and lay out each figure type;<br>• a six-layer content plan: spine, details, annotations, inputs and outputs, reading aids, pictures;<br>• tested visual styles;<br>• a low-saturation palette with colour codes, and hairline weights;<br>• a step-by-step verification.<br>No image API, no API key. |
20
+ | Text check | `check_text.py` reads the figure with OCR and compares it with the list of labels; every mismatch is cut out, enlarged and read again. Verdicts:<br>• `DIFFERS`: both readings spell it the same wrong way, so the figure has a typo and must be fixed;<br>• `MISSING`: OCR cannot tell (numbers, symbols, Greek letters, sub- and superscripts, a doubled letter read as one), so a person looks;<br>• `COUNT`: a label appears in the wrong number of places;<br>• `EXTRA`: text nobody asked for.<br>It also measures the type size. |
21
+ | Enlarge to high resolution | `enlarge.py` cuts a generated figure along clear space into equal, overlapping tiles; the image tool redraws each tile as a sharper copy, and the tiles are aligned and joined into a figure 1.6–2× larger. The detail is redrawn, not interpolated. A tile that moved, changed or cannot be read is enlarged plainly instead, and the report says so. One generation per tile. |
22
+ | Figures that must be code: PRISMA and CONSORT flow diagrams whose counts must add up; formal notations such as circuits, plasmid maps and phylogenetic trees | The agent writes drawing code on a millimetre canvas with exact text and numbers, and outputs a vector PDF. Other structure figures take this route too when the agent has no image tool, the destination refuses AI-generated images, or you ask for vector artwork; they look plainer than generated ones. |
23
+ | Data charts | The agent writes matplotlib code; `scistyle.py` handles journal column widths, fonts and sizes, colour-blind-safe palettes and vector output. The chart guide covers 46 chart types. Your data never goes through an image model. |
24
+ | Submission files | `export_figure.py` exports TIFF, PNG or PDF at a journal's column width and dpi. |
25
+ | Multi-panel figures | `panels.py` lays raster panels out at a journal width and adds a, b, c labels (kept as text in a PDF). |
26
+ | Editable versions | `editable.py` finds the text by OCR, erases it from the picture and puts it back as editable text: PPTX, SVG with editable text, or SVG with the shapes traced as vectors too. |
27
+ | Pre-submission check | `check_figure.py` checks file format, effective dpi, transparency, colour mode, greyscale legibility and colour-blind safety, and writes greyscale and colour-blindness previews.<br>• When a figure has no strong colours to compare, the two colour checks say SKIP, not PASS.<br>• `--look` measures fill chroma and line weight.<br>• `--screen` judges a figure for slides or the web only. |
28
+ | Figure legends | The agent drafts them from the figure; a missing fact stays `[placeholder]` and is never guessed. |
29
+ | Journal rules and AI-image policies | Figure specifications of 10 publishers and AI-image policies of 9, each with its verification date. |
30
+
31
+ ## Install
32
+
33
+ Needs Node.js 18.3 or later. The Python scripts need [uv](https://docs.astral.sh/uv/), which downloads their dependencies on first run, pinned to the day they were tested.
34
+
35
+ ```sh
36
+ npx scifig-agents setup # show the plan, install after you confirm
37
+ npx scifig-agents setup --dry-run # show the plan only, write nothing
38
+ npx scifig-agents setup --client codex --client claude_code --yes
39
+ npx scifig-agents setup --dir ~/somewhere/skills --yes
40
+ npx scifig-agents doctor # install state of each agent, image tools, uv
41
+ npx scifig-agents uninstall # remove only what this tool installed
42
+ ```
43
+
44
+ The same package is on GitHub Packages as `@lionelee/scifig-agents`. Without a registry, run it straight from GitHub with `npx github:lionelee/scifig setup`, or from a clone of this repository with `node bin/scifig.mjs setup`.
45
+
46
+ ### Supported agents
47
+
48
+ Detection matches [rea-agents](https://www.npmjs.com/package/rea-agents) 6.9.0: the same 29 agents, the same `--client` ids, and the same rule, under which an agent is present when its configuration file or its own folder exists. `doctor` lists the ones it finds on your machine.
49
+
50
+ | Where the skill goes | Agents (`--client` id) |
51
+ |---|---|
52
+ | The agent's own skills folder | Claude Code `claude_code` (`skills/` under `$CLAUDE_CONFIG_DIR` or `~/.claude`), CodeBuddy `codebuddy` (`~/.codebuddy/skills`), Antigravity `antigravity` (`~/.gemini/config/skills`), Trae `trae` (`~/.trae/skills`), WorkBuddy `workbuddy` (`~/.workbuddy/skills`), Hermes `hermes` (`~/.hermes/skills`, or under `$HERMES_HOME` and its active profile), Qoder `qoder` (`skills/` under `$QODER_CONFIG_DIR` or `~/.qoder`), Factory Droid `droid` (`~/.factory/skills`), ZCode `zcode` (`~/.zcode/skills`) |
53
+ | The shared `~/.agents/skills`, one copy for all of them | Codex `codex`, Claude Desktop `claude_desktop`, Cursor `cursor`, Cline CLI `cline`, Gemini CLI `gemini_cli`, Windsurf `windsurf`, Devin `devin`, OpenCode `opencode`, GitHub Copilot CLI `copilot_cli`, Command Code `commandcode`, Qwen Code `qwen_code`, Kimi Code `kimi`, VS Code `vscode`, LM Studio `lm_studio`, Grok Build `grok_build`, OMP `omp`, Pi `pi`, DeepSeek Harness `dsh`, FreeBuff `freebuff`, Grok Bot `grok_bot` |
54
+
55
+ With `GEMINI_CLI_HOME` or `DSH_AGENTS_HOME` set, Gemini CLI and DeepSeek Harness get `.agents/skills/` under those folders instead.
56
+
57
+ Finished generated figures need an agent with an image tool:
58
+
59
+ - **Codex**: built-in `image_gen`. The only one tested end to end.
60
+ - **Antigravity**: built-in `generate_image`. Checked against its documentation, not yet run.
61
+ - **Other agents**: no known built-in image tool. Charts, code-drawn diagrams, export, panels, editable versions, checks and legends all work; concept and structure figures are drawn in code and look plainer, unless you connect an image tool.
62
+
63
+ Whether an agent actually loads skills from these folders is up to the agent; the folders follow rea-agents.
64
+
65
+ How the installer behaves:
66
+
67
+ - Each install holds a `.scifig-install.json` with its version and a digest of its contents. A rerun leaves an identical install alone and replaces a different one as a whole.
68
+ - It refuses to touch a `scifig` folder that it did not install.
69
+ - Agents that read the same folder share one copy, and the plan names them all.
70
+ - Where it cannot ask you, it changes nothing unless you pass `--yes`.
71
+ - A half-made copy left by an interrupted setup is cleared by the next one.
72
+ - Start a new agent session to pick up the skill.
73
+
74
+ ## Good to know
75
+
76
+ The measurements behind these notes are recorded in [CALIBRATION.md](CALIBRATION.md).
77
+
78
+ - **The look comes from the agent's own image tool.** It has been tested only with Codex `image_gen`: 191 figures in 63 end-to-end sessions. Antigravity has not been tested. An agent without an image tool (Claude Code, for one) draws figures in code, and they look plainer.
79
+ - **Connectors are where first drafts go wrong most.** About one full figure in five needs an edit for a line. The skill's line rules were written from those faults:
80
+ - row and band names stand in a rail at the left end, not above;
81
+ - connectors are numbered, with their total;
82
+ - every line has one arrowhead;
83
+ - lines do not cross where it can be avoided.
84
+ - **The text check is binding: its verdict is not overruled by a look at the figure.** Image tools now and then misspell. In early tests, two figures with a typo were handed over after an agent glanced at them and blamed OCR, so a `DIFFERS` must be fixed. Its limits:
85
+ - OCR often reads a doubled letter as one; such places come back `MISSING`, with a hint to count the letters;
86
+ - Greek letters, sub- and superscripts and symbols are only flagged for a person to look at;
87
+ - after a word has been corrected, look at it enlarged.
88
+
89
+ A word that two edits cannot fix is erased and set in code.
90
+ - **Calibrated on SciDraw's 79 public samples** by measurement, not by eye:
91
+ - fills are faint tints, given as colour codes;
92
+ - generic subjects are drawn as full as the fuller half of the samples;
93
+ - outlines are about 2 px;
94
+ - pictures are small solid illustrations of three or four objects.
95
+ - **The content comes from your material.** For your own research, every fact in the figure is one you stated. The agent may add what a method you named is, takes in and puts out; it lists such lines for you to confirm, adds them when nobody answers, and names each one in its hand-over so you can strike it. Numbers, names, results and choices specific to your study are never made up. Textbook subjects (a Transformer, a training pipeline) are drawn in full from general knowledge, with the source named.
96
+ - **Two kinds of graphical abstract.** One for a journal follows the journal's rules (few words, no data plots); a research overview for a poster, a talk or a web page is drawn as four or five scenes, each with a heading and a line or two.
97
+ - **A generated figure is an AI-generated image.** Architectures and roadmaps now come from the image tool too, so publishers' AI-image policies apply to them (Nature journals, for one, do not accept AI-generated schematics). The skill asks where a figure is going first, and draws it in code where AI images are not allowed.
98
+ - **Resolution is limited, and can be bought with generations.** Codex returns about 1.5 megapixels per image (1672 × 941 at 16:9) and takes no size argument. `enlarge.py` redraws a figure tile by tile with the same tool:
99
+ - 4 tiles: about 1.6–2× larger, for 4 more generations;
100
+ - 6 tiles: about 2.1× larger, for 6 more.
101
+
102
+ When nobody can be asked, it enlarges only when the request itself asks for a large, sharp or high-resolution file. To spend no generations, `export_figure.py` interpolates, and says that this adds no real detail.
103
+ - **Landscape or portrait, by use.** Landscape has more pixels and finer lines, for slides and dense figures. Printed at a fixed width, portrait gives larger type, so a figure with a page to itself (as in a thesis) is portrait, with `enlarge.py` adding pixels.
104
+ - **The first runs of `editable.py` and `check_text.py` are slow.** Both use OCR: uv downloads about 100 MB of packages, OCR models included, about 600 MB unpacked in its cache. Later runs take seconds. The other scripts need only matplotlib, numpy and pillow.
105
+ - **Telemetry.** onnxruntime, which runs the OCR, reports usage to Microsoft in its official build and writes a device id into your home folder. `editable.py` sets `ORT_DISABLE_TELEMETRY=1` before loading it, `check_text.py` shares that code, and a test checks that nothing new appears in the home folder.
106
+ - **What OCR reads.** English and Chinese. Greek letters, sub- and superscripts, accented letters and symbols are often misread; Japanese kana, Korean and Cyrillic are not supported and never decide a verdict. Check converted labels one by one.
107
+ - **Chinese text** in charts and diagrams falls back to an installed Chinese font (Heiti SC on macOS) while Latin text keeps the journal font; both stay editable text in a PDF. For documents no journal typesets, such as a thesis or a grant proposal, set the size and fonts yourself: `scistyle.use("default", font_pt=10.5, fonts=["Times New Roman", "Songti SC"])`.
108
+ - **Journal data is dated.** The bundled figure specifications and AI-image policies were verified on 2026-07-21; check the journal's site before you submit.
109
+
110
+ ## Layout
111
+
112
+ ```
113
+ bin/scifig.mjs installer entry point: setup, uninstall, doctor
114
+ lib/ the agent table (aligned with rea-agents) and the install logic
115
+ skills/scifig/
116
+ SKILL.md entry point: routes each request
117
+ references/ illustration, figure recipes, diagrams, charts, export and checks, AI policies, journal data
118
+ scripts/ journals, scistyle, export_figure, panels, check_figure, check_text, enlarge, editable
119
+ test/ Node tests (installer, docs, packaging) and pytest tests (scripts)
120
+ eval/ end-to-end runs with Codex, text-check regression, comparison with SciDraw's samples (not shipped)
121
+ docs/ the GitHub Pages gallery
122
+ CALIBRATION.md the measurements behind the rules
123
+ ```
124
+
125
+ ## Relationship to sci-draw.com
126
+
127
+ This project was inspired by the public feature set of SciDraw AI (sci-draw.com): three drawing modes, data charts, export and conversion, pre-submission checks. It is an independent implementation:
128
+
129
+ - It calls none of sci-draw.com's interfaces and contains none of its code or prompts.
130
+ - It bundles reference data that SciDraw published under CC BY 4.0 (journal figure specifications, publisher AI-image policies, a notation table); sources and dates are in [NOTICE.md](NOTICE.md).
131
+ - To use SciDraw's own service, use its official plugin `TopLocalAI/scidraw-plugins`.
132
+
133
+ ## Contributing
134
+
135
+ See [CONTRIBUTING.md](CONTRIBUTING.md). To report a security issue, see [SECURITY.md](SECURITY.md).
136
+
137
+ ## License
138
+
139
+ [MIT](LICENSE) © lionelee. Third-party material: [NOTICE.md](NOTICE.md).
@@ -0,0 +1,147 @@
1
+ # scifig
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 给 AI 编程 agent 用的科研绘图技能包。一条命令就能把 `scifig` skill 装进 Codex、Claude Code、Antigravity 等检测到的 agent:
6
+
7
+ ```sh
8
+ npx scifig-agents setup
9
+ ```
10
+
11
+ 装好后,在 agent 里直接提需求就行,例如:"画一张 Transformer 模型架构图"、"画一张图文摘要"、"用这个 CSV 画一张 Nature 单栏的图"、"把这张图导出成 600 dpi 的 TIFF"、"看看这张图能不能投 Cell"。
12
+
13
+ **示例画廊**:<https://lionelee.github.io/scifig/>,都是 Codex 按这个 skill 从一句话需求做到交付的图。
14
+
15
+ ## 它做什么
16
+
17
+ | 能力 | 做法 |
18
+ |---|---|
19
+ | 概念图和结构图:图文摘要、机制图、通路图、流程图、技术路线图、框架图、模型和系统架构图;文生图、草图转图、改图 | agent 把需求写成详细的出图要求,交给 **agent 自带的生图工具**(Codex 的 `image_gen`、Antigravity 的 `generate_image`)出成品。skill 提供这些内容:<br>• 出图要求的写法、各类图的版式;<br>• 内容按六层规划:主体、每个元素里的要点、连线上的标注、输入与产出、阅读辅助、配图;<br>• 实测过的视觉样式;<br>• 带色值的低饱和度配色、细框线的写法;<br>• 逐项核对步骤。<br>本包不调用任何图像 API,也不需要 API key。 |
20
+ | 图中文字核对 | `check_text.py` 用 OCR 读出图里的文字,和标签清单逐个比对。没对上的地方会裁出来放大,再读一遍。各种结论:<br>• `DIFFERS`:两遍都读成同一个和清单不一样的拼写,说明图上写错了,要改图;<br>• `MISSING`:OCR 认不准(编号、符号、希腊字母、上下标,或者双写字母被读成一个),要人看;<br>• `COUNT`:标签出现的次数不对;<br>• `EXTRA`:多出来的文字。<br>它还会量出图中文字的字号。 |
21
+ | 放大成高分辨率 | `enlarge.py` 把生成的图沿空白处切成大小相同、彼此重叠的几块,由生图工具把每块照原样重画成清晰版,再对齐拼回,得到 1.6–2 倍大的图。细节是重画出来的,不是插值。位置偏了、内容变了或读不出来的块,改用普通放大,并在报告里写明。每块多花一次生成。 |
22
+ | 必须用代码画的图:PRISMA / CONSORT 这类数字要能逐个核对的流程图,以及电路图、质粒图谱、系统发育树等规范记号 | agent 写绘图代码,在毫米坐标的画布上画框和箭头,文字和数字逐字准确,输出矢量 PDF。以下情况,其它结构图也走这条路,观感比生图朴素:没有生图工具的 agent、投稿方不接受 AI 图片、你明确要矢量图。 |
23
+ | 数据图表 | agent 写 matplotlib 代码,`scistyle.py` 负责期刊栏宽、字体字号、色盲友好配色和矢量输出。选图指引覆盖 46 种图表。数据不经过生图模型。 |
24
+ | 投稿文件导出 | `export_figure.py` 按期刊栏宽和 dpi 导出 TIFF、PNG、PDF。 |
25
+ | 多面板拼图 | `panels.py` 把多张位图按期刊栏宽排成一张图,自动加 a、b、c 面板字母(PDF 里的字母保持为文字)。 |
26
+ | 可编辑版本 | `editable.py` 识别图中文字,从图上擦除,再还原成可编辑文本。可输出 PPTX、SVG(仅文字可编辑)或 SVG(图形也转成矢量)。 |
27
+ | 投稿前检查 | `check_figure.py` 检查文件格式、有效 dpi、透明度、色彩模式、灰度可读性和色盲安全,可输出灰度和色盲模拟预览图。<br>• 图里没有足够醒目的颜色可比时,两项颜色检查报 SKIP 而不是 PASS。<br>• 加 `--look` 会量出色块的彩度和框线的粗细。<br>• 加 `--screen` 表示图只用于幻灯片或网页。 |
28
+ | 图注 | agent 看图起草;缺的事实留 `[placeholder]`,不从图里猜。 |
29
+ | 期刊规范和 AI 图片政策 | 内置 10 家出版社的图片规格和 9 家的 AI 图片政策,都带核实日期。 |
30
+
31
+ ## 安装
32
+
33
+ 需要 Node.js 18.3 以上。运行 Python 脚本需要 [uv](https://docs.astral.sh/uv/),脚本的依赖在第一次运行时由 uv 自动下载,版本固定在测试通过的那一天。
34
+
35
+ ```sh
36
+ npx scifig-agents setup # 列出计划,确认后安装到检测到的 agent
37
+ npx scifig-agents setup --dry-run # 只看计划,不写任何文件
38
+ npx scifig-agents setup --client codex --client claude_code --yes
39
+ npx scifig-agents setup --dir ~/somewhere/skills --yes
40
+ npx scifig-agents doctor # 各 agent 的安装状态、生图工具、uv
41
+ npx scifig-agents uninstall # 只删除本工具安装的目录
42
+ ```
43
+
44
+ 自动检测的 agent 和 [rea-agents](https://www.npmjs.com/package/rea-agents) 6.9.0 一样,共 29 个。判断规则也一样:agent 的配置文件或它自己的目录存在,就算装了。`--client` 用的也是同一套 id,`doctor` 会列出这台机器上找到了哪些。skill 装到哪里分两种情况:
45
+
46
+ | skill 装到哪里 | Agent(`--client` 的 id) |
47
+ |---|---|
48
+ | 各自的 skill 目录 | Claude Code `claude_code`(`$CLAUDE_CONFIG_DIR` 或 `~/.claude` 下的 `skills/`)、CodeBuddy `codebuddy`(`~/.codebuddy/skills`)、Antigravity `antigravity`(`~/.gemini/config/skills`)、Trae `trae`(`~/.trae/skills`)、WorkBuddy `workbuddy`(`~/.workbuddy/skills`)、Hermes `hermes`(`~/.hermes/skills`,或 `$HERMES_HOME` 及当前 profile 下的)、Qoder `qoder`(`$QODER_CONFIG_DIR` 或 `~/.qoder` 下的 `skills/`)、Factory Droid `droid`(`~/.factory/skills`)、ZCode `zcode`(`~/.zcode/skills`) |
49
+ | 共用的 `~/.agents/skills`,只装一份 | Codex `codex`、Claude Desktop `claude_desktop`、Cursor `cursor`、Cline CLI `cline`、Gemini CLI `gemini_cli`、Windsurf `windsurf`、Devin `devin`、OpenCode `opencode`、GitHub Copilot CLI `copilot_cli`、Command Code `commandcode`、Qwen Code `qwen_code`、Kimi Code `kimi`、VS Code `vscode`、LM Studio `lm_studio`、Grok Build `grok_build`、OMP `omp`、Pi `pi`、DeepSeek Harness `dsh`、FreeBuff `freebuff`、Grok Bot `grok_bot` |
50
+
51
+ Gemini CLI 和 DeepSeek Harness 设了 `GEMINI_CLI_HOME`、`DSH_AGENTS_HOME` 时,改装到那里的 `.agents/skills/`。
52
+
53
+ 能不能出生图成品,取决于 agent 自带的生图工具:
54
+
55
+ - **Codex**:内置 `image_gen`。只有它做过实测。
56
+ - **Antigravity**:内置 `generate_image`。只按官方文档核对过,没有实际跑过。
57
+ - **其它 agent**:没有已知的内置生图工具。数据图表、代码绘图、导出、拼图、可编辑版本、投稿检查、图注都能用;概念图和结构图只能用代码画(观感朴素一些),或者另外接入生图工具。
58
+
59
+ 某个 agent 实际会不会从这些目录加载 skill,由它自己决定;目录的选法和 rea-agents 保持一致。
60
+
61
+ 安装器的行为:
62
+
63
+ - 每个安装目录里写一个 `.scifig-install.json`,记录版本和内容摘要。再次执行时,内容相同就不动,不同就整体替换。
64
+ - 目标位置已经有一个不是本工具安装的 `scifig` 目录时,拒绝操作,不覆盖。
65
+ - 几个 agent 读同一个目录时,只装一份,计划里会列出读它的所有 agent。
66
+ - 无法交互确认时必须显式加 `--yes`,否则不做任何修改。
67
+ - 上次安装被中断时会留下复制了一半的临时目录,下次 setup 会先清掉。
68
+ - 装好后要新开一个 agent 会话,才会加载到这个 skill。
69
+
70
+ 同一个包在 GitHub Packages 上叫 `@lionelee/scifig-agents`。不经过 npm 仓库的话,可以直接从 GitHub 运行 `npx github:lionelee/scifig setup`,或者克隆仓库后运行 `node bin/scifig.mjs setup`。
71
+
72
+ ## 使用前须知
73
+
74
+ 下面提到的实测数字都记在 [CALIBRATION.md](CALIBRATION.md)(英文)里。
75
+
76
+ - **成品观感取决于 agent 自带的生图工具。** 出图效果只在 Codex 的 `image_gen` 上实测过(191 张图,63 次端到端测试),Antigravity 没有实测。没有内置生图工具的 agent(例如 Claude Code)只能用代码画,观感朴素一些。
77
+ - **连线是首稿最常出问题的地方。** 内容画满的图里,大约每五张就有一张要为连线改一次图。skill 里的连线规则就是针对这些问题写的:
78
+ - 行和色带的名称放在左端竖栏里;
79
+ - 连线逐条编号,并写明总数;
80
+ - 每条线只画一个箭头;
81
+ - 尽量不让连线交叉。
82
+ - **文字靠核对脚本把关,脚本的结论不能凭看图否决。** 生图工具偶尔会写错字。早期测试里,有两张带错字的图被 agent 看一眼就判成"OCR 认错"交付了,所以 `DIFFERS` 必须改图。有三点局限:
83
+ - 双写字母 OCR 常读成一个,这类地方只报 `MISSING`,并提示数一数字母;
84
+ - 希腊字母、上下标和符号 OCR 认不准,只提示"请人看";
85
+ - 改过字之后,要放大看一眼那处文字。
86
+
87
+ 个别错字改两次也改不掉,这时把那一行抹掉,用代码写上正确的字。
88
+ - **观感按 SciDraw 的 79 张公开样例校准过。** 配色、内容量、字号、框线粗细、配图的画法和细节都是量出来再对齐的,没有凭看图判断:
89
+ - 色块是淡彩,带色值;
90
+ - 通用主题按样例里较满的那一半来画;
91
+ - 框线约 2 px;
92
+ - 配图是三四件物体组成的实心小插画。
93
+ - **内容多少取决于你给的材料。** 如果是你自己的研究,图里的事实只来自你说过的话。agent 可以补"这个方法本身是什么、输入什么、产出什么"这类通用内容,会先列出来问你,没人回答时就补上,并在交付说明里逐条列出,供你删改。研究特有的数字、名称、结果和选择不会替你编。教科书式的通用主题(如 Transformer、训练流程)则按通用知识画满,并在交付说明里写明来源。
94
+ - **图文摘要分两种。** 投期刊的按期刊要求来(字少,不放数据图);放在海报、报告、网页上的"研究总览图"画成四五个场景,每个场景配标题和一两行说明。
95
+ - **生成的图属于 AI 生成图片。** 架构图、路线图这类结构图现在也由生图工具生成,出版社的 AI 图片政策对它们同样适用(例如 Nature 系列不接受 AI 生成的示意图)。skill 会先问图用在哪里,不允许时改用代码画。
96
+ - **生成图的分辨率有限,可以多花几次生成补上。** Codex 的生图工具每次返回约 150 万像素(16:9 时为 1672 × 941),没有尺寸参数。`enlarge.py` 用同一个生图工具分块重画再拼回:
97
+ - 分 4 块,约放大到 1.6–2 倍,多花 4 次生成;
98
+ - 分 6 块,约放大到 2.1 倍,多花 6 次生成。
99
+
100
+ 没有人可问时,只有需求里写了要大图、清晰图或者指明了分辨率,才会自动放大。不想多花生成时,`export_figure.py` 可以插值放大,脚本会如实说明那不是真细节。
101
+ - **横版还是竖版要看用途。** 横版像素多、线条细,适合幻灯片和内容密的图。按固定宽度印刷时,竖版的字更大。所以单独占一页、字要大的图(学位论文常见)用竖版,再用 `enlarge.py` 补像素。
102
+ - **`editable.py` 和 `check_text.py` 第一次运行较慢。** 两者都用 OCR,uv 会下载约 100 MB 的依赖(含 OCR 模型),解压后约 600 MB,放在 uv 缓存里;之后每次运行只要几秒。其它脚本只依赖 matplotlib、numpy、pillow。
103
+ - **遥测。** onnxruntime 是 OCR 的运行库,它的官方构建默认向 Microsoft 上报使用数据,并在用户目录写入设备标识。`editable.py` 在加载它之前设置了 `ORT_DISABLE_TELEMETRY=1` 来关闭上报,`check_text.py` 复用同一段代码。测试里有一项专门检查运行后用户目录没有新增文件。
104
+ - **OCR 的范围。** 模型覆盖英文和中文。希腊字母、上下标、带重音的字母和符号经常识别错;日文假名、韩文、西里尔字母不支持,核对时不拿这些字下结论。转换结果要逐个标签核对。
105
+ - **中文。** 图表和结构化图里的中文会自动回退到已安装的中文字体(macOS 上默认是 Heiti SC),拉丁文字仍用期刊字体,PDF 里两者都是可编辑文字。学位论文、基金申请书这类不由期刊排版的文档,可以指定字号和字体,例如五号字、Times New Roman 加宋体:`scistyle.use("default", font_pt=10.5, fonts=["Times New Roman", "Songti SC"])`。
106
+ - **期刊数据有日期。** 内置的期刊规格和 AI 图片政策核实于 2026-07-21,投稿前以期刊官网为准。
107
+
108
+ ## 目录
109
+
110
+ ```
111
+ bin/scifig.mjs 安装器入口:setup、uninstall、doctor
112
+ lib/ 各 agent 的 skill 目录和安装逻辑
113
+ skills/scifig/
114
+ SKILL.md 入口:按需求分流
115
+ references/ 插图、版式、结构化图、图表、导出与检查、AI 图片政策、期刊规格数据
116
+ scripts/ journals.py、scistyle.py、export_figure.py、panels.py、check_figure.py、check_text.py、enlarge.py、editable.py
117
+ test/ 安装器、文档、打包的 Node 测试;脚本的 pytest 测试
118
+ eval/ 真实出图的端到端测试、文字核对的回归、和 SciDraw 样例对比的量法(不随 npm 包发布)
119
+ docs/ GitHub Pages 示例画廊
120
+ CALIBRATION.md 规则背后的实测数字
121
+ ```
122
+
123
+ ## 与 sci-draw.com 的关系
124
+
125
+ 本项目参考了 SciDraw AI(sci-draw.com)公开的产品能力:三种绘图模式、数据图表、导出转换、投稿前检查。它是独立实现:
126
+
127
+ - 不调用 sci-draw.com 的任何接口,不包含它的代码或提示词。
128
+ - 打包了它以 CC BY 4.0 发布的参考数据(期刊图片规格、出版社 AI 图片政策、记号表),出处和核实日期见 `NOTICE.md`。
129
+ - 想直接使用 SciDraw 的服务,请用它的官方插件 `TopLocalAI/scidraw-plugins`。
130
+
131
+ ## 参与开发
132
+
133
+ 流程和约定见 [CONTRIBUTING.md](CONTRIBUTING.md)(英文),安全问题的报告方式见 [SECURITY.md](SECURITY.md)。常用命令:
134
+
135
+ ```sh
136
+ npm test
137
+ PYTHONDONTWRITEBYTECODE=1 uv run --with pytest --with pillow --with numpy --with matplotlib --with python-pptx pytest test/python -q -p no:cacheprovider
138
+ ```
139
+
140
+ 单元测试之外,改 skill 的文档或脚本之后,还要用 `eval/` 里的工具做真实回归(用法见 `eval/README.md`):
141
+
142
+ - 改了 `check_text.py`,就在全部端到端图上和上一版逐条对比;
143
+ - 改了出图规则,就开几个全新的 Codex 会话复验,这一步会花生图额度。
144
+
145
+ ## 许可
146
+
147
+ [MIT](LICENSE) © lionelee。第三方材料见 [NOTICE.md](NOTICE.md)。
package/bin/scifig.mjs ADDED
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { createInterface } from "node:readline/promises";
7
+ import { fileURLToPath } from "node:url";
8
+ import { parseArgs } from "node:util";
9
+ import { CLIENTS, isPresent, sharedSkillsDir, skillsDirOf } from "../lib/clients.mjs";
10
+ import { SKILL_NAME, applyInstall, applyUninstall, digestTree, planInstall, planUninstall, readManifest } from "../lib/install.mjs";
11
+
12
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
13
+ const VERSION = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).version;
14
+ const SOURCE_DIR = join(ROOT, "skills", SKILL_NAME);
15
+
16
+ /** The agent ids, a few to a line under the option that takes them. */
17
+ const CLIENT_IDS = CLIENTS.map((c) => c.id)
18
+ .join(", ")
19
+ .replace(/(.{1,74})(, |$)/g, (_, line, comma) => ` ${line}${comma ? "," : ""}\n`)
20
+ .trimEnd();
21
+
22
+ const USAGE = `scifig-agents ${VERSION}
23
+
24
+ Usage: scifig-agents <command> [options]
25
+
26
+ Commands:
27
+ setup Install the scifig skill into your agents
28
+ uninstall Remove installs made by this tool
29
+ doctor Show where the skill is installed and what its scripts need
30
+
31
+ Options:
32
+ --client <id> An agent to install into (repeatable; default: every agent found):
33
+ ${CLIENT_IDS}
34
+ --dir <path> A skills folder to use as well (repeatable)
35
+ --dry-run Show the plan without changing anything
36
+ --yes Apply without asking
37
+ --json Machine-readable output
38
+ --version Print the version
39
+ --help Print this help`;
40
+
41
+ class UsageError extends Error {}
42
+
43
+ function parse(argv) {
44
+ try {
45
+ return parseArgs({
46
+ args: argv,
47
+ allowPositionals: true,
48
+ options: {
49
+ client: { type: "string", multiple: true, default: [] },
50
+ dir: { type: "string", multiple: true, default: [] },
51
+ "dry-run": { type: "boolean", default: false },
52
+ yes: { type: "boolean", default: false },
53
+ json: { type: "boolean", default: false },
54
+ version: { type: "boolean", default: false },
55
+ help: { type: "boolean", default: false },
56
+ },
57
+ });
58
+ } catch (error) {
59
+ throw new UsageError(error.message);
60
+ }
61
+ }
62
+
63
+ const isExplicit = (options) => options.client.length > 0 || options.dir.length > 0;
64
+ const hostContext = () => ({ home: homedir(), env: process.env, platform: process.platform });
65
+
66
+ /**
67
+ * The --client/--dir choices, or every client accepted by `byDefault` when none are given: one target per
68
+ * folder. Agents that read the same folder share one target, which names them all; the shared
69
+ * ~/.agents/skills is client "shared", as in rea-agents.
70
+ */
71
+ function selectTargets(options, context, byDefault) {
72
+ const unknown = options.client.filter((id) => !CLIENTS.some((c) => c.id === id));
73
+ if (unknown.length > 0) {
74
+ throw new UsageError(`unknown client: ${unknown.join(", ")} (choose from ${CLIENTS.map((c) => c.id).join(", ")})`);
75
+ }
76
+ const shared = join(sharedSkillsDir(context), SKILL_NAME);
77
+ const targets = new Map();
78
+ for (const c of CLIENTS.filter((c) => (isExplicit(options) ? options.client.includes(c.id) : byDefault(c)))) {
79
+ const path = join(skillsDirOf(c, context), SKILL_NAME);
80
+ const target = targets.get(path) ?? { client: path === shared ? "shared" : c.id, clients: [], labels: [], path };
81
+ target.clients.push(c.id);
82
+ target.labels.push(c.label);
83
+ targets.set(path, target);
84
+ }
85
+ for (const dir of options.dir) {
86
+ const path = join(resolve(dir), SKILL_NAME);
87
+ if (!targets.has(path)) targets.set(path, { client: "custom", clients: [], labels: ["Custom folder"], path });
88
+ }
89
+ return [...targets.values()].map(({ labels, ...target }) => ({ ...target, label: labels.join(", ") }));
90
+ }
91
+
92
+ function printPlan(title, targets, footer) {
93
+ console.log(`scifig-agents ${VERSION}: ${title}`);
94
+ for (const t of targets) {
95
+ const extra = t.reason ?? (t.installed_version ? `(installed: ${t.installed_version})` : "");
96
+ const shared = t.clients.length > 1; // a folder several agents read: name them on a line of their own
97
+ console.log(` ${t.action.padEnd(10)} ${(shared ? "Shared folder" : t.label).padEnd(14)} ${t.path} ${extra}`.trimEnd());
98
+ if (shared) console.log(` ${"".padEnd(10)} read by ${t.label}`);
99
+ }
100
+ if (footer) console.log(footer);
101
+ }
102
+
103
+ async function confirm(question) {
104
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
105
+ const answer = await prompt.question(`${question} [y/N] `);
106
+ prompt.close();
107
+ return /^y(es)?$/i.test(answer.trim());
108
+ }
109
+
110
+ /**
111
+ * Show a plan, get consent, apply it. Returns the exit code.
112
+ * `fatal` actions stop everything; `idle` actions need no work.
113
+ */
114
+ async function execute(command, options, targets, { apply, idle, fatal, done }) {
115
+ const dryRun = options["dry-run"];
116
+ const report = (applied, footer) => {
117
+ if (options.json) console.log(JSON.stringify({ command, version: VERSION, dry_run: dryRun, applied, targets }, null, 2));
118
+ else printPlan(dryRun ? `${command} (dry run)` : command, targets, footer);
119
+ };
120
+
121
+ if (targets.some((target) => fatal.includes(target.action))) {
122
+ report(false, "Nothing was changed: move the blocked folder away, or pick the other agents with --client.");
123
+ return 1;
124
+ }
125
+ if (dryRun) {
126
+ report(false, "Dry run: nothing was changed.");
127
+ return 0;
128
+ }
129
+ const pending = targets.filter((target) => !idle.includes(target.action));
130
+ if (pending.length > 0 && !options.yes) {
131
+ if (!process.stdin.isTTY) {
132
+ report(false, "Nothing was changed.");
133
+ console.error("Re-run with --yes to apply this plan, or with --dry-run to preview it.");
134
+ return 1;
135
+ }
136
+ printPlan(command, targets);
137
+ if (!(await confirm("Apply this plan?"))) {
138
+ console.log("Nothing was changed.");
139
+ return 1;
140
+ }
141
+ }
142
+ for (const target of pending) apply(target.path);
143
+ report(true, pending.length > 0 ? done : "Nothing to do.");
144
+ return 0;
145
+ }
146
+
147
+ function setup(options) {
148
+ const context = hostContext();
149
+ const targets = selectTargets(options, context, (c) => isPresent(c, context));
150
+ if (targets.length === 0) {
151
+ console.error("No supported agent found. Choose one with --client, or pass a skills folder with --dir.");
152
+ return 1;
153
+ }
154
+ const source = { dir: SOURCE_DIR, version: VERSION, digest: digestTree(SOURCE_DIR) };
155
+ return execute("setup", options, targets.map((t) => ({ ...t, ...planInstall(t.path, source) })), {
156
+ apply: (path) => applyInstall(path, source),
157
+ idle: ["unchanged"],
158
+ fatal: ["blocked"],
159
+ done: "Done. Start a new agent session so it picks up the skill.",
160
+ });
161
+ }
162
+
163
+ function uninstall(options) {
164
+ // With no explicit choice, look everywhere and report only folders that exist.
165
+ const targets = selectTargets(options, hostContext(), () => true)
166
+ .map((t) => ({ ...t, ...planUninstall(t.path) }))
167
+ .filter((t) => isExplicit(options) || t.action !== "absent");
168
+ return execute("uninstall", options, targets, {
169
+ apply: applyUninstall,
170
+ idle: ["absent", "blocked"],
171
+ fatal: [],
172
+ done: "Removed.",
173
+ });
174
+ }
175
+
176
+ function toolVersion(command) {
177
+ const result = spawnSync(command, ["--version"], { encoding: "utf8" });
178
+ return result.status === 0 ? result.stdout.trim().split("\n")[0] : null;
179
+ }
180
+
181
+ function doctor(options) {
182
+ const context = hostContext();
183
+ const digest = digestTree(SOURCE_DIR);
184
+ const clients = CLIENTS.map((c) => {
185
+ const path = join(skillsDirOf(c, context), SKILL_NAME);
186
+ const manifest = readManifest(path);
187
+ return {
188
+ client: c.id,
189
+ label: c.label,
190
+ detected: isPresent(c, context),
191
+ path,
192
+ state: manifest ? "installed" : existsSync(path) ? "unmanaged" : "not_installed",
193
+ installed_version: manifest?.version ?? null,
194
+ up_to_date: manifest ? manifest.version === VERSION && manifest.digest === digest : null,
195
+ native_image_tool: c.imageTool ?? null,
196
+ };
197
+ });
198
+ const tools = { uv: toolVersion("uv"), python3: toolVersion("python3") };
199
+ if (options.json) {
200
+ console.log(JSON.stringify({ command: "doctor", version: VERSION, clients, tools }, null, 2));
201
+ return 0;
202
+ }
203
+ console.log(`scifig-agents ${VERSION}: doctor`);
204
+ // The agents found here, or with an install of their own; the rest by name only.
205
+ const shown = clients.filter((c) => c.detected || c.state !== "not_installed");
206
+ const width = Math.max(12, ...shown.map((c) => c.label.length));
207
+ for (const c of shown) {
208
+ const install =
209
+ c.state === "installed" ? `installed ${c.installed_version}${c.up_to_date ? "" : " (outdated: run setup)"}` : c.state.replace("_", " ");
210
+ const images = c.native_image_tool ? `image tool: ${c.native_image_tool}` : "no built-in image tool (diagrams, charts, export and checks still work)";
211
+ console.log(` ${c.label.padEnd(width)} ${(c.detected ? "found" : "not found").padEnd(10)} ${install.padEnd(30)} ${images}`);
212
+ }
213
+ const absent = clients.filter((c) => !shown.includes(c)).map((c) => c.label);
214
+ if (absent.length > 0) console.log(` Not found: ${absent.join(", ")}`);
215
+ console.log(` ${"uv".padEnd(width)} ${tools.uv ?? "missing: the Python scripts need it, see https://docs.astral.sh/uv/"}`);
216
+ console.log(` ${"python3".padEnd(width)} ${tools.python3 ?? "missing"}`);
217
+ return 0;
218
+ }
219
+
220
+ async function main() {
221
+ const { values: options, positionals } = parse(process.argv.slice(2));
222
+ if (options.version) {
223
+ console.log(VERSION);
224
+ return 0;
225
+ }
226
+ const [command, ...rest] = positionals;
227
+ if (options.help || command === undefined) {
228
+ console.log(USAGE);
229
+ return 0;
230
+ }
231
+ if (rest.length > 0) throw new UsageError(`unexpected argument: ${rest[0]}`);
232
+ const commands = { setup, uninstall, doctor };
233
+ if (!Object.hasOwn(commands, command)) throw new UsageError(`unknown command: ${command}`);
234
+ return commands[command](options);
235
+ }
236
+
237
+ try {
238
+ process.exitCode = await main();
239
+ } catch (error) {
240
+ if (error instanceof UsageError) {
241
+ console.error(`${error.message}\nRun scifig-agents --help for usage.`);
242
+ process.exitCode = 2;
243
+ } else if (error.syscall) {
244
+ // A filesystem failure (permissions, a file where a folder should be): the message names the path.
245
+ console.error(`scifig-agents: ${error.message}`);
246
+ process.exitCode = 1;
247
+ } else {
248
+ throw error;
249
+ }
250
+ }